Browse Source

Merge pull request #496 from AppFlowy-IO/feat_time_format

Nathan.fooo 3 years ago
parent
commit
6d392c240a
84 changed files with 4343 additions and 1581 deletions
  1. 0 0
      frontend/app_flowy/assets/images/grid/clock.svg
  2. 6 6
      frontend/app_flowy/lib/startup/deps_resolver.dart
  3. 0 375
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart
  4. 73 0
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart
  5. 182 0
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart
  6. 109 0
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_cache.dart
  7. 112 0
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart
  8. 69 0
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart
  9. 2 2
      frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart
  10. 221 0
      frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart
  11. 26 34
      frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart
  12. 3 3
      frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart
  13. 15 44
      frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart
  14. 1 1
      frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart
  15. 1 4
      frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart
  16. 3 3
      frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart
  17. 6 6
      frontend/app_flowy/lib/workspace/application/grid/field/field_action_sheet_bloc.dart
  18. 2 2
      frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart
  19. 8 5
      frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart
  20. 41 16
      frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart
  21. 1 1
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  22. 5 6
      frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart
  23. 1 1
      frontend/app_flowy/lib/workspace/application/grid/grid_service.dart
  24. 1 1
      frontend/app_flowy/lib/workspace/application/grid/prelude.dart
  25. 1 1
      frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart
  26. 1 1
      frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart
  27. 1 1
      frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart
  28. 3 4
      frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart
  29. 2 2
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart
  30. 0 217
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell.dart
  31. 410 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/calendar.dart
  32. 92 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart
  33. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart
  34. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart
  35. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart
  36. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart
  37. 6 8
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart
  38. 47 23
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart
  39. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart
  40. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart
  41. 3 3
      frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart
  42. 12 2
      frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart
  43. 77 9
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart
  44. 1 0
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart
  45. 3 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbenum.dart
  46. 3 2
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbjson.dart
  47. 178 15
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
  48. 33 6
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
  49. 181 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/date_type_option.pb.dart
  50. 28 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/date_type_option.pbjson.dart
  51. 18 10
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart
  52. 13 9
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart
  53. 37 63
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_type_option.pb.dart
  54. 9 11
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_type_option.pbjson.dart
  55. 96 48
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  56. 29 12
      frontend/rust-lib/flowy-grid/src/event_map.rs
  57. 587 7
      frontend/rust-lib/flowy-grid/src/protobuf/model/date_type_option.rs
  58. 41 27
      frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs
  59. 75 145
      frontend/rust-lib/flowy-grid/src/protobuf/model/selection_type_option.rs
  60. 11 0
      frontend/rust-lib/flowy-grid/src/protobuf/proto/date_type_option.proto
  61. 12 8
      frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto
  62. 4 6
      frontend/rust-lib/flowy-grid/src/protobuf/proto/selection_type_option.proto
  63. 12 12
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs
  64. 376 56
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs
  65. 1 1
      frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs
  66. 47 30
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs
  67. 95 109
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs
  68. 19 12
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs
  69. 0 1
      frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option_data.rs
  70. 10 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/util.rs
  71. 56 13
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  72. 38 28
      frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs
  73. 2 2
      frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs
  74. 4 5
      frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs
  75. 29 14
      frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs
  76. 4 1
      shared-lib/flowy-error-code/src/code.rs
  77. 9 5
      shared-lib/flowy-error-code/src/protobuf/model/code.rs
  78. 2 1
      shared-lib/flowy-error-code/src/protobuf/proto/code.proto
  79. 63 8
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  80. 1 3
      shared-lib/flowy-grid-data-model/src/entities/meta.rs
  81. 653 120
      shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
  82. 12 2
      shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto
  83. 1 1
      shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs
  84. 1 0
      shared-lib/flowy-user-data-model/src/parser/user_email.rs

+ 0 - 0
frontend/app_flowy/assets/images/editor/clock_alarm.svg → frontend/app_flowy/assets/images/grid/clock.svg


+ 6 - 6
frontend/app_flowy/lib/startup/deps_resolver.dart

@@ -153,18 +153,18 @@ void _resolveGridDeps(GetIt getIt) {
   getIt.registerFactoryParam<FieldActionSheetBloc, GridFieldCellContext, void>(
     (data, _) => FieldActionSheetBloc(
       field: data.field,
-      service: FieldService(gridId: data.gridId),
+      fieldService: FieldService(gridId: data.gridId, fieldId: data.field.id),
     ),
   );
 
   getIt.registerFactoryParam<FieldEditorBloc, String, EditFieldContextLoader>(
     (gridId, fieldLoader) => FieldEditorBloc(
-      service: FieldService(gridId: gridId),
+      gridId: gridId,
       fieldLoader: fieldLoader,
     ),
   );
 
-  getIt.registerFactoryParam<TextCellBloc, GridDefaultCellContext, void>(
+  getIt.registerFactoryParam<TextCellBloc, GridCellContext, void>(
     (context, _) => TextCellBloc(
       cellContext: context,
     ),
@@ -176,19 +176,19 @@ void _resolveGridDeps(GetIt getIt) {
     ),
   );
 
-  getIt.registerFactoryParam<NumberCellBloc, GridDefaultCellContext, void>(
+  getIt.registerFactoryParam<NumberCellBloc, GridCellContext, void>(
     (context, _) => NumberCellBloc(
       cellContext: context,
     ),
   );
 
-  getIt.registerFactoryParam<DateCellBloc, GridDefaultCellContext, void>(
+  getIt.registerFactoryParam<DateCellBloc, GridDateCellContext, void>(
     (context, _) => DateCellBloc(
       cellContext: context,
     ),
   );
 
-  getIt.registerFactoryParam<CheckboxCellBloc, GridDefaultCellContext, void>(
+  getIt.registerFactoryParam<CheckboxCellBloc, GridCellContext, void>(
     (cellData, _) => CheckboxCellBloc(
       service: CellService(),
       cellContext: cellData,

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

@@ -1,375 +0,0 @@
-import 'dart:async';
-import 'dart:collection';
-
-import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
-import 'package:dartz/dartz.dart';
-import 'package:equatable/equatable.dart';
-import 'package:flowy_sdk/dispatch/dispatch.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
-import 'package:flutter/foundation.dart';
-import 'package:freezed_annotation/freezed_annotation.dart';
-
-import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
-
-part 'cell_service.freezed.dart';
-
-typedef GridDefaultCellContext = GridCellContext<Cell>;
-typedef GridSelectOptionCellContext = GridCellContext<SelectOptionContext>;
-
-class GridCellContextBuilder {
-  final GridCellCache _cellCache;
-  final GridCell _gridCell;
-  GridCellContextBuilder({
-    required GridCellCache cellCache,
-    required GridCell gridCell,
-  })  : _cellCache = cellCache,
-        _gridCell = gridCell;
-
-  GridCellContext build() {
-    switch (_gridCell.field.fieldType) {
-      case FieldType.Checkbox:
-      case FieldType.DateTime:
-      case FieldType.Number:
-        return GridDefaultCellContext(
-          gridCell: _gridCell,
-          cellCache: _cellCache,
-          cellDataLoader: DefaultCellDataLoader(gridCell: _gridCell, reloadOnCellChanged: true),
-        );
-      case FieldType.RichText:
-        return GridDefaultCellContext(
-          gridCell: _gridCell,
-          cellCache: _cellCache,
-          cellDataLoader: DefaultCellDataLoader(gridCell: _gridCell),
-        );
-      case FieldType.MultiSelect:
-      case FieldType.SingleSelect:
-        return GridSelectOptionCellContext(
-          gridCell: _gridCell,
-          cellCache: _cellCache,
-          cellDataLoader: SelectOptionCellDataLoader(gridCell: _gridCell),
-        );
-      default:
-        throw UnimplementedError;
-    }
-  }
-}
-
-// ignore: must_be_immutable
-class GridCellContext<T> extends Equatable {
-  final GridCell gridCell;
-  final GridCellCache cellCache;
-  final GridCellCacheKey _cacheKey;
-  final GridCellDataLoader<T> cellDataLoader;
-  final CellService _cellService = CellService();
-
-  late final CellListener _cellListener;
-  late final ValueNotifier<T?> _cellDataNotifier;
-  bool isListening = false;
-  VoidCallback? _onFieldChangedFn;
-  Timer? _delayOperation;
-
-  GridCellContext({
-    required this.gridCell,
-    required this.cellCache,
-    required this.cellDataLoader,
-  }) : _cacheKey = GridCellCacheKey(objectId: gridCell.rowId, fieldId: gridCell.field.id);
-
-  GridCellContext<T> clone() {
-    return GridCellContext(
-      gridCell: gridCell,
-      cellDataLoader: cellDataLoader,
-      cellCache: cellCache,
-    );
-  }
-
-  String get gridId => gridCell.gridId;
-
-  String get rowId => gridCell.rowId;
-
-  String get cellId => gridCell.rowId + gridCell.field.id;
-
-  String get fieldId => gridCell.field.id;
-
-  Field get field => gridCell.field;
-
-  FieldType get fieldType => gridCell.field.fieldType;
-
-  GridCellCacheKey get cacheKey => _cacheKey;
-
-  VoidCallback? startListening({required void Function(T) onCellChanged}) {
-    if (isListening) {
-      Log.error("Already started. It seems like you should call clone first");
-      return null;
-    }
-
-    isListening = true;
-    _cellDataNotifier = ValueNotifier(cellCache.get(cacheKey));
-    _cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id);
-    _cellListener.start(onCellChanged: (result) {
-      result.fold(
-        (_) => _loadData(),
-        (err) => Log.error(err),
-      );
-    });
-
-    if (cellDataLoader.reloadOnFieldChanged) {
-      _onFieldChangedFn = () {
-        _loadData();
-      };
-      cellCache.addListener(cacheKey, _onFieldChangedFn!);
-    }
-
-    onCellChangedFn() {
-      final value = _cellDataNotifier.value;
-      if (value is T) {
-        onCellChanged(value);
-      }
-
-      if (cellDataLoader.reloadOnCellChanged) {
-        _loadData();
-      }
-    }
-
-    _cellDataNotifier.addListener(onCellChangedFn);
-    return onCellChangedFn;
-  }
-
-  void removeListener(VoidCallback fn) {
-    _cellDataNotifier.removeListener(fn);
-  }
-
-  T? getCellData() {
-    final data = cellCache.get(cacheKey);
-    if (data == null) {
-      _loadData();
-    }
-    return data;
-  }
-
-  void saveCellData(String data) {
-    _cellService.updateCell(gridId: gridId, fieldId: field.id, rowId: rowId, data: data).then((result) {
-      result.fold((l) => null, (err) => Log.error(err));
-    });
-  }
-
-  void _loadData() {
-    _delayOperation?.cancel();
-    _delayOperation = Timer(const Duration(milliseconds: 10), () {
-      cellDataLoader.loadData().then((data) {
-        _cellDataNotifier.value = data;
-        cellCache.insert(GridCellCacheData(key: cacheKey, object: data));
-      });
-    });
-  }
-
-  void dispose() {
-    _delayOperation?.cancel();
-
-    if (_onFieldChangedFn != null) {
-      cellCache.removeListener(cacheKey, _onFieldChangedFn!);
-      _onFieldChangedFn = null;
-    }
-  }
-
-  @override
-  List<Object> get props => [cellCache.get(cacheKey) ?? "", cellId];
-}
-
-abstract class GridCellDataLoader<T> {
-  Future<T?> loadData();
-
-  bool get reloadOnFieldChanged => true;
-  bool get reloadOnCellChanged => false;
-}
-
-abstract class GridCellDataConfig {
-  bool get reloadOnFieldChanged => true;
-  bool get reloadOnCellChanged => false;
-}
-
-class DefaultCellDataLoader extends GridCellDataLoader<Cell> {
-  final CellService service = CellService();
-  final GridCell gridCell;
-  @override
-  final bool reloadOnCellChanged;
-
-  DefaultCellDataLoader({
-    required this.gridCell,
-    this.reloadOnCellChanged = false,
-  });
-
-  @override
-  Future<Cell?> loadData() {
-    final fut = service.getCell(
-      gridId: gridCell.gridId,
-      fieldId: gridCell.field.id,
-      rowId: gridCell.rowId,
-    );
-    return fut.then((result) {
-      return result.fold((data) => data, (err) {
-        Log.error(err);
-        return null;
-      });
-    });
-  }
-}
-
-// key: rowId
-typedef GridCellMap = LinkedHashMap<String, GridCell>;
-
-class GridCellCacheData {
-  GridCellCacheKey key;
-  dynamic object;
-  GridCellCacheData({
-    required this.key,
-    required this.object,
-  });
-}
-
-class GridCellCacheKey {
-  final String fieldId;
-  final String objectId;
-  GridCellCacheKey({
-    required this.fieldId,
-    required this.objectId,
-  });
-}
-
-abstract class GridCellFieldDelegate {
-  void onFieldChanged(void Function(String) callback);
-  void dispose();
-}
-
-class GridCellCache {
-  final String gridId;
-  final GridCellFieldDelegate fieldDelegate;
-
-  /// fieldId: {objectId: callback}
-  final Map<String, Map<String, List<VoidCallback>>> _listenerByFieldId = {};
-
-  /// fieldId: {cacheKey: cacheData}
-  final Map<String, Map<String, dynamic>> _cellDataByFieldId = {};
-  GridCellCache({
-    required this.gridId,
-    required this.fieldDelegate,
-  }) {
-    fieldDelegate.onFieldChanged((fieldId) {
-      _cellDataByFieldId.remove(fieldId);
-      final map = _listenerByFieldId[fieldId];
-      if (map != null) {
-        for (final callbacks in map.values) {
-          for (final callback in callbacks) {
-            callback();
-          }
-        }
-      }
-    });
-  }
-
-  void addListener(GridCellCacheKey cacheKey, VoidCallback callback) {
-    var map = _listenerByFieldId[cacheKey.fieldId];
-    if (map == null) {
-      _listenerByFieldId[cacheKey.fieldId] = {};
-      map = _listenerByFieldId[cacheKey.fieldId];
-      map![cacheKey.objectId] = [callback];
-    } else {
-      var objects = map[cacheKey.objectId];
-      if (objects == null) {
-        map[cacheKey.objectId] = [callback];
-      } else {
-        objects.add(callback);
-      }
-    }
-  }
-
-  void removeListener(GridCellCacheKey cacheKey, VoidCallback fn) {
-    var callbacks = _listenerByFieldId[cacheKey.fieldId]?[cacheKey.objectId];
-    final index = callbacks?.indexWhere((callback) => callback == fn);
-    if (index != null && index != -1) {
-      callbacks?.removeAt(index);
-    }
-  }
-
-  void insert<T extends GridCellCacheData>(T item) {
-    var map = _cellDataByFieldId[item.key.fieldId];
-    if (map == null) {
-      _cellDataByFieldId[item.key.fieldId] = {};
-      map = _cellDataByFieldId[item.key.fieldId];
-    }
-
-    map![item.key.objectId] = item.object;
-  }
-
-  T? get<T>(GridCellCacheKey key) {
-    final map = _cellDataByFieldId[key.fieldId];
-    if (map == null) {
-      return null;
-    } else {
-      final object = map[key.objectId];
-      if (object is T) {
-        return object;
-      } else {
-        if (object != null) {
-          Log.error("Cache data type does not match the cache data type");
-        }
-
-        return null;
-      }
-    }
-  }
-
-  Future<void> dispose() async {
-    fieldDelegate.dispose();
-  }
-}
-
-class CellService {
-  CellService();
-
-  Future<Either<void, FlowyError>> updateCell({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
-    required String data,
-  }) {
-    final payload = CellChangeset.create()
-      ..gridId = gridId
-      ..fieldId = fieldId
-      ..rowId = rowId
-      ..data = data;
-    return GridEventUpdateCell(payload).send();
-  }
-
-  Future<Either<Cell, FlowyError>> getCell({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
-  }) {
-    final payload = CellIdentifierPayload.create()
-      ..gridId = gridId
-      ..fieldId = fieldId
-      ..rowId = rowId;
-    return GridEventGetCell(payload).send();
-  }
-}
-
-@freezed
-class GridCell with _$GridCell {
-  const factory GridCell({
-    required String gridId,
-    required String rowId,
-    required Field field,
-    Cell? cell,
-  }) = _GridCell;
-
-  // ignore: unused_element
-  const GridCell._();
-
-  String cellId() {
-    return rowId + field.id + "${field.fieldType}";
-  }
-}

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

@@ -0,0 +1,73 @@
+import 'dart:async';
+import 'dart:collection';
+
+import 'package:dartz/dartz.dart';
+import 'package:equatable/equatable.dart';
+import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flutter/foundation.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
+import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
+import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
+
+part 'cell_service.freezed.dart';
+part 'data_loader.dart';
+part 'context_builder.dart';
+part 'data_cache.dart';
+part 'data_persistence.dart';
+
+// key: rowId
+
+class CellService {
+  CellService();
+
+  Future<Either<void, FlowyError>> updateCell({
+    required String gridId,
+    required String fieldId,
+    required String rowId,
+    required String data,
+  }) {
+    final payload = CellChangeset.create()
+      ..gridId = gridId
+      ..fieldId = fieldId
+      ..rowId = rowId
+      ..cellContentChangeset = data;
+    return GridEventUpdateCell(payload).send();
+  }
+
+  Future<Either<Cell, FlowyError>> getCell({
+    required String gridId,
+    required String fieldId,
+    required String rowId,
+  }) {
+    final payload = CellIdentifierPayload.create()
+      ..gridId = gridId
+      ..fieldId = fieldId
+      ..rowId = rowId;
+    return GridEventGetCell(payload).send();
+  }
+}
+
+@freezed
+class GridCell with _$GridCell {
+  const factory GridCell({
+    required String gridId,
+    required String rowId,
+    required Field field,
+    Cell? cell,
+  }) = _GridCell;
+
+  // ignore: unused_element
+  const GridCell._();
+
+  String cellId() {
+    return rowId + field.id + "${field.fieldType}";
+  }
+}

+ 182 - 0
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart

@@ -0,0 +1,182 @@
+part of 'cell_service.dart';
+
+typedef GridCellContext = _GridCellContext<Cell, String>;
+typedef GridSelectOptionCellContext = _GridCellContext<SelectOptionCellData, String>;
+typedef GridDateCellContext = _GridCellContext<DateCellData, DateCalData>;
+
+class GridCellContextBuilder {
+  final GridCellCache _cellCache;
+  final GridCell _gridCell;
+  GridCellContextBuilder({
+    required GridCellCache cellCache,
+    required GridCell gridCell,
+  })  : _cellCache = cellCache,
+        _gridCell = gridCell;
+
+  _GridCellContext build() {
+    switch (_gridCell.field.fieldType) {
+      case FieldType.Checkbox:
+        return GridCellContext(
+          gridCell: _gridCell,
+          cellCache: _cellCache,
+          cellDataLoader: CellDataLoader(gridCell: _gridCell),
+          cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
+        );
+      case FieldType.DateTime:
+        return GridDateCellContext(
+          gridCell: _gridCell,
+          cellCache: _cellCache,
+          cellDataLoader: DateCellDataLoader(gridCell: _gridCell),
+          cellDataPersistence: DateCellDataPersistence(gridCell: _gridCell),
+        );
+      case FieldType.Number:
+        return GridCellContext(
+          gridCell: _gridCell,
+          cellCache: _cellCache,
+          cellDataLoader: CellDataLoader(gridCell: _gridCell, reloadOnCellChanged: true),
+          cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
+        );
+      case FieldType.RichText:
+        return GridCellContext(
+          gridCell: _gridCell,
+          cellCache: _cellCache,
+          cellDataLoader: CellDataLoader(gridCell: _gridCell),
+          cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
+        );
+      case FieldType.MultiSelect:
+      case FieldType.SingleSelect:
+        return GridSelectOptionCellContext(
+          gridCell: _gridCell,
+          cellCache: _cellCache,
+          cellDataLoader: SelectOptionCellDataLoader(gridCell: _gridCell),
+          cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
+        );
+      default:
+        throw UnimplementedError;
+    }
+  }
+}
+
+// ignore: must_be_immutable
+class _GridCellContext<T, D> extends Equatable {
+  final GridCell gridCell;
+  final GridCellCache cellCache;
+  final GridCellCacheKey _cacheKey;
+  final _GridCellDataLoader<T> cellDataLoader;
+  final _GridCellDataPersistence<D> cellDataPersistence;
+  final FieldService _fieldService;
+
+  late final CellListener _cellListener;
+  late final ValueNotifier<T?> _cellDataNotifier;
+  bool isListening = false;
+  VoidCallback? _onFieldChangedFn;
+  Timer? _delayOperation;
+
+  _GridCellContext({
+    required this.gridCell,
+    required this.cellCache,
+    required this.cellDataLoader,
+    required this.cellDataPersistence,
+  })  : _fieldService = FieldService(gridId: gridCell.gridId, fieldId: gridCell.field.id),
+        _cacheKey = GridCellCacheKey(objectId: gridCell.rowId, fieldId: gridCell.field.id);
+
+  _GridCellContext<T, D> clone() {
+    return _GridCellContext(
+        gridCell: gridCell,
+        cellDataLoader: cellDataLoader,
+        cellCache: cellCache,
+        cellDataPersistence: cellDataPersistence);
+  }
+
+  String get gridId => gridCell.gridId;
+
+  String get rowId => gridCell.rowId;
+
+  String get cellId => gridCell.rowId + gridCell.field.id;
+
+  String get fieldId => gridCell.field.id;
+
+  Field get field => gridCell.field;
+
+  FieldType get fieldType => gridCell.field.fieldType;
+
+  VoidCallback? startListening({required void Function(T) onCellChanged}) {
+    if (isListening) {
+      Log.error("Already started. It seems like you should call clone first");
+      return null;
+    }
+
+    isListening = true;
+    _cellDataNotifier = ValueNotifier(cellCache.get(_cacheKey));
+    _cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id);
+    _cellListener.start(onCellChanged: (result) {
+      result.fold(
+        (_) => _loadData(),
+        (err) => Log.error(err),
+      );
+    });
+
+    if (cellDataLoader.config.reloadOnFieldChanged) {
+      _onFieldChangedFn = () {
+        _loadData();
+      };
+      cellCache.addFieldListener(_cacheKey, _onFieldChangedFn!);
+    }
+
+    onCellChangedFn() {
+      final value = _cellDataNotifier.value;
+      if (value is T) {
+        onCellChanged(value);
+      }
+
+      if (cellDataLoader.config.reloadOnCellChanged) {
+        _loadData();
+      }
+    }
+
+    _cellDataNotifier.addListener(onCellChangedFn);
+    return onCellChangedFn;
+  }
+
+  void removeListener(VoidCallback fn) {
+    _cellDataNotifier.removeListener(fn);
+  }
+
+  T? getCellData() {
+    final data = cellCache.get(_cacheKey);
+    if (data == null) {
+      _loadData();
+    }
+    return data;
+  }
+
+  Future<Either<List<int>, FlowyError>> getTypeOptionData() {
+    return _fieldService.getTypeOptionData(fieldType: fieldType);
+  }
+
+  Future<Option<FlowyError>> saveCellData(D data) {
+    return cellDataPersistence.save(data);
+  }
+
+  void _loadData() {
+    _delayOperation?.cancel();
+    _delayOperation = Timer(const Duration(milliseconds: 10), () {
+      cellDataLoader.loadData().then((data) {
+        _cellDataNotifier.value = data;
+        cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
+      });
+    });
+  }
+
+  void dispose() {
+    _delayOperation?.cancel();
+
+    if (_onFieldChangedFn != null) {
+      cellCache.removeFieldListener(_cacheKey, _onFieldChangedFn!);
+      _onFieldChangedFn = null;
+    }
+  }
+
+  @override
+  List<Object> get props => [cellCache.get(_cacheKey) ?? "", cellId];
+}

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

@@ -0,0 +1,109 @@
+part of 'cell_service.dart';
+
+typedef GridCellMap = LinkedHashMap<String, GridCell>;
+
+class GridCellCacheData {
+  GridCellCacheKey key;
+  dynamic object;
+  GridCellCacheData({
+    required this.key,
+    required this.object,
+  });
+}
+
+class GridCellCacheKey {
+  final String fieldId;
+  final String objectId;
+  GridCellCacheKey({
+    required this.fieldId,
+    required this.objectId,
+  });
+}
+
+abstract class GridCellFieldDelegate {
+  void onFieldChanged(void Function(String) callback);
+  void dispose();
+}
+
+class GridCellCache {
+  final String gridId;
+  final GridCellFieldDelegate fieldDelegate;
+
+  /// fieldId: {objectId: callback}
+  final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId = {};
+
+  /// fieldId: {cacheKey: cacheData}
+  final Map<String, Map<String, dynamic>> _cellDataByFieldId = {};
+  GridCellCache({
+    required this.gridId,
+    required this.fieldDelegate,
+  }) {
+    fieldDelegate.onFieldChanged((fieldId) {
+      _cellDataByFieldId.remove(fieldId);
+      final map = _fieldListenerByFieldId[fieldId];
+      if (map != null) {
+        for (final callbacks in map.values) {
+          for (final callback in callbacks) {
+            callback();
+          }
+        }
+      }
+    });
+  }
+
+  void addFieldListener(GridCellCacheKey cacheKey, VoidCallback onFieldChanged) {
+    var map = _fieldListenerByFieldId[cacheKey.fieldId];
+    if (map == null) {
+      _fieldListenerByFieldId[cacheKey.fieldId] = {};
+      map = _fieldListenerByFieldId[cacheKey.fieldId];
+      map![cacheKey.objectId] = [onFieldChanged];
+    } else {
+      var objects = map[cacheKey.objectId];
+      if (objects == null) {
+        map[cacheKey.objectId] = [onFieldChanged];
+      } else {
+        objects.add(onFieldChanged);
+      }
+    }
+  }
+
+  void removeFieldListener(GridCellCacheKey cacheKey, VoidCallback fn) {
+    var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.objectId];
+    final index = callbacks?.indexWhere((callback) => callback == fn);
+    if (index != null && index != -1) {
+      callbacks?.removeAt(index);
+    }
+  }
+
+  void insert<T extends GridCellCacheData>(T item) {
+    var map = _cellDataByFieldId[item.key.fieldId];
+    if (map == null) {
+      _cellDataByFieldId[item.key.fieldId] = {};
+      map = _cellDataByFieldId[item.key.fieldId];
+    }
+
+    map![item.key.objectId] = item.object;
+  }
+
+  T? get<T>(GridCellCacheKey key) {
+    final map = _cellDataByFieldId[key.fieldId];
+    if (map == null) {
+      return null;
+    } else {
+      final object = map[key.objectId];
+      if (object is T) {
+        return object;
+      } else {
+        if (object != null) {
+          Log.error("Cache data type does not match the cache data type");
+        }
+
+        return null;
+      }
+    }
+  }
+
+  Future<void> dispose() async {
+    fieldDelegate.dispose();
+  }
+}

+ 112 - 0
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart

@@ -0,0 +1,112 @@
+part of 'cell_service.dart';
+
+abstract class GridCellDataConfig {
+  // The cell data will reload if it receives the field's change notification.
+  bool get reloadOnFieldChanged;
+
+  // The cell data will reload if it receives the cell's change notification.
+  // For example, the number cell should be reloaded after user input the number.
+  // user input: 12
+  // cell display: $12
+  bool get reloadOnCellChanged;
+}
+
+class DefaultCellDataConfig implements GridCellDataConfig {
+  @override
+  final bool reloadOnCellChanged;
+
+  @override
+  final bool reloadOnFieldChanged;
+
+  DefaultCellDataConfig({
+    this.reloadOnCellChanged = false,
+    this.reloadOnFieldChanged = false,
+  });
+}
+
+abstract class _GridCellDataLoader<T> {
+  Future<T?> loadData();
+
+  GridCellDataConfig get config;
+}
+
+class CellDataLoader extends _GridCellDataLoader<Cell> {
+  final CellService service = CellService();
+  final GridCell gridCell;
+  final GridCellDataConfig _config;
+
+  CellDataLoader({
+    required this.gridCell,
+    bool reloadOnCellChanged = false,
+  }) : _config = DefaultCellDataConfig(reloadOnCellChanged: reloadOnCellChanged);
+
+  @override
+  Future<Cell?> loadData() {
+    final fut = service.getCell(
+      gridId: gridCell.gridId,
+      fieldId: gridCell.field.id,
+      rowId: gridCell.rowId,
+    );
+    return fut.then((result) {
+      return result.fold((data) => data, (err) {
+        Log.error(err);
+        return null;
+      });
+    });
+  }
+
+  @override
+  GridCellDataConfig get config => _config;
+}
+
+class DateCellDataLoader extends _GridCellDataLoader<DateCellData> {
+  final GridCell gridCell;
+  final GridCellDataConfig _config;
+  DateCellDataLoader({
+    required this.gridCell,
+  }) : _config = DefaultCellDataConfig(reloadOnFieldChanged: true);
+
+  @override
+  GridCellDataConfig get config => _config;
+
+  @override
+  Future<DateCellData?> loadData() {
+    final payload = CellIdentifierPayload.create()
+      ..gridId = gridCell.gridId
+      ..fieldId = gridCell.field.id
+      ..rowId = gridCell.rowId;
+
+    return GridEventGetDateCellData(payload).send().then((result) {
+      return result.fold(
+        (data) => data,
+        (err) {
+          Log.error(err);
+          return null;
+        },
+      );
+    });
+  }
+}
+
+class SelectOptionCellDataLoader extends _GridCellDataLoader<SelectOptionCellData> {
+  final SelectOptionService service;
+  final GridCell gridCell;
+  SelectOptionCellDataLoader({
+    required this.gridCell,
+  }) : service = SelectOptionService(gridCell: gridCell);
+  @override
+  Future<SelectOptionCellData?> loadData() async {
+    return service.getOpitonContext().then((result) {
+      return result.fold(
+        (data) => data,
+        (err) {
+          Log.error(err);
+          return null;
+        },
+      );
+    });
+  }
+
+  @override
+  GridCellDataConfig get config => DefaultCellDataConfig();
+}

+ 69 - 0
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart

@@ -0,0 +1,69 @@
+part of 'cell_service.dart';
+
+abstract class _GridCellDataPersistence<D> {
+  Future<Option<FlowyError>> save(D data);
+}
+
+class CellDataPersistence implements _GridCellDataPersistence<String> {
+  final GridCell gridCell;
+
+  CellDataPersistence({
+    required this.gridCell,
+  });
+  final CellService _cellService = CellService();
+
+  @override
+  Future<Option<FlowyError>> save(String data) async {
+    final fut = _cellService.updateCell(
+      gridId: gridCell.gridId,
+      fieldId: gridCell.field.id,
+      rowId: gridCell.rowId,
+      data: data,
+    );
+
+    return fut.then((result) {
+      return result.fold(
+        (l) => none(),
+        (err) => Some(err),
+      );
+    });
+  }
+}
+
+@freezed
+class DateCalData with _$DateCalData {
+  const factory DateCalData({required DateTime date, String? time}) = _DateCellPersistenceData;
+}
+
+class DateCellDataPersistence implements _GridCellDataPersistence<DateCalData> {
+  final GridCell gridCell;
+  DateCellDataPersistence({
+    required this.gridCell,
+  });
+
+  @override
+  Future<Option<FlowyError>> save(DateCalData data) {
+    var payload = DateChangesetPayload.create()..cellIdentifier = _cellIdentifier(gridCell);
+
+    final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString();
+    payload.date = date;
+
+    if (data.time != null) {
+      payload.time = data.time!;
+    }
+
+    return GridEventUpdateDateCell(payload).send().then((result) {
+      return result.fold(
+        (l) => none(),
+        (err) => Some(err),
+      );
+    });
+  }
+}
+
+CellIdentifierPayload _cellIdentifier(GridCell gridCell) {
+  return CellIdentifierPayload.create()
+    ..gridId = gridCell.gridId
+    ..fieldId = gridCell.field.id
+    ..rowId = gridCell.rowId;
+}

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

@@ -2,12 +2,12 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
-import 'cell_service.dart';
+import 'cell_service/cell_service.dart';
 
 part 'checkbox_cell_bloc.freezed.dart';
 
 class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
-  final GridDefaultCellContext cellContext;
+  final GridCellContext cellContext;
   void Function()? _onCellChangedFn;
 
   CheckboxCellBloc({

+ 221 - 0
frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart

@@ -0,0 +1,221 @@
+import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:table_calendar/table_calendar.dart';
+import 'dart:async';
+import 'cell_service/cell_service.dart';
+import 'package:dartz/dartz.dart';
+import 'package:protobuf/protobuf.dart';
+import 'package:fixnum/fixnum.dart' as $fixnum;
+part 'date_cal_bloc.freezed.dart';
+
+class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
+  final GridDateCellContext cellContext;
+  void Function()? _onCellChangedFn;
+
+  DateCalBloc({
+    required DateTypeOption dateTypeOption,
+    required DateCellData? cellData,
+    required this.cellContext,
+  }) : super(DateCalState.initial(dateTypeOption, cellData)) {
+    on<DateCalEvent>(
+      (event, emit) async {
+        await event.when(
+          initial: () async => _startListening(),
+          selectDay: (date) async {
+            await _updateDateData(emit, date: date, time: state.time);
+          },
+          setCalFormat: (format) {
+            emit(state.copyWith(format: format));
+          },
+          setFocusedDay: (focusedDay) {
+            emit(state.copyWith(focusedDay: focusedDay));
+          },
+          didReceiveCellUpdate: (DateCellData cellData) {
+            final dateData = dateDataFromCellData(cellData);
+            final time = dateData.foldRight("", (dateData, previous) => dateData.time);
+            emit(state.copyWith(dateData: dateData, time: time));
+          },
+          setIncludeTime: (includeTime) async {
+            await _updateTypeOption(emit, includeTime: includeTime);
+          },
+          setDateFormat: (dateFormat) async {
+            await _updateTypeOption(emit, dateFormat: dateFormat);
+          },
+          setTimeFormat: (timeFormat) async {
+            await _updateTypeOption(emit, timeFormat: timeFormat);
+          },
+          setTime: (time) async {
+            await _updateDateData(emit, time: time);
+          },
+        );
+      },
+    );
+  }
+
+  Future<void> _updateDateData(Emitter<DateCalState> emit, {DateTime? date, String? time}) {
+    final DateCalData newDateData = state.dateData.fold(
+      () => DateCalData(date: date ?? DateTime.now(), time: time),
+      (dateData) {
+        var newDateData = dateData;
+        if (date != null && !isSameDay(newDateData.date, date)) {
+          newDateData = newDateData.copyWith(date: date);
+        }
+
+        if (newDateData.time != time) {
+          newDateData = newDateData.copyWith(time: time);
+        }
+        return newDateData;
+      },
+    );
+
+    return _saveDateData(emit, newDateData);
+  }
+
+  Future<void> _saveDateData(Emitter<DateCalState> emit, DateCalData newDateData) async {
+    if (state.dateData == Some(newDateData)) {
+      return;
+    }
+
+    final result = await cellContext.saveCellData(newDateData);
+    result.fold(
+      () => emit(state.copyWith(
+        dateData: Some(newDateData),
+        timeFormatError: none(),
+      )),
+      (err) {
+        switch (ErrorCode.valueOf(err.code)!) {
+          case ErrorCode.InvalidDateTimeFormat:
+            emit(state.copyWith(
+              dateData: Some(newDateData),
+              timeFormatError: Some(err.toString()),
+            ));
+            break;
+          default:
+            Log.error(err);
+        }
+      },
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    if (_onCellChangedFn != null) {
+      cellContext.removeListener(_onCellChangedFn!);
+      _onCellChangedFn = null;
+    }
+    cellContext.dispose();
+    return super.close();
+  }
+
+  void _startListening() {
+    _onCellChangedFn = cellContext.startListening(
+      onCellChanged: ((cell) {
+        if (!isClosed) {
+          add(DateCalEvent.didReceiveCellUpdate(cell));
+        }
+      }),
+    );
+  }
+
+  Future<void>? _updateTypeOption(
+    Emitter<DateCalState> emit, {
+    DateFormat? dateFormat,
+    TimeFormat? timeFormat,
+    bool? includeTime,
+  }) async {
+    state.dateTypeOption.freeze();
+    final newDateTypeOption = state.dateTypeOption.rebuild((typeOption) {
+      if (dateFormat != null) {
+        typeOption.dateFormat = dateFormat;
+      }
+
+      if (timeFormat != null) {
+        typeOption.timeFormat = timeFormat;
+      }
+
+      if (includeTime != null) {
+        typeOption.includeTime = includeTime;
+      }
+    });
+
+    final result = await FieldService.updateFieldTypeOption(
+      gridId: cellContext.gridId,
+      fieldId: cellContext.field.id,
+      typeOptionData: newDateTypeOption.writeToBuffer(),
+    );
+
+    result.fold(
+      (l) => emit(state.copyWith(dateTypeOption: newDateTypeOption)),
+      (err) => Log.error(err),
+    );
+  }
+}
+
+@freezed
+class DateCalEvent with _$DateCalEvent {
+  const factory DateCalEvent.initial() = _Initial;
+  const factory DateCalEvent.selectDay(DateTime day) = _SelectDay;
+  const factory DateCalEvent.setCalFormat(CalendarFormat format) = _CalendarFormat;
+  const factory DateCalEvent.setFocusedDay(DateTime day) = _FocusedDay;
+  const factory DateCalEvent.setTimeFormat(TimeFormat timeFormat) = _TimeFormat;
+  const factory DateCalEvent.setDateFormat(DateFormat dateFormat) = _DateFormat;
+  const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime;
+  const factory DateCalEvent.setTime(String time) = _Time;
+  const factory DateCalEvent.didReceiveCellUpdate(DateCellData data) = _DidReceiveCellUpdate;
+}
+
+@freezed
+class DateCalState with _$DateCalState {
+  const factory DateCalState({
+    required DateTypeOption dateTypeOption,
+    required CalendarFormat format,
+    required DateTime focusedDay,
+    required Option<String> timeFormatError,
+    required Option<DateCalData> dateData,
+    required String? time,
+  }) = _DateCalState;
+
+  factory DateCalState.initial(
+    DateTypeOption dateTypeOption,
+    DateCellData? cellData,
+  ) {
+    Option<DateCalData> dateData = dateDataFromCellData(cellData);
+    final time = dateData.foldRight("", (dateData, previous) => dateData.time);
+    return DateCalState(
+      dateTypeOption: dateTypeOption,
+      format: CalendarFormat.month,
+      focusedDay: DateTime.now(),
+      time: time,
+      dateData: dateData,
+      timeFormatError: none(),
+    );
+  }
+}
+
+Option<DateCalData> dateDataFromCellData(DateCellData? cellData) {
+  String? time = timeFromCellData(cellData);
+  Option<DateCalData> dateData = none();
+  if (cellData != null) {
+    final timestamp = cellData.timestamp * 1000;
+    final date = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt());
+    dateData = Some(DateCalData(date: date, time: time));
+  }
+  return dateData;
+}
+
+$fixnum.Int64 timestampFromDateTime(DateTime dateTime) {
+  final timestamp = (dateTime.millisecondsSinceEpoch ~/ 1000);
+  return $fixnum.Int64(timestamp);
+}
+
+String? timeFromCellData(DateCellData? cellData) {
+  String? time;
+  if (cellData?.hasTime() ?? false) {
+    time = cellData?.time;
+  }
+  return time;
+}

+ 26 - 34
frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart

@@ -1,33 +1,23 @@
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell, Field;
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
+import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
-import 'cell_service.dart';
-
+import 'cell_service/cell_service.dart';
+import 'package:dartz/dartz.dart';
 part 'date_cell_bloc.freezed.dart';
 
 class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
-  final GridDefaultCellContext cellContext;
+  final GridDateCellContext cellContext;
   void Function()? _onCellChangedFn;
 
   DateCellBloc({required this.cellContext}) : super(DateCellState.initial(cellContext)) {
     on<DateCellEvent>(
       (event, emit) async {
-        event.map(
-          initial: (_InitialCell value) {
-            _startListening();
-          },
-          selectDay: (_SelectDay value) {
-            _updateCellData(value.day);
-          },
-          didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
-            emit(state.copyWith(
-              content: value.cell.content,
-            ));
-          },
-          didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
-            emit(state.copyWith(field: value.field));
-          },
+        event.when(
+          initial: () => _startListening(),
+          didReceiveCellUpdate: (DateCellData value) => emit(state.copyWith(data: Some(value))),
+          didReceiveFieldUpdate: (Field value) => emit(state.copyWith(field: value)),
         );
       },
     );
@@ -45,38 +35,40 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
 
   void _startListening() {
     _onCellChangedFn = cellContext.startListening(
-      onCellChanged: ((cell) {
+      onCellChanged: ((data) {
         if (!isClosed) {
-          add(DateCellEvent.didReceiveCellUpdate(cell));
+          add(DateCellEvent.didReceiveCellUpdate(data));
         }
       }),
     );
   }
-
-  void _updateCellData(DateTime day) {
-    final data = day.millisecondsSinceEpoch ~/ 1000;
-    cellContext.saveCellData(data.toString());
-  }
 }
 
 @freezed
 class DateCellEvent with _$DateCellEvent {
   const factory DateCellEvent.initial() = _InitialCell;
-  const factory DateCellEvent.selectDay(DateTime day) = _SelectDay;
-  const factory DateCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
+  const factory DateCellEvent.didReceiveCellUpdate(DateCellData data) = _DidReceiveCellUpdate;
   const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
 }
 
 @freezed
 class DateCellState with _$DateCellState {
   const factory DateCellState({
-    required String content,
+    required Option<DateCellData> data,
     required Field field,
-    DateTime? selectedDay,
   }) = _DateCellState;
 
-  factory DateCellState.initial(GridCellContext context) => DateCellState(
-        field: context.field,
-        content: context.getCellData()?.content ?? "",
-      );
+  factory DateCellState.initial(GridDateCellContext context) {
+    final cellData = context.getCellData();
+    Option<DateCellData> data = none();
+
+    if (cellData != null) {
+      data = Some(cellData);
+    }
+
+    return DateCellState(
+      field: context.field,
+      data: data,
+    );
+  }
 }

+ 3 - 3
frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart

@@ -2,12 +2,12 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
-import 'cell_service.dart';
+import 'cell_service/cell_service.dart';
 
 part 'number_cell_bloc.freezed.dart';
 
 class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
-  final GridDefaultCellContext cellContext;
+  final GridCellContext cellContext;
   void Function()? _onCellChangedFn;
 
   NumberCellBloc({
@@ -68,7 +68,7 @@ class NumberCellState with _$NumberCellState {
     required String content,
   }) = _NumberCellState;
 
-  factory NumberCellState.initial(GridDefaultCellContext context) {
+  factory NumberCellState.initial(GridCellContext context) {
     final cell = context.getCellData();
     return NumberCellState(content: cell?.content ?? "");
   }

+ 15 - 44
frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart

@@ -1,33 +1,10 @@
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
-import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
-
 import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
-
-import 'cell_service.dart';
-
-class SelectOptionCellDataLoader extends GridCellDataLoader<SelectOptionContext> {
-  final SelectOptionService service;
-  final GridCell gridCell;
-  SelectOptionCellDataLoader({
-    required this.gridCell,
-  }) : service = SelectOptionService(gridCell: gridCell);
-  @override
-  Future<SelectOptionContext?> loadData() async {
-    return service.getOpitonContext().then((result) {
-      return result.fold(
-        (data) => data,
-        (err) {
-          Log.error(err);
-          return null;
-        },
-      );
-    });
-  }
-}
+import 'cell_service/cell_service.dart';
 
 class SelectOptionService {
   final GridCell gridCell;
@@ -60,55 +37,49 @@ class SelectOptionService {
   Future<Either<Unit, FlowyError>> update({
     required SelectOption option,
   }) {
-    final cellIdentifier = CellIdentifierPayload.create()
-      ..gridId = gridId
-      ..fieldId = fieldId
-      ..rowId = rowId;
     final payload = SelectOptionChangesetPayload.create()
       ..updateOption = option
-      ..cellIdentifier = cellIdentifier;
+      ..cellIdentifier = _cellIdentifier();
     return GridEventUpdateSelectOption(payload).send();
   }
 
   Future<Either<Unit, FlowyError>> delete({
     required SelectOption option,
   }) {
-    final cellIdentifier = CellIdentifierPayload.create()
-      ..gridId = gridId
-      ..fieldId = fieldId
-      ..rowId = rowId;
-
     final payload = SelectOptionChangesetPayload.create()
       ..deleteOption = option
-      ..cellIdentifier = cellIdentifier;
+      ..cellIdentifier = _cellIdentifier();
 
     return GridEventUpdateSelectOption(payload).send();
   }
 
-  Future<Either<SelectOptionContext, FlowyError>> getOpitonContext() {
+  Future<Either<SelectOptionCellData, FlowyError>> getOpitonContext() {
     final payload = CellIdentifierPayload.create()
       ..gridId = gridId
       ..fieldId = fieldId
       ..rowId = rowId;
 
-    return GridEventGetSelectOptionContext(payload).send();
+    return GridEventGetSelectOptionCellData(payload).send();
   }
 
   Future<Either<void, FlowyError>> select({required String optionId}) {
     final payload = SelectOptionCellChangesetPayload.create()
-      ..gridId = gridId
-      ..fieldId = fieldId
-      ..rowId = rowId
+      ..cellIdentifier = _cellIdentifier()
       ..insertOptionId = optionId;
-    return GridEventUpdateCellSelectOption(payload).send();
+    return GridEventUpdateSelectOptionCell(payload).send();
   }
 
   Future<Either<void, FlowyError>> unSelect({required String optionId}) {
     final payload = SelectOptionCellChangesetPayload.create()
+      ..cellIdentifier = _cellIdentifier()
+      ..deleteOptionId = optionId;
+    return GridEventUpdateSelectOptionCell(payload).send();
+  }
+
+  CellIdentifierPayload _cellIdentifier() {
+    return CellIdentifierPayload.create()
       ..gridId = gridId
       ..fieldId = fieldId
-      ..rowId = rowId
-      ..deleteOptionId = optionId;
-    return GridEventUpdateCellSelectOption(payload).send();
+      ..rowId = rowId;
   }
 }

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

@@ -2,7 +2,7 @@ import 'dart:async';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 
 part 'selection_cell_bloc.freezed.dart';
 

+ 1 - 4
frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart

@@ -1,13 +1,10 @@
 import 'dart:async';
-
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
-
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'select_option_service.dart';
 
 part 'selection_editor_bloc.freezed.dart';

+ 3 - 3
frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart

@@ -2,12 +2,12 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
-import 'cell_service.dart';
+import 'cell_service/cell_service.dart';
 
 part 'text_cell_bloc.freezed.dart';
 
 class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
-  final GridDefaultCellContext cellContext;
+  final GridCellContext cellContext;
   void Function()? _onCellChangedFn;
   TextCellBloc({
     required this.cellContext,
@@ -70,7 +70,7 @@ class TextCellState with _$TextCellState {
     required String content,
   }) = _TextCellState;
 
-  factory TextCellState.initial(GridDefaultCellContext context) => TextCellState(
+  factory TextCellState.initial(GridCellContext context) => TextCellState(
         content: context.getCellData()?.content ?? "",
       );
 }

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

@@ -8,36 +8,36 @@ import 'field_service.dart';
 part 'field_action_sheet_bloc.freezed.dart';
 
 class FieldActionSheetBloc extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
-  final FieldService service;
+  final FieldService fieldService;
 
-  FieldActionSheetBloc({required Field field, required this.service})
+  FieldActionSheetBloc({required Field field, required this.fieldService})
       : super(FieldActionSheetState.initial(EditFieldContext.create()..gridField = field)) {
     on<FieldActionSheetEvent>(
       (event, emit) async {
         await event.map(
           updateFieldName: (_UpdateFieldName value) async {
-            final result = await service.updateField(fieldId: field.id, name: value.name);
+            final result = await fieldService.updateField(name: value.name);
             result.fold(
               (l) => null,
               (err) => Log.error(err),
             );
           },
           hideField: (_HideField value) async {
-            final result = await service.updateField(fieldId: field.id, visibility: false);
+            final result = await fieldService.updateField(visibility: false);
             result.fold(
               (l) => null,
               (err) => Log.error(err),
             );
           },
           deleteField: (_DeleteField value) async {
-            final result = await service.deleteField(fieldId: field.id);
+            final result = await fieldService.deleteField();
             result.fold(
               (l) => null,
               (err) => Log.error(err),
             );
           },
           duplicateField: (_DuplicateField value) async {
-            final result = await service.duplicateField(fieldId: field.id);
+            final result = await fieldService.duplicateField();
             result.fold(
               (l) => null,
               (err) => Log.error(err),

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

@@ -15,7 +15,7 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
   FieldCellBloc({
     required GridFieldCellContext cellContext,
   })  : _fieldListener = SingleFieldListener(fieldId: cellContext.field.id),
-        _fieldService = FieldService(gridId: cellContext.gridId),
+        _fieldService = FieldService(gridId: cellContext.gridId, fieldId: cellContext.field.id),
         super(FieldCellState.initial(cellContext)) {
     on<FieldCellEvent>(
       (event, emit) async {
@@ -30,7 +30,7 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
             final defaultWidth = state.field.width.toDouble();
             final width = defaultWidth + value.offset;
             if (width > defaultWidth && width < 300) {
-              _fieldService.updateField(fieldId: state.field.id, width: width);
+              _fieldService.updateField(width: width);
             }
           },
         );

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

@@ -11,14 +11,14 @@ import 'package:protobuf/protobuf.dart';
 part 'field_editor_bloc.freezed.dart';
 
 class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
-  final FieldService service;
+  final String gridId;
   final EditFieldContextLoader _loader;
 
   FieldEditorBloc({
-    required this.service,
+    required this.gridId,
     required EditFieldContextLoader fieldLoader,
   })  : _loader = fieldLoader,
-        super(FieldEditorState.initial(service.gridId)) {
+        super(FieldEditorState.initial(gridId)) {
     on<FieldEditorEvent>(
       (event, emit) async {
         await event.map(
@@ -73,7 +73,9 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
             newContext.typeOptionData = typeOptionData;
           }
         });
-        service.insertField(
+
+        FieldService.insertField(
+          gridId: gridId,
           field: newContext.gridField,
           typeOptionData: newContext.typeOptionData,
         );
@@ -87,7 +89,8 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
     await state.editFieldContext.fold(
       () async => null,
       (context) async {
-        final result = await service.insertField(
+        final result = await FieldService.insertField(
+          gridId: gridId,
           field: context.gridField,
           typeOptionData: context.typeOptionData,
         );

+ 41 - 16
frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart

@@ -8,10 +8,11 @@ part 'field_service.freezed.dart';
 
 class FieldService {
   final String gridId;
+  final String fieldId;
 
-  FieldService({required this.gridId});
+  FieldService({required this.gridId, required this.fieldId});
 
-  Future<Either<EditFieldContext, FlowyError>> switchToField(String fieldId, FieldType fieldType) {
+  Future<Either<EditFieldContext, FlowyError>> switchToField(FieldType fieldType) {
     final payload = EditFieldPayload.create()
       ..gridId = gridId
       ..fieldId = fieldId
@@ -20,8 +21,8 @@ class FieldService {
     return GridEventSwitchToField(payload).send();
   }
 
-  Future<Either<EditFieldContext, FlowyError>> getEditFieldContext(String fieldId, FieldType fieldType) {
-    final payload = GetEditFieldContextPayload.create()
+  Future<Either<EditFieldContext, FlowyError>> getEditFieldContext(FieldType fieldType) {
+    final payload = EditFieldPayload.create()
       ..gridId = gridId
       ..fieldId = fieldId
       ..fieldType = fieldType;
@@ -29,7 +30,7 @@ class FieldService {
     return GridEventGetEditFieldContext(payload).send();
   }
 
-  Future<Either<Unit, FlowyError>> moveField(String fieldId, int fromIndex, int toIndex) {
+  Future<Either<Unit, FlowyError>> moveField(int fromIndex, int toIndex) {
     final payload = MoveItemPayload.create()
       ..gridId = gridId
       ..itemId = fieldId
@@ -41,7 +42,6 @@ class FieldService {
   }
 
   Future<Either<Unit, FlowyError>> updateField({
-    required String fieldId,
     String? name,
     FieldType? fieldType,
     bool? frozen,
@@ -81,7 +81,8 @@ class FieldService {
   }
 
   // Create the field if it does not exist. Otherwise, update the field.
-  Future<Either<Unit, FlowyError>> insertField({
+  static Future<Either<Unit, FlowyError>> insertField({
+    required String gridId,
     required Field field,
     List<int>? typeOptionData,
     String? startFieldId,
@@ -98,9 +99,20 @@ class FieldService {
     return GridEventInsertField(payload).send();
   }
 
-  Future<Either<Unit, FlowyError>> deleteField({
+  static Future<Either<Unit, FlowyError>> updateFieldTypeOption({
+    required String gridId,
     required String fieldId,
+    required List<int> typeOptionData,
   }) {
+    var payload = UpdateFieldTypeOptionPayload.create()
+      ..gridId = gridId
+      ..fieldId = fieldId
+      ..typeOptionData = typeOptionData;
+
+    return GridEventUpdateFieldTypeOption(payload).send();
+  }
+
+  Future<Either<Unit, FlowyError>> deleteField() {
     final payload = FieldIdentifierPayload.create()
       ..gridId = gridId
       ..fieldId = fieldId;
@@ -108,15 +120,28 @@ class FieldService {
     return GridEventDeleteField(payload).send();
   }
 
-  Future<Either<Unit, FlowyError>> duplicateField({
-    required String fieldId,
-  }) {
+  Future<Either<Unit, FlowyError>> duplicateField() {
     final payload = FieldIdentifierPayload.create()
       ..gridId = gridId
       ..fieldId = fieldId;
 
     return GridEventDuplicateField(payload).send();
   }
+
+  Future<Either<List<int>, FlowyError>> getTypeOptionData({
+    required FieldType fieldType,
+  }) {
+    final payload = EditFieldPayload.create()
+      ..gridId = gridId
+      ..fieldId = fieldId
+      ..fieldType = fieldType;
+    return GridEventGetFieldTypeOption(payload).send().then((result) {
+      return result.fold(
+        (data) => left(data.typeOptionData),
+        (err) => right(err),
+      );
+    });
+  }
 }
 
 @freezed
@@ -141,7 +166,7 @@ class NewFieldContextLoader extends EditFieldContextLoader {
 
   @override
   Future<Either<EditFieldContext, FlowyError>> load() {
-    final payload = GetEditFieldContextPayload.create()
+    final payload = EditFieldPayload.create()
       ..gridId = gridId
       ..fieldType = FieldType.RichText;
 
@@ -150,7 +175,7 @@ class NewFieldContextLoader extends EditFieldContextLoader {
 
   @override
   Future<Either<EditFieldContext, FlowyError>> switchToField(String fieldId, FieldType fieldType) {
-    final payload = GetEditFieldContextPayload.create()
+    final payload = EditFieldPayload.create()
       ..gridId = gridId
       ..fieldType = fieldType;
 
@@ -169,7 +194,7 @@ class FieldContextLoaderAdaptor extends EditFieldContextLoader {
 
   @override
   Future<Either<EditFieldContext, FlowyError>> load() {
-    final payload = GetEditFieldContextPayload.create()
+    final payload = EditFieldPayload.create()
       ..gridId = gridId
       ..fieldId = field.id
       ..fieldType = field.fieldType;
@@ -179,7 +204,7 @@ class FieldContextLoaderAdaptor extends EditFieldContextLoader {
 
   @override
   Future<Either<EditFieldContext, FlowyError>> switchToField(String fieldId, FieldType fieldType) async {
-    final fieldService = FieldService(gridId: gridId);
-    return fieldService.switchToField(fieldId, fieldType);
+    final fieldService = FieldService(gridId: gridId, fieldId: fieldId);
+    return fieldService.switchToField(fieldType);
   }
 }

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

@@ -5,7 +5,7 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-import 'cell/cell_service.dart';
+import 'cell/cell_service/cell_service.dart';
 import 'grid_service.dart';
 import 'row/row_service.dart';
 

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

@@ -9,14 +9,13 @@ import 'grid_service.dart';
 part 'grid_header_bloc.freezed.dart';
 
 class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
-  final FieldService _fieldService;
   final GridFieldCache fieldCache;
+  final String gridId;
 
   GridHeaderBloc({
-    required String gridId,
+    required this.gridId,
     required this.fieldCache,
-  })  : _fieldService = FieldService(gridId: gridId),
-        super(GridHeaderState.initial(fieldCache.clonedFields)) {
+  }) : super(GridHeaderState.initial(fieldCache.clonedFields)) {
     on<GridHeaderEvent>(
       (event, emit) async {
         await event.map(
@@ -39,8 +38,8 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
     fields.insert(value.toIndex, fields.removeAt(value.fromIndex));
     emit(state.copyWith(fields: fields));
 
-    final result = await _fieldService.moveField(
-      value.field.id,
+    final fieldService = FieldService(gridId: gridId, fieldId: value.field.id);
+    final result = await fieldService.moveField(
       value.fromIndex,
       value.toIndex,
     );

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

@@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter/foundation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 
-import 'cell/cell_service.dart';
+import 'cell/cell_service/cell_service.dart';
 import 'row/row_service.dart';
 
 class GridService {

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

@@ -21,7 +21,7 @@ export 'cell/number_cell_bloc.dart';
 export 'cell/selection_cell_bloc.dart';
 export 'cell/date_cell_bloc.dart';
 export 'cell/checkbox_cell_bloc.dart';
-export 'cell/cell_service.dart';
+export 'cell/cell_service/cell_service.dart';
 
 // Setting
 export 'setting/setting_bloc.dart';

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

@@ -1,5 +1,5 @@
 import 'dart:collection';
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'package:equatable/equatable.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
 import 'package:flutter_bloc/flutter_bloc.dart';

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

@@ -1,4 +1,4 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

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

@@ -1,6 +1,6 @@
 import 'dart:collection';
 
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/log.dart';

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

@@ -9,13 +9,11 @@ import 'dart:async';
 part 'property_bloc.freezed.dart';
 
 class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
-  final FieldService _service;
   final GridFieldCache _fieldCache;
   Function()? _listenFieldCallback;
 
   GridPropertyBloc({required String gridId, required GridFieldCache fieldCache})
-      : _service = FieldService(gridId: gridId),
-        _fieldCache = fieldCache,
+      : _fieldCache = fieldCache,
         super(GridPropertyState.initial(gridId, fieldCache.clonedFields)) {
     on<GridPropertyEvent>(
       (event, emit) async {
@@ -24,7 +22,8 @@ class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
             _startListening();
           },
           setFieldVisibility: (_SetFieldVisibility value) async {
-            final result = await _service.updateField(fieldId: value.fieldId, visibility: value.visibility);
+            final fieldService = FieldService(gridId: gridId, fieldId: value.fieldId);
+            final result = await fieldService.updateField(visibility: value.visibility);
             result.fold(
               (l) => null,
               (err) => Log.error(err),

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

@@ -1,4 +1,4 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
 import 'package:flutter/widgets.dart';
@@ -9,7 +9,7 @@ import 'package:provider/provider.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 import 'package:styled_widget/styled_widget.dart';
 import 'checkbox_cell.dart';
-import 'date_cell.dart';
+import 'date_cell/date_cell.dart';
 import 'number_cell.dart';
 import 'selection_cell/selection_cell.dart';
 import 'text_cell.dart';

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

@@ -1,217 +0,0 @@
-import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/workspace/application/grid/prelude.dart';
-import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
-import 'package:flowy_infra_ui/style_widget/text.dart';
-import 'package:flutter/widgets.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:table_calendar/table_calendar.dart';
-import 'cell_builder.dart';
-
-class DateCellStyle extends GridCellStyle {
-  Alignment alignment;
-
-  DateCellStyle({this.alignment = Alignment.center});
-}
-
-abstract class GridCellDelegate {
-  void onFocus(bool isFocus);
-  GridCellDelegate get delegate;
-}
-
-class DateCell extends GridCellWidget {
-  final GridCellContextBuilder cellContextBuilder;
-  late final DateCellStyle? cellStyle;
-
-  DateCell({
-    GridCellStyle? style,
-    required this.cellContextBuilder,
-    Key? key,
-  }) : super(key: key) {
-    if (style != null) {
-      cellStyle = (style as DateCellStyle);
-    } else {
-      cellStyle = null;
-    }
-  }
-
-  @override
-  State<DateCell> createState() => _DateCellState();
-}
-
-class _DateCellState extends State<DateCell> {
-  late DateCellBloc _cellBloc;
-
-  @override
-  void initState() {
-    final cellContext = widget.cellContextBuilder.build();
-    _cellBloc = getIt<DateCellBloc>(param1: cellContext)..add(const DateCellEvent.initial());
-    super.initState();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final alignment = widget.cellStyle != null ? widget.cellStyle!.alignment : Alignment.center;
-    return BlocProvider.value(
-      value: _cellBloc,
-      child: BlocBuilder<DateCellBloc, DateCellState>(
-        builder: (context, state) {
-          return SizedBox.expand(
-            child: GestureDetector(
-              behavior: HitTestBehavior.opaque,
-              onTap: () {
-                widget.onFocus.value = true;
-                _CellCalendar.show(
-                  context,
-                  onSelected: (day) {
-                    context.read<DateCellBloc>().add(DateCellEvent.selectDay(day));
-                  },
-                  onDismissed: () => widget.onFocus.value = false,
-                );
-              },
-              child: MouseRegion(
-                opaque: false,
-                cursor: SystemMouseCursors.click,
-                child: Align(alignment: alignment, child: FlowyText.medium(state.content, fontSize: 12)),
-              ),
-            ),
-          );
-        },
-      ),
-    );
-  }
-
-  @override
-  Future<void> dispose() async {
-    _cellBloc.close();
-    super.dispose();
-  }
-}
-
-final kToday = DateTime.now();
-final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day);
-final kLastDay = DateTime(kToday.year, kToday.month + 3, kToday.day);
-
-class _CellCalendar extends StatefulWidget with FlowyOverlayDelegate {
-  final void Function(DateTime) onSelected;
-  final VoidCallback onDismissed;
-  final bool includeTime;
-  const _CellCalendar({
-    required this.onSelected,
-    required this.onDismissed,
-    required this.includeTime,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  State<_CellCalendar> createState() => _CellCalendarState();
-
-  static Future<void> show(
-    BuildContext context, {
-    required void Function(DateTime) onSelected,
-    required VoidCallback onDismissed,
-  }) async {
-    _CellCalendar.remove(context);
-
-    final calendar = _CellCalendar(
-      onSelected: onSelected,
-      onDismissed: onDismissed,
-      includeTime: false,
-    );
-    // const size = Size(460, 400);
-    // final window = await getWindowInfo();
-    // FlowyOverlay.of(context).insertWithRect(
-    //   widget: OverlayContainer(
-    //     child: calendar,
-    //     constraints: BoxConstraints.loose(const Size(460, 400)),
-    //   ),
-    //   identifier: _CellCalendar.identifier(),
-    //   anchorPosition: Offset(-size.width / 2.0, -size.height / 2.0),
-    //   anchorSize: window.frame.size,
-    //   anchorDirection: AnchorDirection.center,
-    //   style: FlowyOverlayStyle(blur: false),
-    //   delegate: calendar,
-    // );
-
-    FlowyOverlay.of(context).insertWithAnchor(
-      widget: OverlayContainer(
-        child: calendar,
-        constraints: BoxConstraints.tight(const Size(320, 320)),
-      ),
-      identifier: _CellCalendar.identifier(),
-      anchorContext: context,
-      anchorDirection: AnchorDirection.leftWithCenterAligned,
-      style: FlowyOverlayStyle(blur: false),
-      delegate: calendar,
-    );
-  }
-
-  static void remove(BuildContext context) {
-    FlowyOverlay.of(context).remove(identifier());
-  }
-
-  static String identifier() {
-    return (_CellCalendar).toString();
-  }
-
-  @override
-  void didRemove() => onDismissed();
-}
-
-class _CellCalendarState extends State<_CellCalendar> {
-  CalendarFormat _calendarFormat = CalendarFormat.month;
-  DateTime _focusedDay = DateTime.now();
-  DateTime? _selectedDay;
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    return TableCalendar(
-      firstDay: kFirstDay,
-      lastDay: kLastDay,
-      focusedDay: _focusedDay,
-      rowHeight: 40,
-      calendarFormat: _calendarFormat,
-      headerStyle: const HeaderStyle(formatButtonVisible: false),
-      calendarStyle: CalendarStyle(
-        selectedDecoration: BoxDecoration(
-          color: theme.main1,
-          shape: BoxShape.circle,
-        ),
-        todayDecoration: BoxDecoration(
-          color: theme.shader4,
-          shape: BoxShape.circle,
-        ),
-        selectedTextStyle: TextStyle(
-          color: theme.surface,
-          fontSize: 14.0,
-        ),
-        todayTextStyle: TextStyle(
-          color: theme.surface,
-          fontSize: 14.0,
-        ),
-      ),
-      selectedDayPredicate: (day) {
-        return isSameDay(_selectedDay, day);
-      },
-      onDaySelected: (selectedDay, focusedDay) {
-        if (!isSameDay(_selectedDay, selectedDay)) {
-          // Call `setState()` when updating the selected day
-          setState(() {
-            _selectedDay = selectedDay;
-            _focusedDay = focusedDay;
-            widget.onSelected(selectedDay);
-          });
-        }
-      },
-      onFormatChanged: (format) {
-        setState(() {
-          _calendarFormat = format;
-        });
-      },
-      onPageChanged: (focusedDay) {
-        _focusedDay = focusedDay;
-      },
-    );
-  }
-}

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

@@ -0,0 +1,410 @@
+import 'package:app_flowy/generated/locale_keys.g.dart';
+import 'package:app_flowy/workspace/application/grid/cell/date_cal_bloc.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flowy_infra_ui/style_widget/button.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:table_calendar/table_calendar.dart';
+import 'package:app_flowy/workspace/application/grid/prelude.dart';
+
+final kToday = DateTime.now();
+final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day);
+final kLastDay = DateTime(kToday.year, kToday.month + 3, kToday.day);
+const kMargin = EdgeInsets.symmetric(horizontal: 6, vertical: 10);
+
+class CellCalendar with FlowyOverlayDelegate {
+  final VoidCallback onDismissed;
+
+  const CellCalendar({
+    required this.onDismissed,
+  });
+
+  Future<void> show(
+    BuildContext context, {
+    required GridDateCellContext cellContext,
+  }) async {
+    CellCalendar.remove(context);
+
+    final result = await cellContext.getTypeOptionData();
+    result.fold(
+      (data) {
+        final typeOptionData = DateTypeOption.fromBuffer(data);
+        // DateTime? selectedDay;
+        // final cellData = cellContext.getCellData();
+
+        // if (cellData != null) {
+        //   final timestamp = $fixnum.Int64.parseInt(cellData).toInt();
+        //   selectedDay = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
+        // }
+
+        final calendar = _CellCalendarWidget(
+          cellContext: cellContext,
+          dateTypeOption: typeOptionData,
+        );
+
+        FlowyOverlay.of(context).insertWithAnchor(
+          widget: OverlayContainer(
+            child: calendar,
+            constraints: BoxConstraints.loose(const Size(320, 500)),
+          ),
+          identifier: CellCalendar.identifier(),
+          anchorContext: context,
+          anchorDirection: AnchorDirection.leftWithCenterAligned,
+          style: FlowyOverlayStyle(blur: false),
+          delegate: this,
+        );
+      },
+      (err) => Log.error(err),
+    );
+  }
+
+  static void remove(BuildContext context) {
+    FlowyOverlay.of(context).remove(identifier());
+  }
+
+  static String identifier() {
+    return (CellCalendar).toString();
+  }
+
+  @override
+  void didRemove() => onDismissed();
+
+  @override
+  bool asBarrier() => true;
+}
+
+class _CellCalendarWidget extends StatelessWidget {
+  final GridDateCellContext cellContext;
+  final DateTypeOption dateTypeOption;
+
+  const _CellCalendarWidget({
+    required this.cellContext,
+    required this.dateTypeOption,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return BlocProvider(
+      create: (context) {
+        return DateCalBloc(
+          dateTypeOption: dateTypeOption,
+          cellData: cellContext.getCellData(),
+          cellContext: cellContext,
+        )..add(const DateCalEvent.initial());
+      },
+      child: BlocBuilder<DateCalBloc, DateCalState>(
+        buildWhen: (p, c) => false,
+        builder: (context, state) {
+          List<Widget> children = [
+            _buildCalendar(theme, context),
+            _TimeTextField(bloc: context.read<DateCalBloc>()),
+            Divider(height: 1, color: theme.shader5),
+            const _IncludeTimeButton(),
+            const _DateTypeOptionButton()
+          ];
+
+          return ListView.separated(
+            shrinkWrap: true,
+            controller: ScrollController(),
+            separatorBuilder: (context, index) {
+              return VSpace(GridSize.typeOptionSeparatorHeight);
+            },
+            itemCount: children.length,
+            itemBuilder: (BuildContext context, int index) {
+              return children[index];
+            },
+          );
+        },
+      ),
+    );
+  }
+
+  Widget _buildCalendar(AppTheme theme, BuildContext context) {
+    return BlocBuilder<DateCalBloc, DateCalState>(
+      builder: (context, state) {
+        return TableCalendar(
+          firstDay: kFirstDay,
+          lastDay: kLastDay,
+          focusedDay: state.focusedDay,
+          rowHeight: 40,
+          calendarFormat: state.format,
+          headerStyle: HeaderStyle(
+            formatButtonVisible: false,
+            titleCentered: true,
+            leftChevronMargin: EdgeInsets.zero,
+            leftChevronPadding: EdgeInsets.zero,
+            leftChevronIcon: svgWidget("home/arrow_left"),
+            rightChevronPadding: EdgeInsets.zero,
+            rightChevronMargin: EdgeInsets.zero,
+            rightChevronIcon: svgWidget("home/arrow_right"),
+          ),
+          calendarStyle: CalendarStyle(
+            selectedDecoration: BoxDecoration(
+              color: theme.main1,
+              shape: BoxShape.circle,
+            ),
+            todayDecoration: BoxDecoration(
+              color: theme.shader4,
+              shape: BoxShape.circle,
+            ),
+            selectedTextStyle: TextStyle(
+              color: theme.surface,
+              fontSize: 14.0,
+            ),
+            todayTextStyle: TextStyle(
+              color: theme.surface,
+              fontSize: 14.0,
+            ),
+          ),
+          selectedDayPredicate: (day) {
+            return state.dateData.fold(
+              () => false,
+              (dateData) => isSameDay(dateData.date, day),
+            );
+          },
+          onDaySelected: (selectedDay, focusedDay) {
+            context.read<DateCalBloc>().add(DateCalEvent.selectDay(selectedDay));
+          },
+          onFormatChanged: (format) {
+            context.read<DateCalBloc>().add(DateCalEvent.setCalFormat(format));
+          },
+          onPageChanged: (focusedDay) {
+            context.read<DateCalBloc>().add(DateCalEvent.setFocusedDay(focusedDay));
+          },
+        );
+      },
+    );
+  }
+}
+
+class _IncludeTimeButton extends StatelessWidget {
+  const _IncludeTimeButton({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return BlocSelector<DateCalBloc, DateCalState, bool>(
+      selector: (state) => state.dateTypeOption.includeTime,
+      builder: (context, includeTime) {
+        return SizedBox(
+          height: 50,
+          child: Padding(
+            padding: kMargin,
+            child: Row(
+              children: [
+                svgWidget("grid/clock", color: theme.iconColor),
+                const HSpace(4),
+                FlowyText.medium(LocaleKeys.grid_field_includeTime.tr(), fontSize: 14),
+                const Spacer(),
+                Switch(
+                  value: includeTime,
+                  onChanged: (newValue) => context.read<DateCalBloc>().add(DateCalEvent.setIncludeTime(newValue)),
+                ),
+              ],
+            ),
+          ),
+        );
+      },
+    );
+  }
+}
+
+class _TimeTextField extends StatefulWidget {
+  final DateCalBloc bloc;
+  const _TimeTextField({
+    required this.bloc,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<_TimeTextField> createState() => _TimeTextFieldState();
+}
+
+class _TimeTextFieldState extends State<_TimeTextField> {
+  late final FocusNode _focusNode;
+  late final TextEditingController _controller;
+
+  @override
+  void initState() {
+    _focusNode = FocusNode();
+    _controller = TextEditingController(text: widget.bloc.state.time);
+    if (widget.bloc.state.dateTypeOption.includeTime) {
+      _focusNode.addListener(() {
+        if (mounted) {
+          widget.bloc.add(DateCalEvent.setTime(_controller.text));
+        }
+      });
+    }
+
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return BlocConsumer<DateCalBloc, DateCalState>(
+      listener: (context, state) {
+        _controller.text = state.time ?? "";
+      },
+      listenWhen: (p, c) => p.time != c.time,
+      builder: (context, state) {
+        if (state.dateTypeOption.includeTime) {
+          return Padding(
+            padding: kMargin,
+            child: RoundedInputField(
+              height: 40,
+              focusNode: _focusNode,
+              controller: _controller,
+              style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
+              normalBorderColor: theme.shader4,
+              errorBorderColor: theme.red,
+              focusBorderColor: theme.main1,
+              cursorColor: theme.main1,
+              errorText: state.timeFormatError.fold(() => "", (error) => error),
+              onEditingComplete: (value) {
+                widget.bloc.add(DateCalEvent.setTime(value));
+              },
+            ),
+          );
+        } else {
+          return const SizedBox();
+        }
+      },
+    );
+  }
+
+  @override
+  void dispose() {
+    _focusNode.dispose();
+    super.dispose();
+  }
+}
+
+class _DateTypeOptionButton extends StatelessWidget {
+  const _DateTypeOptionButton({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    final title = LocaleKeys.grid_field_dateFormat.tr() + " &" + LocaleKeys.grid_field_timeFormat.tr();
+    return BlocSelector<DateCalBloc, DateCalState, DateTypeOption>(
+      selector: (state) => state.dateTypeOption,
+      builder: (context, dateTypeOption) {
+        return FlowyButton(
+          text: FlowyText.medium(title, fontSize: 12),
+          hoverColor: theme.hover,
+          margin: kMargin,
+          onTap: () => _showTimeSetting(dateTypeOption, context),
+          rightIcon: svgWidget("grid/more", color: theme.iconColor),
+        );
+      },
+    );
+  }
+
+  void _showTimeSetting(DateTypeOption dateTypeOption, BuildContext context) {
+    final setting = _CalDateTimeSetting(
+      dateTypeOption: dateTypeOption,
+      onEvent: (event) => context.read<DateCalBloc>().add(event),
+    );
+    setting.show(context);
+  }
+}
+
+class _CalDateTimeSetting extends StatefulWidget {
+  final DateTypeOption dateTypeOption;
+  final Function(DateCalEvent) onEvent;
+  const _CalDateTimeSetting({required this.dateTypeOption, required this.onEvent, Key? key}) : super(key: key);
+
+  @override
+  State<_CalDateTimeSetting> createState() => _CalDateTimeSettingState();
+
+  static String identifier() {
+    return (_CalDateTimeSetting).toString();
+  }
+
+  void show(BuildContext context) {
+    FlowyOverlay.of(context).insertWithAnchor(
+      widget: OverlayContainer(
+        child: this,
+        constraints: BoxConstraints.loose(const Size(140, 100)),
+      ),
+      identifier: _CalDateTimeSetting.identifier(),
+      anchorContext: context,
+      anchorDirection: AnchorDirection.rightWithCenterAligned,
+      anchorOffset: const Offset(20, 0),
+    );
+  }
+}
+
+class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
+  String? overlayIdentifier;
+
+  @override
+  Widget build(BuildContext context) {
+    List<Widget> children = [
+      DateFormatButton(onTap: () {
+        final list = DateFormatList(
+          selectedFormat: widget.dateTypeOption.dateFormat,
+          onSelected: (format) => widget.onEvent(DateCalEvent.setDateFormat(format)),
+        );
+        _showOverlay(context, list);
+      }),
+      TimeFormatButton(
+        timeFormat: widget.dateTypeOption.timeFormat,
+        onTap: () {
+          final list = TimeFormatList(
+            selectedFormat: widget.dateTypeOption.timeFormat,
+            onSelected: (format) => widget.onEvent(DateCalEvent.setTimeFormat(format)),
+          );
+          _showOverlay(context, list);
+        },
+      ),
+    ];
+
+    return SizedBox(
+      width: 180,
+      child: ListView.separated(
+        shrinkWrap: true,
+        controller: ScrollController(),
+        separatorBuilder: (context, index) {
+          return VSpace(GridSize.typeOptionSeparatorHeight);
+        },
+        itemCount: children.length,
+        itemBuilder: (BuildContext context, int index) {
+          return children[index];
+        },
+      ),
+    );
+  }
+
+  void _showOverlay(BuildContext context, Widget child) {
+    if (overlayIdentifier != null) {
+      FlowyOverlay.of(context).remove(overlayIdentifier!);
+    }
+
+    overlayIdentifier = child.toString();
+    FlowyOverlay.of(context).insertWithAnchor(
+      widget: OverlayContainer(
+        child: child,
+        constraints: BoxConstraints.loose(const Size(460, 440)),
+      ),
+      identifier: overlayIdentifier!,
+      anchorContext: context,
+      anchorDirection: AnchorDirection.rightWithCenterAligned,
+      style: FlowyOverlayStyle(blur: false),
+      anchorOffset: const Offset(20, 0),
+    );
+  }
+}

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

@@ -0,0 +1,92 @@
+import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/grid/prelude.dart';
+
+import '../cell_builder.dart';
+import 'calendar.dart';
+
+class DateCellStyle extends GridCellStyle {
+  Alignment alignment;
+
+  DateCellStyle({this.alignment = Alignment.center});
+}
+
+abstract class GridCellDelegate {
+  void onFocus(bool isFocus);
+  GridCellDelegate get delegate;
+}
+
+class DateCell extends GridCellWidget {
+  final GridCellContextBuilder cellContextBuilder;
+  late final DateCellStyle? cellStyle;
+
+  DateCell({
+    GridCellStyle? style,
+    required this.cellContextBuilder,
+    Key? key,
+  }) : super(key: key) {
+    if (style != null) {
+      cellStyle = (style as DateCellStyle);
+    } else {
+      cellStyle = null;
+    }
+  }
+
+  @override
+  State<DateCell> createState() => _DateCellState();
+}
+
+class _DateCellState extends State<DateCell> {
+  late DateCellBloc _cellBloc;
+
+  @override
+  void initState() {
+    final cellContext = widget.cellContextBuilder.build();
+    _cellBloc = getIt<DateCellBloc>(param1: cellContext)..add(const DateCellEvent.initial());
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final alignment = widget.cellStyle != null ? widget.cellStyle!.alignment : Alignment.center;
+    return BlocProvider.value(
+      value: _cellBloc,
+      child: BlocBuilder<DateCellBloc, DateCellState>(
+        builder: (context, state) {
+          return SizedBox.expand(
+            child: GestureDetector(
+              behavior: HitTestBehavior.opaque,
+              onTap: () => _showCalendar(context),
+              child: MouseRegion(
+                opaque: false,
+                cursor: SystemMouseCursors.click,
+                child: Align(
+                  alignment: alignment,
+                  child: FlowyText.medium(state.data.foldRight("", (data, _) => data.date), fontSize: 12),
+                ),
+              ),
+            ),
+          );
+        },
+      ),
+    );
+  }
+
+  void _showCalendar(BuildContext context) {
+    final bloc = context.read<DateCellBloc>();
+    widget.onFocus.value = true;
+    final calendar = CellCalendar(onDismissed: () => widget.onFocus.value = false);
+    calendar.show(
+      context,
+      cellContext: bloc.cellContext.clone(),
+    );
+  }
+
+  @override
+  Future<void> dispose() async {
+    _cellBloc.close();
+    super.dispose();
+  }
+}

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

@@ -1,6 +1,6 @@
 export 'cell_builder.dart';
 export 'text_cell.dart';
 export 'number_cell.dart';
-export 'date_cell.dart';
+export 'date_cell/date_cell.dart';
 export 'checkbox_cell.dart';
 export 'selection_cell/selection_cell.dart';

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

@@ -1,5 +1,5 @@
 import 'dart:collection';
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'package:app_flowy/workspace/application/grid/cell/selection_editor_bloc.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart';

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

@@ -53,7 +53,7 @@ class _InputTextFieldState extends State<InputTextField> {
           widget.onChanged!(text);
         }
       },
-      onEditingComplete: () {
+      onEditingComplete: (_) {
         if (widget.onDone != null) {
           widget.onDone!(_controller.text);
         }

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

@@ -150,7 +150,7 @@ class FieldCellButton extends StatelessWidget {
       onTap: onTap,
       leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
       text: FlowyText.medium(field.name, fontSize: 12),
-      padding: GridSize.cellContentInsets,
+      margin: GridSize.cellContentInsets,
     );
   }
 }

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

@@ -80,7 +80,7 @@ class _FieldEditorPannelState extends State<FieldEditorPannel> {
       height: GridSize.typeOptionItemHeight,
       child: FlowyButton(
         text: FlowyText.medium(field.fieldType.title(), fontSize: 12),
-        padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
+        margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
         hoverColor: theme.hover,
         onTap: () {
           final list = FieldTypeList(onSelectField: (newFieldType) {
@@ -119,14 +119,12 @@ class _FieldEditorPannelState extends State<FieldEditorPannel> {
       context.read<FieldEditorPannelBloc>().add(FieldEditorPannelEvent.didUpdateTypeOptionData(data));
     });
 
-    final typeOptionContext = TypeOptionContext(
-      gridId: state.gridId,
-      field: state.field,
-      data: state.typeOptionData,
-    );
-
     final builder = _makeTypeOptionBuild(
-      typeOptionContext: typeOptionContext,
+      typeOptionContext: TypeOptionContext(
+        gridId: state.gridId,
+        field: state.field,
+        data: state.typeOptionData,
+      ),
       overlayDelegate: overlayDelegate,
       dataDelegate: dataDelegate,
     );

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

@@ -50,8 +50,8 @@ class DateTypeOptionWidget extends TypeOptionWidget {
         listener: (context, state) => dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()),
         builder: (context, state) {
           return Column(children: [
-            _dateFormatButton(context, state.typeOption.dateFormat),
-            _timeFormatButton(context, state.typeOption.timeFormat),
+            _renderDateFormatButton(context, state.typeOption.dateFormat),
+            _renderTimeFormatButton(context, state.typeOption.timeFormat),
             const _IncludeTimeButton(),
           ]);
         },
@@ -59,44 +59,68 @@ class DateTypeOptionWidget extends TypeOptionWidget {
     );
   }
 
-  Widget _dateFormatButton(BuildContext context, DateFormat dataFormat) {
+  Widget _renderDateFormatButton(BuildContext context, DateFormat dataFormat) {
+    return DateFormatButton(onTap: () {
+      final list = DateFormatList(
+        selectedFormat: dataFormat,
+        onSelected: (format) {
+          context.read<DateTypeOptionBloc>().add(DateTypeOptionEvent.didSelectDateFormat(format));
+        },
+      );
+      overlayDelegate.showOverlay(context, list);
+    });
+  }
+
+  Widget _renderTimeFormatButton(BuildContext context, TimeFormat timeFormat) {
+    return TimeFormatButton(
+      timeFormat: timeFormat,
+      onTap: () {
+        final list = TimeFormatList(
+            selectedFormat: timeFormat,
+            onSelected: (format) {
+              context.read<DateTypeOptionBloc>().add(DateTypeOptionEvent.didSelectTimeFormat(format));
+            });
+        overlayDelegate.showOverlay(context, list);
+      },
+    );
+  }
+}
+
+class DateFormatButton extends StatelessWidget {
+  final VoidCallback onTap;
+  const DateFormatButton({required this.onTap, Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     return SizedBox(
       height: GridSize.typeOptionItemHeight,
       child: FlowyButton(
         text: FlowyText.medium(LocaleKeys.grid_field_dateFormat.tr(), fontSize: 12),
-        padding: GridSize.typeOptionContentInsets,
+        margin: GridSize.typeOptionContentInsets,
         hoverColor: theme.hover,
-        onTap: () {
-          final list = DateFormatList(
-            selectedFormat: dataFormat,
-            onSelected: (format) {
-              context.read<DateTypeOptionBloc>().add(DateTypeOptionEvent.didSelectDateFormat(format));
-            },
-          );
-          overlayDelegate.showOverlay(context, list);
-        },
+        onTap: onTap,
         rightIcon: svgWidget("grid/more", color: theme.iconColor),
       ),
     );
   }
+}
 
-  Widget _timeFormatButton(BuildContext context, TimeFormat timeFormat) {
+class TimeFormatButton extends StatelessWidget {
+  final TimeFormat timeFormat;
+  final VoidCallback onTap;
+  const TimeFormatButton({required this.timeFormat, required this.onTap, Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     return SizedBox(
       height: GridSize.typeOptionItemHeight,
       child: FlowyButton(
         text: FlowyText.medium(LocaleKeys.grid_field_timeFormat.tr(), fontSize: 12),
-        padding: GridSize.typeOptionContentInsets,
+        margin: GridSize.typeOptionContentInsets,
         hoverColor: theme.hover,
-        onTap: () {
-          final list = TimeFormatList(
-              selectedFormat: timeFormat,
-              onSelected: (format) {
-                context.read<DateTypeOptionBloc>().add(DateTypeOptionEvent.didSelectTimeFormat(format));
-              });
-          overlayDelegate.showOverlay(context, list);
-        },
+        onTap: onTap,
         rightIcon: svgWidget("grid/more", color: theme.iconColor),
       ),
     );

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

@@ -60,7 +60,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
                   FlowyText.regular(state.typeOption.format.title(), fontSize: 12),
                 ],
               ),
-              padding: GridSize.typeOptionContentInsets,
+              margin: GridSize.typeOptionContentInsets,
               hoverColor: theme.hover,
               onTap: () {
                 final list = NumberFormatList(

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

@@ -1,4 +1,4 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_service.dart';

+ 3 - 3
frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart

@@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
 class FlowyButton extends StatelessWidget {
   final Widget text;
   final VoidCallback? onTap;
-  final EdgeInsets padding;
+  final EdgeInsets margin;
   final Widget? leftIcon;
   final Widget? rightIcon;
   final Color hoverColor;
@@ -16,7 +16,7 @@ class FlowyButton extends StatelessWidget {
     Key? key,
     required this.text,
     this.onTap,
-    this.padding = const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
+    this.margin = const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
     this.leftIcon,
     this.rightIcon,
     this.hoverColor = Colors.transparent,
@@ -50,7 +50,7 @@ class FlowyButton extends StatelessWidget {
     }
 
     return Padding(
-      padding: padding,
+      padding: margin,
       child: Row(
         mainAxisAlignment: MainAxisAlignment.center,
         crossAxisAlignment: CrossAxisAlignment.center,

+ 12 - 2
frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart

@@ -15,7 +15,7 @@ class RoundedInputField extends StatefulWidget {
   final String errorText;
   final TextStyle style;
   final ValueChanged<String>? onChanged;
-  final VoidCallback? onEditingComplete;
+  final Function(String)? onEditingComplete;
   final String? initialValue;
   final EdgeInsets margin;
   final EdgeInsets padding;
@@ -60,6 +60,12 @@ class _RoundedInputFieldState extends State<RoundedInputField> {
   @override
   void initState() {
     obscuteText = widget.obscureText;
+    if (widget.controller != null) {
+      inputText = widget.controller!.text;
+    } else {
+      inputText = widget.initialValue ?? "";
+    }
+
     super.initState();
   }
 
@@ -90,7 +96,11 @@ class _RoundedInputFieldState extends State<RoundedInputField> {
             }
             setState(() {});
           },
-          onEditingComplete: widget.onEditingComplete,
+          onEditingComplete: () {
+            if (widget.onEditingComplete != null) {
+              widget.onEditingComplete!(inputText);
+            }
+          },
           cursorColor: widget.cursorColor,
           obscureText: obscuteText,
           style: widget.style,

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

@@ -69,6 +69,23 @@ class GridEventUpdateField {
     }
 }
 
+class GridEventUpdateFieldTypeOption {
+     UpdateFieldTypeOptionPayload request;
+     GridEventUpdateFieldTypeOption(this.request);
+
+    Future<Either<Unit, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.UpdateFieldTypeOption.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
 class GridEventInsertField {
      InsertFieldPayload request;
      GridEventInsertField(this.request);
@@ -138,7 +155,7 @@ class GridEventDuplicateField {
 }
 
 class GridEventGetEditFieldContext {
-     GetEditFieldContextPayload request;
+     EditFieldPayload request;
      GridEventGetEditFieldContext(this.request);
 
     Future<Either<EditFieldContext, FlowyError>> send() {
@@ -171,6 +188,23 @@ class GridEventMoveItem {
     }
 }
 
+class GridEventGetFieldTypeOption {
+     EditFieldPayload request;
+     GridEventGetFieldTypeOption(this.request);
+
+    Future<Either<FieldTypeOptionData, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.GetFieldTypeOption.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(FieldTypeOptionData.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
 class GridEventNewSelectOption {
      CreateSelectOptionPayload request;
      GridEventNewSelectOption(this.request);
@@ -188,18 +222,18 @@ class GridEventNewSelectOption {
     }
 }
 
-class GridEventGetSelectOptionContext {
+class GridEventGetSelectOptionCellData {
      CellIdentifierPayload request;
-     GridEventGetSelectOptionContext(this.request);
+     GridEventGetSelectOptionCellData(this.request);
 
-    Future<Either<SelectOptionContext, FlowyError>> send() {
+    Future<Either<SelectOptionCellData, FlowyError>> send() {
     final request = FFIRequest.create()
-          ..event = GridEvent.GetSelectOptionContext.toString()
+          ..event = GridEvent.GetSelectOptionCellData.toString()
           ..payload = requestToBytes(this.request);
 
     return Dispatch.asyncRequest(request)
         .then((bytesResult) => bytesResult.fold(
-           (okBytes) => left(SelectOptionContext.fromBuffer(okBytes)),
+           (okBytes) => left(SelectOptionCellData.fromBuffer(okBytes)),
            (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
     }
@@ -324,13 +358,13 @@ class GridEventUpdateCell {
     }
 }
 
-class GridEventUpdateCellSelectOption {
+class GridEventUpdateSelectOptionCell {
      SelectOptionCellChangesetPayload request;
-     GridEventUpdateCellSelectOption(this.request);
+     GridEventUpdateSelectOptionCell(this.request);
 
     Future<Either<Unit, FlowyError>> send() {
     final request = FFIRequest.create()
-          ..event = GridEvent.UpdateCellSelectOption.toString()
+          ..event = GridEvent.UpdateSelectOptionCell.toString()
           ..payload = requestToBytes(this.request);
 
     return Dispatch.asyncRequest(request)
@@ -341,3 +375,37 @@ class GridEventUpdateCellSelectOption {
     }
 }
 
+class GridEventUpdateDateCell {
+     DateChangesetPayload request;
+     GridEventUpdateDateCell(this.request);
+
+    Future<Either<Unit, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.UpdateDateCell.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
+class GridEventGetDateCellData {
+     CellIdentifierPayload request;
+     GridEventGetDateCellData(this.request);
+
+    Future<Either<DateCellData, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.GetDateCellData.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(DateCellData.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+

+ 1 - 0
frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart

@@ -5,6 +5,7 @@ import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/dart-ffi/ffi_response.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';

+ 3 - 1
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbenum.dart

@@ -52,7 +52,8 @@ class ErrorCode extends $pb.ProtobufEnum {
   static const ErrorCode FieldNotExists = ErrorCode._(443, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'FieldNotExists');
   static const ErrorCode FieldInvalidOperation = ErrorCode._(444, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'FieldInvalidOperation');
   static const ErrorCode TypeOptionDataIsEmpty = ErrorCode._(450, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TypeOptionDataIsEmpty');
-  static const ErrorCode InvalidData = ErrorCode._(500, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InvalidData');
+  static const ErrorCode InvalidDateTimeFormat = ErrorCode._(500, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InvalidDateTimeFormat');
+  static const ErrorCode InvalidData = ErrorCode._(1000, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InvalidData');
 
   static const $core.List<ErrorCode> values = <ErrorCode> [
     Internal,
@@ -97,6 +98,7 @@ class ErrorCode extends $pb.ProtobufEnum {
     FieldNotExists,
     FieldInvalidOperation,
     TypeOptionDataIsEmpty,
+    InvalidDateTimeFormat,
     InvalidData,
   ];
 

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

@@ -54,9 +54,10 @@ const ErrorCode$json = const {
     const {'1': 'FieldNotExists', '2': 443},
     const {'1': 'FieldInvalidOperation', '2': 444},
     const {'1': 'TypeOptionDataIsEmpty', '2': 450},
-    const {'1': 'InvalidData', '2': 500},
+    const {'1': 'InvalidDateTimeFormat', '2': 500},
+    const {'1': 'InvalidData', '2': 1000},
   ],
 };
 
 /// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSDAoISW50ZXJuYWwQABIUChBVc2VyVW5hdXRob3JpemVkEAISEgoOUmVjb3JkTm90Rm91bmQQAxIRCg1Vc2VySWRJc0VtcHR5EAQSGAoUV29ya3NwYWNlTmFtZUludmFsaWQQZBIWChJXb3Jrc3BhY2VJZEludmFsaWQQZRIYChRBcHBDb2xvclN0eWxlSW52YWxpZBBmEhgKFFdvcmtzcGFjZURlc2NUb29Mb25nEGcSGAoUV29ya3NwYWNlTmFtZVRvb0xvbmcQaBIQCgxBcHBJZEludmFsaWQQbhISCg5BcHBOYW1lSW52YWxpZBBvEhMKD1ZpZXdOYW1lSW52YWxpZBB4EhgKFFZpZXdUaHVtYm5haWxJbnZhbGlkEHkSEQoNVmlld0lkSW52YWxpZBB6EhMKD1ZpZXdEZXNjVG9vTG9uZxB7EhMKD1ZpZXdEYXRhSW52YWxpZBB8EhMKD1ZpZXdOYW1lVG9vTG9uZxB9EhEKDENvbm5lY3RFcnJvchDIARIRCgxFbWFpbElzRW1wdHkQrAISFwoSRW1haWxGb3JtYXRJbnZhbGlkEK0CEhcKEkVtYWlsQWxyZWFkeUV4aXN0cxCuAhIUCg9QYXNzd29yZElzRW1wdHkQrwISFAoPUGFzc3dvcmRUb29Mb25nELACEiUKIFBhc3N3b3JkQ29udGFpbnNGb3JiaWRDaGFyYWN0ZXJzELECEhoKFVBhc3N3b3JkRm9ybWF0SW52YWxpZBCyAhIVChBQYXNzd29yZE5vdE1hdGNoELMCEhQKD1VzZXJOYW1lVG9vTG9uZxC0AhInCiJVc2VyTmFtZUNvbnRhaW5Gb3JiaWRkZW5DaGFyYWN0ZXJzELUCEhQKD1VzZXJOYW1lSXNFbXB0eRC2AhISCg1Vc2VySWRJbnZhbGlkELcCEhEKDFVzZXJOb3RFeGlzdBC4AhIQCgtUZXh0VG9vTG9uZxCQAxISCg1HcmlkSWRJc0VtcHR5EJoDEhMKDkJsb2NrSWRJc0VtcHR5EKQDEhEKDFJvd0lkSXNFbXB0eRCuAxIUCg9PcHRpb25JZElzRW1wdHkQrwMSEwoORmllbGRJZElzRW1wdHkQuAMSFgoRRmllbGREb2VzTm90RXhpc3QQuQMSHAoXU2VsZWN0T3B0aW9uTmFtZUlzRW1wdHkQugMSEwoORmllbGROb3RFeGlzdHMQuwMSGgoVRmllbGRJbnZhbGlkT3BlcmF0aW9uELwDEhoKFVR5cGVPcHRpb25EYXRhSXNFbXB0eRDCAxIQCgtJbnZhbGlkRGF0YRD0Aw==');
+final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSDAoISW50ZXJuYWwQABIUChBVc2VyVW5hdXRob3JpemVkEAISEgoOUmVjb3JkTm90Rm91bmQQAxIRCg1Vc2VySWRJc0VtcHR5EAQSGAoUV29ya3NwYWNlTmFtZUludmFsaWQQZBIWChJXb3Jrc3BhY2VJZEludmFsaWQQZRIYChRBcHBDb2xvclN0eWxlSW52YWxpZBBmEhgKFFdvcmtzcGFjZURlc2NUb29Mb25nEGcSGAoUV29ya3NwYWNlTmFtZVRvb0xvbmcQaBIQCgxBcHBJZEludmFsaWQQbhISCg5BcHBOYW1lSW52YWxpZBBvEhMKD1ZpZXdOYW1lSW52YWxpZBB4EhgKFFZpZXdUaHVtYm5haWxJbnZhbGlkEHkSEQoNVmlld0lkSW52YWxpZBB6EhMKD1ZpZXdEZXNjVG9vTG9uZxB7EhMKD1ZpZXdEYXRhSW52YWxpZBB8EhMKD1ZpZXdOYW1lVG9vTG9uZxB9EhEKDENvbm5lY3RFcnJvchDIARIRCgxFbWFpbElzRW1wdHkQrAISFwoSRW1haWxGb3JtYXRJbnZhbGlkEK0CEhcKEkVtYWlsQWxyZWFkeUV4aXN0cxCuAhIUCg9QYXNzd29yZElzRW1wdHkQrwISFAoPUGFzc3dvcmRUb29Mb25nELACEiUKIFBhc3N3b3JkQ29udGFpbnNGb3JiaWRDaGFyYWN0ZXJzELECEhoKFVBhc3N3b3JkRm9ybWF0SW52YWxpZBCyAhIVChBQYXNzd29yZE5vdE1hdGNoELMCEhQKD1VzZXJOYW1lVG9vTG9uZxC0AhInCiJVc2VyTmFtZUNvbnRhaW5Gb3JiaWRkZW5DaGFyYWN0ZXJzELUCEhQKD1VzZXJOYW1lSXNFbXB0eRC2AhISCg1Vc2VySWRJbnZhbGlkELcCEhEKDFVzZXJOb3RFeGlzdBC4AhIQCgtUZXh0VG9vTG9uZxCQAxISCg1HcmlkSWRJc0VtcHR5EJoDEhMKDkJsb2NrSWRJc0VtcHR5EKQDEhEKDFJvd0lkSXNFbXB0eRCuAxIUCg9PcHRpb25JZElzRW1wdHkQrwMSEwoORmllbGRJZElzRW1wdHkQuAMSFgoRRmllbGREb2VzTm90RXhpc3QQuQMSHAoXU2VsZWN0T3B0aW9uTmFtZUlzRW1wdHkQugMSEwoORmllbGROb3RFeGlzdHMQuwMSGgoVRmllbGRJbnZhbGlkT3BlcmF0aW9uELwDEhoKFVR5cGVPcHRpb25EYXRhSXNFbXB0eRDCAxIaChVJbnZhbGlkRGF0ZVRpbWVGb3JtYXQQ9AMSEAoLSW52YWxpZERhdGEQ6Ac=');

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

@@ -490,8 +490,18 @@ class GetEditFieldContextPayload extends $pb.GeneratedMessage {
   void clearFieldType() => clearField(3);
 }
 
+enum EditFieldPayload_OneOfFieldId {
+  fieldId, 
+  notSet
+}
+
 class EditFieldPayload extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, EditFieldPayload_OneOfFieldId> _EditFieldPayload_OneOfFieldIdByTag = {
+    2 : EditFieldPayload_OneOfFieldId.fieldId,
+    0 : EditFieldPayload_OneOfFieldId.notSet
+  };
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'EditFieldPayload', createEmptyInstance: create)
+    ..oo(0, [2])
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
     ..e<FieldType>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldType', $pb.PbFieldType.OE, defaultOrMaker: FieldType.RichText, valueOf: FieldType.valueOf, enumValues: FieldType.values)
@@ -537,6 +547,9 @@ class EditFieldPayload extends $pb.GeneratedMessage {
   static EditFieldPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EditFieldPayload>(create);
   static EditFieldPayload? _defaultInstance;
 
+  EditFieldPayload_OneOfFieldId whichOneOfFieldId() => _EditFieldPayload_OneOfFieldIdByTag[$_whichOneof(0)]!;
+  void clearOneOfFieldId() => clearField($_whichOneof(0));
+
   @$pb.TagNumber(1)
   $core.String get gridId => $_getSZ(0);
   @$pb.TagNumber(1)
@@ -642,6 +655,67 @@ class EditFieldContext extends $pb.GeneratedMessage {
   void clearTypeOptionData() => clearField(3);
 }
 
+class FieldTypeOptionData extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'FieldTypeOptionData', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
+    ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptionData', $pb.PbFieldType.OY)
+    ..hasRequiredFields = false
+  ;
+
+  FieldTypeOptionData._() : super();
+  factory FieldTypeOptionData({
+    $core.String? fieldId,
+    $core.List<$core.int>? typeOptionData,
+  }) {
+    final _result = create();
+    if (fieldId != null) {
+      _result.fieldId = fieldId;
+    }
+    if (typeOptionData != null) {
+      _result.typeOptionData = typeOptionData;
+    }
+    return _result;
+  }
+  factory FieldTypeOptionData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory FieldTypeOptionData.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')
+  FieldTypeOptionData clone() => FieldTypeOptionData()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  FieldTypeOptionData copyWith(void Function(FieldTypeOptionData) updates) => super.copyWith((message) => updates(message as FieldTypeOptionData)) as FieldTypeOptionData; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static FieldTypeOptionData create() => FieldTypeOptionData._();
+  FieldTypeOptionData createEmptyInstance() => create();
+  static $pb.PbList<FieldTypeOptionData> createRepeated() => $pb.PbList<FieldTypeOptionData>();
+  @$core.pragma('dart2js:noInline')
+  static FieldTypeOptionData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FieldTypeOptionData>(create);
+  static FieldTypeOptionData? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get fieldId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set fieldId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasFieldId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearFieldId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.List<$core.int> get typeOptionData => $_getN(1);
+  @$pb.TagNumber(2)
+  set typeOptionData($core.List<$core.int> v) { $_setBytes(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasTypeOptionData() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearTypeOptionData() => clearField(2);
+}
+
 class RepeatedField extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RepeatedField', createEmptyInstance: create)
     ..pc<Field>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: Field.create)
@@ -1276,6 +1350,7 @@ class Cell extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Cell', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'content')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
     ..hasRequiredFields = false
   ;
 
@@ -1283,6 +1358,7 @@ class Cell extends $pb.GeneratedMessage {
   factory Cell({
     $core.String? fieldId,
     $core.String? content,
+    $core.String? data,
   }) {
     final _result = create();
     if (fieldId != null) {
@@ -1291,6 +1367,9 @@ class Cell extends $pb.GeneratedMessage {
     if (content != null) {
       _result.content = content;
     }
+    if (data != null) {
+      _result.data = data;
+    }
     return _result;
   }
   factory Cell.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -1331,6 +1410,15 @@ class Cell extends $pb.GeneratedMessage {
   $core.bool hasContent() => $_has(1);
   @$pb.TagNumber(2)
   void clearContent() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.String get data => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set data($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasData() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearData() => clearField(3);
 }
 
 class RepeatedCell extends $pb.GeneratedMessage {
@@ -1693,6 +1781,81 @@ class InsertFieldPayload extends $pb.GeneratedMessage {
   void clearStartFieldId() => clearField(4);
 }
 
+class UpdateFieldTypeOptionPayload extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UpdateFieldTypeOptionPayload', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
+    ..a<$core.List<$core.int>>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptionData', $pb.PbFieldType.OY)
+    ..hasRequiredFields = false
+  ;
+
+  UpdateFieldTypeOptionPayload._() : super();
+  factory UpdateFieldTypeOptionPayload({
+    $core.String? gridId,
+    $core.String? fieldId,
+    $core.List<$core.int>? typeOptionData,
+  }) {
+    final _result = create();
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
+    if (fieldId != null) {
+      _result.fieldId = fieldId;
+    }
+    if (typeOptionData != null) {
+      _result.typeOptionData = typeOptionData;
+    }
+    return _result;
+  }
+  factory UpdateFieldTypeOptionPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory UpdateFieldTypeOptionPayload.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')
+  UpdateFieldTypeOptionPayload clone() => UpdateFieldTypeOptionPayload()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  UpdateFieldTypeOptionPayload copyWith(void Function(UpdateFieldTypeOptionPayload) updates) => super.copyWith((message) => updates(message as UpdateFieldTypeOptionPayload)) as UpdateFieldTypeOptionPayload; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static UpdateFieldTypeOptionPayload create() => UpdateFieldTypeOptionPayload._();
+  UpdateFieldTypeOptionPayload createEmptyInstance() => create();
+  static $pb.PbList<UpdateFieldTypeOptionPayload> createRepeated() => $pb.PbList<UpdateFieldTypeOptionPayload>();
+  @$core.pragma('dart2js:noInline')
+  static UpdateFieldTypeOptionPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UpdateFieldTypeOptionPayload>(create);
+  static UpdateFieldTypeOptionPayload? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get gridId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set gridId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasGridId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearGridId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get fieldId => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set fieldId($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasFieldId() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearFieldId() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.List<$core.int> get typeOptionData => $_getN(2);
+  @$pb.TagNumber(3)
+  set typeOptionData($core.List<$core.int> v) { $_setBytes(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasTypeOptionData() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearTypeOptionData() => clearField(3);
+}
+
 class 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')
@@ -2164,22 +2327,22 @@ class MoveItemPayload extends $pb.GeneratedMessage {
   void clearTy() => clearField(5);
 }
 
-enum CellChangeset_OneOfData {
-  data, 
+enum CellChangeset_OneOfCellContentChangeset {
+  cellContentChangeset, 
   notSet
 }
 
 class CellChangeset extends $pb.GeneratedMessage {
-  static const $core.Map<$core.int, CellChangeset_OneOfData> _CellChangeset_OneOfDataByTag = {
-    4 : CellChangeset_OneOfData.data,
-    0 : CellChangeset_OneOfData.notSet
+  static const $core.Map<$core.int, CellChangeset_OneOfCellContentChangeset> _CellChangeset_OneOfCellContentChangesetByTag = {
+    4 : CellChangeset_OneOfCellContentChangeset.cellContentChangeset,
+    0 : CellChangeset_OneOfCellContentChangeset.notSet
   };
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CellChangeset', createEmptyInstance: create)
     ..oo(0, [4])
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowId')
     ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
-    ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
+    ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'cellContentChangeset')
     ..hasRequiredFields = false
   ;
 
@@ -2188,7 +2351,7 @@ class CellChangeset extends $pb.GeneratedMessage {
     $core.String? gridId,
     $core.String? rowId,
     $core.String? fieldId,
-    $core.String? data,
+    $core.String? cellContentChangeset,
   }) {
     final _result = create();
     if (gridId != null) {
@@ -2200,8 +2363,8 @@ class CellChangeset extends $pb.GeneratedMessage {
     if (fieldId != null) {
       _result.fieldId = fieldId;
     }
-    if (data != null) {
-      _result.data = data;
+    if (cellContentChangeset != null) {
+      _result.cellContentChangeset = cellContentChangeset;
     }
     return _result;
   }
@@ -2226,8 +2389,8 @@ class CellChangeset extends $pb.GeneratedMessage {
   static CellChangeset getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CellChangeset>(create);
   static CellChangeset? _defaultInstance;
 
-  CellChangeset_OneOfData whichOneOfData() => _CellChangeset_OneOfDataByTag[$_whichOneof(0)]!;
-  void clearOneOfData() => clearField($_whichOneof(0));
+  CellChangeset_OneOfCellContentChangeset whichOneOfCellContentChangeset() => _CellChangeset_OneOfCellContentChangesetByTag[$_whichOneof(0)]!;
+  void clearOneOfCellContentChangeset() => clearField($_whichOneof(0));
 
   @$pb.TagNumber(1)
   $core.String get gridId => $_getSZ(0);
@@ -2257,12 +2420,12 @@ class CellChangeset extends $pb.GeneratedMessage {
   void clearFieldId() => clearField(3);
 
   @$pb.TagNumber(4)
-  $core.String get data => $_getSZ(3);
+  $core.String get cellContentChangeset => $_getSZ(3);
   @$pb.TagNumber(4)
-  set data($core.String v) { $_setString(3, v); }
+  set cellContentChangeset($core.String v) { $_setString(3, v); }
   @$pb.TagNumber(4)
-  $core.bool hasData() => $_has(3);
+  $core.bool hasCellContentChangeset() => $_has(3);
   @$pb.TagNumber(4)
-  void clearData() => clearField(4);
+  void clearCellContentChangeset() => clearField(4);
 }
 

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

@@ -117,13 +117,16 @@ const EditFieldPayload$json = const {
   '1': 'EditFieldPayload',
   '2': const [
     const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
-    const {'1': 'field_id', '3': 2, '4': 1, '5': 9, '10': 'fieldId'},
+    const {'1': 'field_id', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'fieldId'},
     const {'1': 'field_type', '3': 3, '4': 1, '5': 14, '6': '.FieldType', '10': 'fieldType'},
   ],
+  '8': const [
+    const {'1': 'one_of_field_id'},
+  ],
 };
 
 /// Descriptor for `EditFieldPayload`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List editFieldPayloadDescriptor = $convert.base64Decode('ChBFZGl0RmllbGRQYXlsb2FkEhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIZCghmaWVsZF9pZBgCIAEoCVIHZmllbGRJZBIpCgpmaWVsZF90eXBlGAMgASgOMgouRmllbGRUeXBlUglmaWVsZFR5cGU=');
+final $typed_data.Uint8List editFieldPayloadDescriptor = $convert.base64Decode('ChBFZGl0RmllbGRQYXlsb2FkEhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIbCghmaWVsZF9pZBgCIAEoCUgAUgdmaWVsZElkEikKCmZpZWxkX3R5cGUYAyABKA4yCi5GaWVsZFR5cGVSCWZpZWxkVHlwZUIRCg9vbmVfb2ZfZmllbGRfaWQ=');
 @$core.Deprecated('Use editFieldContextDescriptor instead')
 const EditFieldContext$json = const {
   '1': 'EditFieldContext',
@@ -136,6 +139,17 @@ const EditFieldContext$json = const {
 
 /// Descriptor for `EditFieldContext`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List editFieldContextDescriptor = $convert.base64Decode('ChBFZGl0RmllbGRDb250ZXh0EhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIlCgpncmlkX2ZpZWxkGAIgASgLMgYuRmllbGRSCWdyaWRGaWVsZBIoChB0eXBlX29wdGlvbl9kYXRhGAMgASgMUg50eXBlT3B0aW9uRGF0YQ==');
+@$core.Deprecated('Use fieldTypeOptionDataDescriptor instead')
+const FieldTypeOptionData$json = const {
+  '1': 'FieldTypeOptionData',
+  '2': const [
+    const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'},
+    const {'1': 'type_option_data', '3': 2, '4': 1, '5': 12, '10': 'typeOptionData'},
+  ],
+};
+
+/// Descriptor for `FieldTypeOptionData`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List fieldTypeOptionDataDescriptor = $convert.base64Decode('ChNGaWVsZFR5cGVPcHRpb25EYXRhEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEigKEHR5cGVfb3B0aW9uX2RhdGEYAiABKAxSDnR5cGVPcHRpb25EYXRh');
 @$core.Deprecated('Use repeatedFieldDescriptor instead')
 const RepeatedField$json = const {
   '1': 'RepeatedField',
@@ -277,11 +291,12 @@ const Cell$json = const {
   '2': const [
     const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'},
     const {'1': 'content', '3': 2, '4': 1, '5': 9, '10': 'content'},
+    const {'1': 'data', '3': 3, '4': 1, '5': 9, '10': 'data'},
   ],
 };
 
 /// Descriptor for `Cell`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List cellDescriptor = $convert.base64Decode('CgRDZWxsEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEhgKB2NvbnRlbnQYAiABKAlSB2NvbnRlbnQ=');
+final $typed_data.Uint8List cellDescriptor = $convert.base64Decode('CgRDZWxsEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEhgKB2NvbnRlbnQYAiABKAlSB2NvbnRlbnQSEgoEZGF0YRgDIAEoCVIEZGF0YQ==');
 @$core.Deprecated('Use repeatedCellDescriptor instead')
 const RepeatedCell$json = const {
   '1': 'RepeatedCell',
@@ -352,6 +367,18 @@ const InsertFieldPayload$json = const {
 
 /// Descriptor for `InsertFieldPayload`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List insertFieldPayloadDescriptor = $convert.base64Decode('ChJJbnNlcnRGaWVsZFBheWxvYWQSFwoHZ3JpZF9pZBgBIAEoCVIGZ3JpZElkEhwKBWZpZWxkGAIgASgLMgYuRmllbGRSBWZpZWxkEigKEHR5cGVfb3B0aW9uX2RhdGEYAyABKAxSDnR5cGVPcHRpb25EYXRhEiYKDnN0YXJ0X2ZpZWxkX2lkGAQgASgJSABSDHN0YXJ0RmllbGRJZEIXChVvbmVfb2Zfc3RhcnRfZmllbGRfaWQ=');
+@$core.Deprecated('Use updateFieldTypeOptionPayloadDescriptor instead')
+const UpdateFieldTypeOptionPayload$json = const {
+  '1': 'UpdateFieldTypeOptionPayload',
+  '2': const [
+    const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
+    const {'1': 'field_id', '3': 2, '4': 1, '5': 9, '10': 'fieldId'},
+    const {'1': 'type_option_data', '3': 3, '4': 1, '5': 12, '10': 'typeOptionData'},
+  ],
+};
+
+/// Descriptor for `UpdateFieldTypeOptionPayload`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List updateFieldTypeOptionPayloadDescriptor = $convert.base64Decode('ChxVcGRhdGVGaWVsZFR5cGVPcHRpb25QYXlsb2FkEhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIZCghmaWVsZF9pZBgCIAEoCVIHZmllbGRJZBIoChB0eXBlX29wdGlvbl9kYXRhGAMgASgMUg50eXBlT3B0aW9uRGF0YQ==');
 @$core.Deprecated('Use queryFieldPayloadDescriptor instead')
 const QueryFieldPayload$json = const {
   '1': 'QueryFieldPayload',
@@ -422,12 +449,12 @@ const CellChangeset$json = const {
     const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
     const {'1': 'row_id', '3': 2, '4': 1, '5': 9, '10': 'rowId'},
     const {'1': 'field_id', '3': 3, '4': 1, '5': 9, '10': 'fieldId'},
-    const {'1': 'data', '3': 4, '4': 1, '5': 9, '9': 0, '10': 'data'},
+    const {'1': 'cell_content_changeset', '3': 4, '4': 1, '5': 9, '9': 0, '10': 'cellContentChangeset'},
   ],
   '8': const [
-    const {'1': 'one_of_data'},
+    const {'1': 'one_of_cell_content_changeset'},
   ],
 };
 
 /// Descriptor for `CellChangeset`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List cellChangesetDescriptor = $convert.base64Decode('Cg1DZWxsQ2hhbmdlc2V0EhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIVCgZyb3dfaWQYAiABKAlSBXJvd0lkEhkKCGZpZWxkX2lkGAMgASgJUgdmaWVsZElkEhQKBGRhdGEYBCABKAlIAFIEZGF0YUINCgtvbmVfb2ZfZGF0YQ==');
+final $typed_data.Uint8List cellChangesetDescriptor = $convert.base64Decode('Cg1DZWxsQ2hhbmdlc2V0EhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIVCgZyb3dfaWQYAiABKAlSBXJvd0lkEhkKCGZpZWxkX2lkGAMgASgJUgdmaWVsZElkEjYKFmNlbGxfY29udGVudF9jaGFuZ2VzZXQYBCABKAlIAFIUY2VsbENvbnRlbnRDaGFuZ2VzZXRCHwodb25lX29mX2NlbGxfY29udGVudF9jaGFuZ2VzZXQ=');

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

@@ -7,8 +7,11 @@
 
 import 'dart:core' as $core;
 
+import 'package:fixnum/fixnum.dart' as $fixnum;
 import 'package:protobuf/protobuf.dart' as $pb;
 
+import 'cell_entities.pb.dart' as $0;
+
 import 'date_type_option.pbenum.dart';
 
 export 'date_type_option.pbenum.dart';
@@ -88,3 +91,181 @@ class DateTypeOption extends $pb.GeneratedMessage {
   void clearIncludeTime() => clearField(3);
 }
 
+class DateCellData extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DateCellData', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'date')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'time')
+    ..aInt64(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'timestamp')
+    ..hasRequiredFields = false
+  ;
+
+  DateCellData._() : super();
+  factory DateCellData({
+    $core.String? date,
+    $core.String? time,
+    $fixnum.Int64? timestamp,
+  }) {
+    final _result = create();
+    if (date != null) {
+      _result.date = date;
+    }
+    if (time != null) {
+      _result.time = time;
+    }
+    if (timestamp != null) {
+      _result.timestamp = timestamp;
+    }
+    return _result;
+  }
+  factory DateCellData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory DateCellData.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')
+  DateCellData clone() => DateCellData()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  DateCellData copyWith(void Function(DateCellData) updates) => super.copyWith((message) => updates(message as DateCellData)) as DateCellData; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static DateCellData create() => DateCellData._();
+  DateCellData createEmptyInstance() => create();
+  static $pb.PbList<DateCellData> createRepeated() => $pb.PbList<DateCellData>();
+  @$core.pragma('dart2js:noInline')
+  static DateCellData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DateCellData>(create);
+  static DateCellData? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get date => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set date($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasDate() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearDate() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get time => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set time($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasTime() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearTime() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $fixnum.Int64 get timestamp => $_getI64(2);
+  @$pb.TagNumber(3)
+  set timestamp($fixnum.Int64 v) { $_setInt64(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasTimestamp() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearTimestamp() => clearField(3);
+}
+
+enum DateChangesetPayload_OneOfDate {
+  date, 
+  notSet
+}
+
+enum DateChangesetPayload_OneOfTime {
+  time, 
+  notSet
+}
+
+class DateChangesetPayload extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, DateChangesetPayload_OneOfDate> _DateChangesetPayload_OneOfDateByTag = {
+    2 : DateChangesetPayload_OneOfDate.date,
+    0 : DateChangesetPayload_OneOfDate.notSet
+  };
+  static const $core.Map<$core.int, DateChangesetPayload_OneOfTime> _DateChangesetPayload_OneOfTimeByTag = {
+    3 : DateChangesetPayload_OneOfTime.time,
+    0 : DateChangesetPayload_OneOfTime.notSet
+  };
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DateChangesetPayload', createEmptyInstance: create)
+    ..oo(0, [2])
+    ..oo(1, [3])
+    ..aOM<$0.CellIdentifierPayload>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'cellIdentifier', subBuilder: $0.CellIdentifierPayload.create)
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'date')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'time')
+    ..hasRequiredFields = false
+  ;
+
+  DateChangesetPayload._() : super();
+  factory DateChangesetPayload({
+    $0.CellIdentifierPayload? cellIdentifier,
+    $core.String? date,
+    $core.String? time,
+  }) {
+    final _result = create();
+    if (cellIdentifier != null) {
+      _result.cellIdentifier = cellIdentifier;
+    }
+    if (date != null) {
+      _result.date = date;
+    }
+    if (time != null) {
+      _result.time = time;
+    }
+    return _result;
+  }
+  factory DateChangesetPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory DateChangesetPayload.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')
+  DateChangesetPayload clone() => DateChangesetPayload()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  DateChangesetPayload copyWith(void Function(DateChangesetPayload) updates) => super.copyWith((message) => updates(message as DateChangesetPayload)) as DateChangesetPayload; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static DateChangesetPayload create() => DateChangesetPayload._();
+  DateChangesetPayload createEmptyInstance() => create();
+  static $pb.PbList<DateChangesetPayload> createRepeated() => $pb.PbList<DateChangesetPayload>();
+  @$core.pragma('dart2js:noInline')
+  static DateChangesetPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DateChangesetPayload>(create);
+  static DateChangesetPayload? _defaultInstance;
+
+  DateChangesetPayload_OneOfDate whichOneOfDate() => _DateChangesetPayload_OneOfDateByTag[$_whichOneof(0)]!;
+  void clearOneOfDate() => clearField($_whichOneof(0));
+
+  DateChangesetPayload_OneOfTime whichOneOfTime() => _DateChangesetPayload_OneOfTimeByTag[$_whichOneof(1)]!;
+  void clearOneOfTime() => clearField($_whichOneof(1));
+
+  @$pb.TagNumber(1)
+  $0.CellIdentifierPayload get cellIdentifier => $_getN(0);
+  @$pb.TagNumber(1)
+  set cellIdentifier($0.CellIdentifierPayload v) { setField(1, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasCellIdentifier() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearCellIdentifier() => clearField(1);
+  @$pb.TagNumber(1)
+  $0.CellIdentifierPayload ensureCellIdentifier() => $_ensure(0);
+
+  @$pb.TagNumber(2)
+  $core.String get date => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set date($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasDate() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearDate() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.String get time => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set time($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasTime() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearTime() => clearField(3);
+}
+

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

@@ -44,3 +44,31 @@ const DateTypeOption$json = const {
 
 /// Descriptor for `DateTypeOption`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List dateTypeOptionDescriptor = $convert.base64Decode('Cg5EYXRlVHlwZU9wdGlvbhIsCgtkYXRlX2Zvcm1hdBgBIAEoDjILLkRhdGVGb3JtYXRSCmRhdGVGb3JtYXQSLAoLdGltZV9mb3JtYXQYAiABKA4yCy5UaW1lRm9ybWF0Ugp0aW1lRm9ybWF0EiEKDGluY2x1ZGVfdGltZRgDIAEoCFILaW5jbHVkZVRpbWU=');
+@$core.Deprecated('Use dateCellDataDescriptor instead')
+const DateCellData$json = const {
+  '1': 'DateCellData',
+  '2': const [
+    const {'1': 'date', '3': 1, '4': 1, '5': 9, '10': 'date'},
+    const {'1': 'time', '3': 2, '4': 1, '5': 9, '10': 'time'},
+    const {'1': 'timestamp', '3': 3, '4': 1, '5': 3, '10': 'timestamp'},
+  ],
+};
+
+/// Descriptor for `DateCellData`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List dateCellDataDescriptor = $convert.base64Decode('CgxEYXRlQ2VsbERhdGESEgoEZGF0ZRgBIAEoCVIEZGF0ZRISCgR0aW1lGAIgASgJUgR0aW1lEhwKCXRpbWVzdGFtcBgDIAEoA1IJdGltZXN0YW1w');
+@$core.Deprecated('Use dateChangesetPayloadDescriptor instead')
+const DateChangesetPayload$json = const {
+  '1': 'DateChangesetPayload',
+  '2': const [
+    const {'1': 'cell_identifier', '3': 1, '4': 1, '5': 11, '6': '.CellIdentifierPayload', '10': 'cellIdentifier'},
+    const {'1': 'date', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'date'},
+    const {'1': 'time', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'time'},
+  ],
+  '8': const [
+    const {'1': 'one_of_date'},
+    const {'1': 'one_of_time'},
+  ],
+};
+
+/// Descriptor for `DateChangesetPayload`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List dateChangesetPayloadDescriptor = $convert.base64Decode('ChREYXRlQ2hhbmdlc2V0UGF5bG9hZBI/Cg9jZWxsX2lkZW50aWZpZXIYASABKAsyFi5DZWxsSWRlbnRpZmllclBheWxvYWRSDmNlbGxJZGVudGlmaWVyEhQKBGRhdGUYAiABKAlIAFIEZGF0ZRIUCgR0aW1lGAMgASgJSAFSBHRpbWVCDQoLb25lX29mX2RhdGVCDQoLb25lX29mX3RpbWU=');

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

@@ -14,14 +14,16 @@ class GridEvent extends $pb.ProtobufEnum {
   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 UpdateField = GridEvent._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateField');
-  static const GridEvent InsertField = GridEvent._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InsertField');
-  static const GridEvent DeleteField = GridEvent._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteField');
-  static const GridEvent SwitchToField = GridEvent._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SwitchToField');
-  static const GridEvent DuplicateField = GridEvent._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateField');
-  static const GridEvent GetEditFieldContext = GridEvent._(16, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetEditFieldContext');
-  static const GridEvent MoveItem = GridEvent._(17, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MoveItem');
+  static const GridEvent UpdateFieldTypeOption = GridEvent._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateFieldTypeOption');
+  static const GridEvent InsertField = GridEvent._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InsertField');
+  static const GridEvent DeleteField = GridEvent._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteField');
+  static const GridEvent SwitchToField = GridEvent._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SwitchToField');
+  static const GridEvent DuplicateField = GridEvent._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateField');
+  static const GridEvent GetEditFieldContext = GridEvent._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetEditFieldContext');
+  static const GridEvent MoveItem = GridEvent._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MoveItem');
+  static const GridEvent GetFieldTypeOption = GridEvent._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetFieldTypeOption');
   static const GridEvent NewSelectOption = GridEvent._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NewSelectOption');
-  static const GridEvent GetSelectOptionContext = GridEvent._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetSelectOptionContext');
+  static const GridEvent GetSelectOptionCellData = GridEvent._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetSelectOptionCellData');
   static const GridEvent UpdateSelectOption = GridEvent._(32, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateSelectOption');
   static const GridEvent CreateRow = GridEvent._(50, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateRow');
   static const GridEvent GetRow = GridEvent._(51, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetRow');
@@ -29,21 +31,25 @@ class GridEvent extends $pb.ProtobufEnum {
   static const GridEvent DuplicateRow = GridEvent._(53, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateRow');
   static const GridEvent GetCell = GridEvent._(70, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetCell');
   static const GridEvent UpdateCell = GridEvent._(71, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateCell');
-  static const GridEvent UpdateCellSelectOption = GridEvent._(72, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateCellSelectOption');
+  static const GridEvent UpdateSelectOptionCell = GridEvent._(72, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateSelectOptionCell');
+  static const GridEvent UpdateDateCell = GridEvent._(80, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateDateCell');
+  static const GridEvent GetDateCellData = GridEvent._(90, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetDateCellData');
 
   static const $core.List<GridEvent> values = <GridEvent> [
     GetGridData,
     GetGridBlocks,
     GetFields,
     UpdateField,
+    UpdateFieldTypeOption,
     InsertField,
     DeleteField,
     SwitchToField,
     DuplicateField,
     GetEditFieldContext,
     MoveItem,
+    GetFieldTypeOption,
     NewSelectOption,
-    GetSelectOptionContext,
+    GetSelectOptionCellData,
     UpdateSelectOption,
     CreateRow,
     GetRow,
@@ -51,7 +57,9 @@ class GridEvent extends $pb.ProtobufEnum {
     DuplicateRow,
     GetCell,
     UpdateCell,
-    UpdateCellSelectOption,
+    UpdateSelectOptionCell,
+    UpdateDateCell,
+    GetDateCellData,
   ];
 
   static final $core.Map<$core.int, GridEvent> _byValue = $pb.ProtobufEnum.initByValue(values);

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

@@ -16,14 +16,16 @@ const GridEvent$json = const {
     const {'1': 'GetGridBlocks', '2': 1},
     const {'1': 'GetFields', '2': 10},
     const {'1': 'UpdateField', '2': 11},
-    const {'1': 'InsertField', '2': 12},
-    const {'1': 'DeleteField', '2': 13},
-    const {'1': 'SwitchToField', '2': 14},
-    const {'1': 'DuplicateField', '2': 15},
-    const {'1': 'GetEditFieldContext', '2': 16},
-    const {'1': 'MoveItem', '2': 17},
+    const {'1': 'UpdateFieldTypeOption', '2': 12},
+    const {'1': 'InsertField', '2': 13},
+    const {'1': 'DeleteField', '2': 14},
+    const {'1': 'SwitchToField', '2': 20},
+    const {'1': 'DuplicateField', '2': 21},
+    const {'1': 'GetEditFieldContext', '2': 22},
+    const {'1': 'MoveItem', '2': 23},
+    const {'1': 'GetFieldTypeOption', '2': 24},
     const {'1': 'NewSelectOption', '2': 30},
-    const {'1': 'GetSelectOptionContext', '2': 31},
+    const {'1': 'GetSelectOptionCellData', '2': 31},
     const {'1': 'UpdateSelectOption', '2': 32},
     const {'1': 'CreateRow', '2': 50},
     const {'1': 'GetRow', '2': 51},
@@ -31,9 +33,11 @@ const GridEvent$json = const {
     const {'1': 'DuplicateRow', '2': 53},
     const {'1': 'GetCell', '2': 70},
     const {'1': 'UpdateCell', '2': 71},
-    const {'1': 'UpdateCellSelectOption', '2': 72},
+    const {'1': 'UpdateSelectOptionCell', '2': 72},
+    const {'1': 'UpdateDateCell', '2': 80},
+    const {'1': 'GetDateCellData', '2': 90},
   ],
 };
 
 /// Descriptor for `GridEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIPCgtJbnNlcnRGaWVsZBAMEg8KC0RlbGV0ZUZpZWxkEA0SEQoNU3dpdGNoVG9GaWVsZBAOEhIKDkR1cGxpY2F0ZUZpZWxkEA8SFwoTR2V0RWRpdEZpZWxkQ29udGV4dBAQEgwKCE1vdmVJdGVtEBESEwoPTmV3U2VsZWN0T3B0aW9uEB4SGgoWR2V0U2VsZWN0T3B0aW9uQ29udGV4dBAfEhYKElVwZGF0ZVNlbGVjdE9wdGlvbhAgEg0KCUNyZWF0ZVJvdxAyEgoKBkdldFJvdxAzEg0KCURlbGV0ZVJvdxA0EhAKDER1cGxpY2F0ZVJvdxA1EgsKB0dldENlbGwQRhIOCgpVcGRhdGVDZWxsEEcSGgoWVXBkYXRlQ2VsbFNlbGVjdE9wdGlvbhBI');
+final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIZChVVcGRhdGVGaWVsZFR5cGVPcHRpb24QDBIPCgtJbnNlcnRGaWVsZBANEg8KC0RlbGV0ZUZpZWxkEA4SEQoNU3dpdGNoVG9GaWVsZBAUEhIKDkR1cGxpY2F0ZUZpZWxkEBUSFwoTR2V0RWRpdEZpZWxkQ29udGV4dBAWEgwKCE1vdmVJdGVtEBcSFgoSR2V0RmllbGRUeXBlT3B0aW9uEBgSEwoPTmV3U2VsZWN0T3B0aW9uEB4SGwoXR2V0U2VsZWN0T3B0aW9uQ2VsbERhdGEQHxIWChJVcGRhdGVTZWxlY3RPcHRpb24QIBINCglDcmVhdGVSb3cQMhIKCgZHZXRSb3cQMxINCglEZWxldGVSb3cQNBIQCgxEdXBsaWNhdGVSb3cQNRILCgdHZXRDZWxsEEYSDgoKVXBkYXRlQ2VsbBBHEhoKFlVwZGF0ZVNlbGVjdE9wdGlvbkNlbGwQSBISCg5VcGRhdGVEYXRlQ2VsbBBQEhMKD0dldERhdGVDZWxsRGF0YRBa');

+ 37 - 63
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_type_option.pb.dart

@@ -348,41 +348,31 @@ enum SelectOptionCellChangesetPayload_OneOfDeleteOptionId {
 
 class SelectOptionCellChangesetPayload extends $pb.GeneratedMessage {
   static const $core.Map<$core.int, SelectOptionCellChangesetPayload_OneOfInsertOptionId> _SelectOptionCellChangesetPayload_OneOfInsertOptionIdByTag = {
-    4 : SelectOptionCellChangesetPayload_OneOfInsertOptionId.insertOptionId,
+    2 : SelectOptionCellChangesetPayload_OneOfInsertOptionId.insertOptionId,
     0 : SelectOptionCellChangesetPayload_OneOfInsertOptionId.notSet
   };
   static const $core.Map<$core.int, SelectOptionCellChangesetPayload_OneOfDeleteOptionId> _SelectOptionCellChangesetPayload_OneOfDeleteOptionIdByTag = {
-    5 : SelectOptionCellChangesetPayload_OneOfDeleteOptionId.deleteOptionId,
+    3 : SelectOptionCellChangesetPayload_OneOfDeleteOptionId.deleteOptionId,
     0 : SelectOptionCellChangesetPayload_OneOfDeleteOptionId.notSet
   };
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SelectOptionCellChangesetPayload', createEmptyInstance: create)
-    ..oo(0, [4])
-    ..oo(1, [5])
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowId')
-    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
-    ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'insertOptionId')
-    ..aOS(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deleteOptionId')
+    ..oo(0, [2])
+    ..oo(1, [3])
+    ..aOM<$0.CellIdentifierPayload>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'cellIdentifier', subBuilder: $0.CellIdentifierPayload.create)
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'insertOptionId')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deleteOptionId')
     ..hasRequiredFields = false
   ;
 
   SelectOptionCellChangesetPayload._() : super();
   factory SelectOptionCellChangesetPayload({
-    $core.String? gridId,
-    $core.String? rowId,
-    $core.String? fieldId,
+    $0.CellIdentifierPayload? cellIdentifier,
     $core.String? insertOptionId,
     $core.String? deleteOptionId,
   }) {
     final _result = create();
-    if (gridId != null) {
-      _result.gridId = gridId;
-    }
-    if (rowId != null) {
-      _result.rowId = rowId;
-    }
-    if (fieldId != null) {
-      _result.fieldId = fieldId;
+    if (cellIdentifier != null) {
+      _result.cellIdentifier = cellIdentifier;
     }
     if (insertOptionId != null) {
       _result.insertOptionId = insertOptionId;
@@ -420,60 +410,44 @@ class SelectOptionCellChangesetPayload extends $pb.GeneratedMessage {
   void clearOneOfDeleteOptionId() => clearField($_whichOneof(1));
 
   @$pb.TagNumber(1)
-  $core.String get gridId => $_getSZ(0);
+  $0.CellIdentifierPayload get cellIdentifier => $_getN(0);
   @$pb.TagNumber(1)
-  set gridId($core.String v) { $_setString(0, v); }
+  set cellIdentifier($0.CellIdentifierPayload v) { setField(1, v); }
   @$pb.TagNumber(1)
-  $core.bool hasGridId() => $_has(0);
+  $core.bool hasCellIdentifier() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearCellIdentifier() => clearField(1);
   @$pb.TagNumber(1)
-  void clearGridId() => clearField(1);
+  $0.CellIdentifierPayload ensureCellIdentifier() => $_ensure(0);
 
   @$pb.TagNumber(2)
-  $core.String get rowId => $_getSZ(1);
+  $core.String get insertOptionId => $_getSZ(1);
   @$pb.TagNumber(2)
-  set rowId($core.String v) { $_setString(1, v); }
+  set insertOptionId($core.String v) { $_setString(1, v); }
   @$pb.TagNumber(2)
-  $core.bool hasRowId() => $_has(1);
+  $core.bool hasInsertOptionId() => $_has(1);
   @$pb.TagNumber(2)
-  void clearRowId() => clearField(2);
+  void clearInsertOptionId() => clearField(2);
 
   @$pb.TagNumber(3)
-  $core.String get fieldId => $_getSZ(2);
+  $core.String get deleteOptionId => $_getSZ(2);
   @$pb.TagNumber(3)
-  set fieldId($core.String v) { $_setString(2, v); }
+  set deleteOptionId($core.String v) { $_setString(2, v); }
   @$pb.TagNumber(3)
-  $core.bool hasFieldId() => $_has(2);
+  $core.bool hasDeleteOptionId() => $_has(2);
   @$pb.TagNumber(3)
-  void clearFieldId() => clearField(3);
-
-  @$pb.TagNumber(4)
-  $core.String get insertOptionId => $_getSZ(3);
-  @$pb.TagNumber(4)
-  set insertOptionId($core.String v) { $_setString(3, v); }
-  @$pb.TagNumber(4)
-  $core.bool hasInsertOptionId() => $_has(3);
-  @$pb.TagNumber(4)
-  void clearInsertOptionId() => clearField(4);
-
-  @$pb.TagNumber(5)
-  $core.String get deleteOptionId => $_getSZ(4);
-  @$pb.TagNumber(5)
-  set deleteOptionId($core.String v) { $_setString(4, v); }
-  @$pb.TagNumber(5)
-  $core.bool hasDeleteOptionId() => $_has(4);
-  @$pb.TagNumber(5)
-  void clearDeleteOptionId() => clearField(5);
+  void clearDeleteOptionId() => clearField(3);
 }
 
-class SelectOptionContext extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SelectOptionContext', createEmptyInstance: create)
+class SelectOptionCellData extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SelectOptionCellData', createEmptyInstance: create)
     ..pc<SelectOption>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'options', $pb.PbFieldType.PM, subBuilder: SelectOption.create)
     ..pc<SelectOption>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'selectOptions', $pb.PbFieldType.PM, subBuilder: SelectOption.create)
     ..hasRequiredFields = false
   ;
 
-  SelectOptionContext._() : super();
-  factory SelectOptionContext({
+  SelectOptionCellData._() : super();
+  factory SelectOptionCellData({
     $core.Iterable<SelectOption>? options,
     $core.Iterable<SelectOption>? selectOptions,
   }) {
@@ -486,26 +460,26 @@ class SelectOptionContext extends $pb.GeneratedMessage {
     }
     return _result;
   }
-  factory SelectOptionContext.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory SelectOptionContext.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory SelectOptionCellData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory SelectOptionCellData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
   'Will be removed in next major version')
-  SelectOptionContext clone() => SelectOptionContext()..mergeFromMessage(this);
+  SelectOptionCellData clone() => SelectOptionCellData()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  SelectOptionContext copyWith(void Function(SelectOptionContext) updates) => super.copyWith((message) => updates(message as SelectOptionContext)) as SelectOptionContext; // ignore: deprecated_member_use
+  SelectOptionCellData copyWith(void Function(SelectOptionCellData) updates) => super.copyWith((message) => updates(message as SelectOptionCellData)) as SelectOptionCellData; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static SelectOptionContext create() => SelectOptionContext._();
-  SelectOptionContext createEmptyInstance() => create();
-  static $pb.PbList<SelectOptionContext> createRepeated() => $pb.PbList<SelectOptionContext>();
+  static SelectOptionCellData create() => SelectOptionCellData._();
+  SelectOptionCellData createEmptyInstance() => create();
+  static $pb.PbList<SelectOptionCellData> createRepeated() => $pb.PbList<SelectOptionCellData>();
   @$core.pragma('dart2js:noInline')
-  static SelectOptionContext getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SelectOptionContext>(create);
-  static SelectOptionContext? _defaultInstance;
+  static SelectOptionCellData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SelectOptionCellData>(create);
+  static SelectOptionCellData? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.List<SelectOption> get options => $_getList(0);

+ 9 - 11
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_type_option.pbjson.dart

@@ -82,11 +82,9 @@ final $typed_data.Uint8List selectOptionChangesetPayloadDescriptor = $convert.ba
 const SelectOptionCellChangesetPayload$json = const {
   '1': 'SelectOptionCellChangesetPayload',
   '2': const [
-    const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
-    const {'1': 'row_id', '3': 2, '4': 1, '5': 9, '10': 'rowId'},
-    const {'1': 'field_id', '3': 3, '4': 1, '5': 9, '10': 'fieldId'},
-    const {'1': 'insert_option_id', '3': 4, '4': 1, '5': 9, '9': 0, '10': 'insertOptionId'},
-    const {'1': 'delete_option_id', '3': 5, '4': 1, '5': 9, '9': 1, '10': 'deleteOptionId'},
+    const {'1': 'cell_identifier', '3': 1, '4': 1, '5': 11, '6': '.CellIdentifierPayload', '10': 'cellIdentifier'},
+    const {'1': 'insert_option_id', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'insertOptionId'},
+    const {'1': 'delete_option_id', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'deleteOptionId'},
   ],
   '8': const [
     const {'1': 'one_of_insert_option_id'},
@@ -95,15 +93,15 @@ const SelectOptionCellChangesetPayload$json = const {
 };
 
 /// Descriptor for `SelectOptionCellChangesetPayload`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List selectOptionCellChangesetPayloadDescriptor = $convert.base64Decode('CiBTZWxlY3RPcHRpb25DZWxsQ2hhbmdlc2V0UGF5bG9hZBIXCgdncmlkX2lkGAEgASgJUgZncmlkSWQSFQoGcm93X2lkGAIgASgJUgVyb3dJZBIZCghmaWVsZF9pZBgDIAEoCVIHZmllbGRJZBIqChBpbnNlcnRfb3B0aW9uX2lkGAQgASgJSABSDmluc2VydE9wdGlvbklkEioKEGRlbGV0ZV9vcHRpb25faWQYBSABKAlIAVIOZGVsZXRlT3B0aW9uSWRCGQoXb25lX29mX2luc2VydF9vcHRpb25faWRCGQoXb25lX29mX2RlbGV0ZV9vcHRpb25faWQ=');
-@$core.Deprecated('Use selectOptionContextDescriptor instead')
-const SelectOptionContext$json = const {
-  '1': 'SelectOptionContext',
+final $typed_data.Uint8List selectOptionCellChangesetPayloadDescriptor = $convert.base64Decode('CiBTZWxlY3RPcHRpb25DZWxsQ2hhbmdlc2V0UGF5bG9hZBI/Cg9jZWxsX2lkZW50aWZpZXIYASABKAsyFi5DZWxsSWRlbnRpZmllclBheWxvYWRSDmNlbGxJZGVudGlmaWVyEioKEGluc2VydF9vcHRpb25faWQYAiABKAlIAFIOaW5zZXJ0T3B0aW9uSWQSKgoQZGVsZXRlX29wdGlvbl9pZBgDIAEoCUgBUg5kZWxldGVPcHRpb25JZEIZChdvbmVfb2ZfaW5zZXJ0X29wdGlvbl9pZEIZChdvbmVfb2ZfZGVsZXRlX29wdGlvbl9pZA==');
+@$core.Deprecated('Use selectOptionCellDataDescriptor instead')
+const SelectOptionCellData$json = const {
+  '1': 'SelectOptionCellData',
   '2': const [
     const {'1': 'options', '3': 1, '4': 3, '5': 11, '6': '.SelectOption', '10': 'options'},
     const {'1': 'select_options', '3': 2, '4': 3, '5': 11, '6': '.SelectOption', '10': 'selectOptions'},
   ],
 };
 
-/// Descriptor for `SelectOptionContext`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List selectOptionContextDescriptor = $convert.base64Decode('ChNTZWxlY3RPcHRpb25Db250ZXh0EicKB29wdGlvbnMYASADKAsyDS5TZWxlY3RPcHRpb25SB29wdGlvbnMSNAoOc2VsZWN0X29wdGlvbnMYAiADKAsyDS5TZWxlY3RPcHRpb25SDXNlbGVjdE9wdGlvbnM=');
+/// Descriptor for `SelectOptionCellData`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List selectOptionCellDataDescriptor = $convert.base64Decode('ChRTZWxlY3RPcHRpb25DZWxsRGF0YRInCgdvcHRpb25zGAEgAygLMg0uU2VsZWN0T3B0aW9uUgdvcHRpb25zEjQKDnNlbGVjdF9vcHRpb25zGAIgAygLMg0uU2VsZWN0T3B0aW9uUg1zZWxlY3RPcHRpb25z');

+ 96 - 48
frontend/rust-lib/flowy-grid/src/event_handler.rs

@@ -70,6 +70,19 @@ pub(crate) async fn insert_field_handler(
     Ok(())
 }
 
+#[tracing::instrument(level = "debug", skip(data, manager), err)]
+pub(crate) async fn update_field_type_option_handler(
+    data: Data<UpdateFieldTypeOptionPayload>,
+    manager: AppData<Arc<GridManager>>,
+) -> Result<(), FlowyError> {
+    let params: UpdateFieldTypeOptionParams = data.into_inner().try_into()?;
+    let editor = manager.get_grid_editor(&params.grid_id)?;
+    let _ = editor
+        .update_field_type_option(&params.grid_id, &params.field_id, params.type_option_data)
+        .await?;
+    Ok(())
+}
+
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
 pub(crate) async fn delete_field_handler(
     data: Data<FieldIdentifierPayload>,
@@ -87,20 +100,15 @@ pub(crate) async fn switch_to_field_handler(
     manager: AppData<Arc<GridManager>>,
 ) -> DataResult<EditFieldContext, FlowyError> {
     let params: EditFieldParams = data.into_inner().try_into()?;
+    if params.field_id.is_none() {
+        return Err(ErrorCode::FieldIdIsEmpty.into());
+    }
+    let field_id = params.field_id.unwrap();
     let editor = manager.get_grid_editor(&params.grid_id)?;
-    editor
-        .switch_to_field_type(&params.field_id, &params.field_type)
-        .await?;
-
-    let field_meta = editor.get_field_meta(&params.field_id).await;
-    let edit_context = make_field_edit_context(
-        &params.grid_id,
-        Some(params.field_id),
-        params.field_type,
-        editor,
-        field_meta,
-    )
-    .await?;
+    editor.switch_to_field_type(&field_id, &params.field_type).await?;
+    let field_meta = editor.get_field_meta(&field_id).await;
+    let edit_context =
+        make_edit_field_context(&params.grid_id, Some(field_id), params.field_type, editor, field_meta).await?;
     data_result(edit_context)
 }
 
@@ -117,17 +125,33 @@ pub(crate) async fn duplicate_field_handler(
 
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
 pub(crate) async fn get_field_context_handler(
-    data: Data<GetEditFieldContextPayload>,
+    data: Data<EditFieldPayload>,
     manager: AppData<Arc<GridManager>>,
 ) -> DataResult<EditFieldContext, FlowyError> {
-    let params = data.into_inner();
+    let params: EditFieldParams = data.into_inner().try_into()?;
     let editor = manager.get_grid_editor(&params.grid_id)?;
     let edit_context =
-        make_field_edit_context(&params.grid_id, params.field_id, params.field_type, editor, None).await?;
+        make_edit_field_context(&params.grid_id, params.field_id, params.field_type, editor, None).await?;
 
     data_result(edit_context)
 }
 
+#[tracing::instrument(level = "debug", skip(data, manager), err)]
+pub(crate) async fn get_field_type_option_data_handler(
+    data: Data<EditFieldPayload>,
+    manager: AppData<Arc<GridManager>>,
+) -> DataResult<FieldTypeOptionData, FlowyError> {
+    let params: EditFieldParams = data.into_inner().try_into()?;
+    let editor = manager.get_grid_editor(&params.grid_id)?;
+    let field_meta = get_or_create_field_meta(params.field_id, &params.field_type, editor).await?;
+    let type_option_data = get_type_option_data(&field_meta, &field_meta.field_type).await?;
+
+    data_result(FieldTypeOptionData {
+        field_id: field_meta.id.clone(),
+        type_option_data,
+    })
+}
+
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
 pub(crate) async fn move_item_handler(
     data: Data<MoveItemPayload>,
@@ -139,7 +163,7 @@ pub(crate) async fn move_item_handler(
     Ok(())
 }
 
-async fn make_field_edit_context(
+async fn make_edit_field_context(
     grid_id: &str,
     field_id: Option<String>,
     field_type: FieldType,
@@ -147,12 +171,7 @@ async fn make_field_edit_context(
     field_meta: Option<FieldMeta>,
 ) -> FlowyResult<EditFieldContext> {
     let field_meta = field_meta.unwrap_or(get_or_create_field_meta(field_id, &field_type, editor).await?);
-    let s = field_meta
-        .get_type_option_str(None)
-        .unwrap_or_else(|| default_type_option_builder_from_type(&field_type).entry().json_str());
-
-    let builder = type_option_builder_from_json_str(&s, &field_meta.field_type);
-    let type_option_data = builder.entry().protobuf_bytes().to_vec();
+    let type_option_data = get_type_option_data(&field_meta, &field_type).await?;
     let field: Field = field_meta.into();
     Ok(EditFieldContext {
         grid_id: grid_id.to_string(),
@@ -161,6 +180,16 @@ async fn make_field_edit_context(
     })
 }
 
+async fn get_type_option_data(field_meta: &FieldMeta, field_type: &FieldType) -> FlowyResult<Vec<u8>> {
+    let s = field_meta
+        .get_type_option_str(field_type)
+        .unwrap_or_else(|| default_type_option_builder_from_type(field_type).entry().json_str());
+    let builder = type_option_builder_from_json_str(&s, &field_meta.field_type);
+    let type_option_data = builder.entry().protobuf_bytes().to_vec();
+
+    Ok(type_option_data)
+}
+
 async fn get_or_create_field_meta(
     field_id: Option<String>,
     field_type: &FieldType,
@@ -221,7 +250,7 @@ pub(crate) async fn create_row_handler(
     Ok(())
 }
 
-#[tracing::instrument(level = "debug", skip_all, err)]
+// #[tracing::instrument(level = "debug", skip_all, err)]
 pub(crate) async fn get_cell_handler(
     data: Data<CellIdentifierPayload>,
     manager: AppData<Arc<GridManager>>,
@@ -229,7 +258,7 @@ pub(crate) async fn get_cell_handler(
     let params: CellIdentifier = data.into_inner().try_into()?;
     let editor = manager.get_grid_editor(&params.grid_id)?;
     match editor.get_cell(&params).await {
-        None => data_result(Cell::new(&params.field_id, "".to_owned())),
+        None => data_result(Cell::empty(&params.field_id)),
         Some(cell) => data_result(cell),
     }
 }
@@ -245,6 +274,27 @@ pub(crate) async fn update_cell_handler(
     Ok(())
 }
 
+#[tracing::instrument(level = "debug", skip(data, manager), err)]
+pub(crate) async fn get_date_cell_data_handler(
+    data: Data<CellIdentifierPayload>,
+    manager: AppData<Arc<GridManager>>,
+) -> DataResult<DateCellData, FlowyError> {
+    let params: CellIdentifier = data.into_inner().try_into()?;
+    let editor = manager.get_grid_editor(&params.grid_id)?;
+    match editor.get_field_meta(&params.field_id).await {
+        None => {
+            tracing::error!("Can't find the corresponding field with id: {}", params.field_id);
+            data_result(DateCellData::default())
+        }
+        Some(field_meta) => {
+            let cell_meta = editor.get_cell_meta(&params.row_id, &params.field_id).await?;
+            let type_option = DateTypeOption::from(&field_meta);
+            let date_cell_data = type_option.make_date_cell_data(&cell_meta)?;
+            data_result(date_cell_data)
+        }
+    }
+}
+
 #[tracing::instrument(level = "debug", skip_all, err)]
 pub(crate) async fn new_select_option_handler(
     data: Data<CreateSelectOptionPayload>,
@@ -272,10 +322,10 @@ pub(crate) async fn update_select_option_handler(
 
     if let Some(mut field_meta) = editor.get_field_meta(&changeset.cell_identifier.field_id).await {
         let mut type_option = select_option_operation(&field_meta)?;
-        let mut cell_data = None;
+        let mut cell_content_changeset = None;
 
         if let Some(option) = changeset.insert_option {
-            cell_data = Some(SelectOptionCellChangeset::from_insert(&option.id).cell_data());
+            cell_content_changeset = Some(SelectOptionCellContentChangeset::from_insert(&option.id).to_str());
             type_option.insert_option(option);
         }
 
@@ -284,7 +334,7 @@ pub(crate) async fn update_select_option_handler(
         }
 
         if let Some(option) = changeset.delete_option {
-            cell_data = Some(SelectOptionCellChangeset::from_delete(&option.id).cell_data());
+            cell_content_changeset = Some(SelectOptionCellContentChangeset::from_delete(&option.id).to_str());
             type_option.delete_option(option);
         }
 
@@ -295,54 +345,52 @@ pub(crate) async fn update_select_option_handler(
             grid_id: changeset.cell_identifier.grid_id,
             row_id: changeset.cell_identifier.row_id,
             field_id: changeset.cell_identifier.field_id,
-            data: cell_data,
+            cell_content_changeset,
         };
         let _ = editor.update_cell(changeset).await?;
     }
     Ok(())
 }
-//
-// #[tracing::instrument(level = "debug", skip_all, err)]
-// pub(crate) async fn update_date_option_handler(
-//     data: Data<SelectOptionCellChangesetPayload>,
-//     manager: AppData<Arc<GridManager>>,
-// ) -> Result<(), FlowyError> {
-//     let params: SelectOptionCellChangesetParams = data.into_inner().try_into()?;
-//     let editor = manager.get_grid_editor(&params.grid_id)?;
-//     let changeset: CellChangeset = params.into();
-//     let _ = editor.update_cell(changeset).await?;
-//     Ok(())
-// }
 
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
 pub(crate) async fn get_select_option_handler(
     data: Data<CellIdentifierPayload>,
     manager: AppData<Arc<GridManager>>,
-) -> DataResult<SelectOptionContext, FlowyError> {
+) -> DataResult<SelectOptionCellData, FlowyError> {
     let params: CellIdentifier = data.into_inner().try_into()?;
     let editor = manager.get_grid_editor(&params.grid_id)?;
     match editor.get_field_meta(&params.field_id).await {
         None => {
             tracing::error!("Can't find the corresponding field with id: {}", params.field_id);
-            data_result(SelectOptionContext::default())
+            data_result(SelectOptionCellData::default())
         }
         Some(field_meta) => {
             let cell_meta = editor.get_cell_meta(&params.row_id, &params.field_id).await?;
             let type_option = select_option_operation(&field_meta)?;
-            let option_context = type_option.option_context(&cell_meta);
+            let option_context = type_option.select_option_cell_data(&cell_meta);
             data_result(option_context)
         }
     }
 }
 
 #[tracing::instrument(level = "debug", skip_all, err)]
-pub(crate) async fn update_cell_select_option_handler(
+pub(crate) async fn update_select_option_cell_handler(
     data: Data<SelectOptionCellChangesetPayload>,
     manager: AppData<Arc<GridManager>>,
 ) -> Result<(), FlowyError> {
     let params: SelectOptionCellChangesetParams = data.into_inner().try_into()?;
-    let editor = manager.get_grid_editor(&params.grid_id)?;
-    let changeset: CellChangeset = params.into();
-    let _ = editor.update_cell(changeset).await?;
+    let editor = manager.get_grid_editor(&params.cell_identifier.grid_id)?;
+    let _ = editor.update_cell(params.into()).await?;
+    Ok(())
+}
+
+#[tracing::instrument(level = "debug", skip_all, err)]
+pub(crate) async fn update_date_cell_handler(
+    data: Data<DateChangesetPayload>,
+    manager: AppData<Arc<GridManager>>,
+) -> Result<(), FlowyError> {
+    let params: DateChangesetParams = data.into_inner().try_into()?;
+    let editor = manager.get_grid_editor(&params.cell_identifier.grid_id)?;
+    let _ = editor.update_cell(params.into()).await?;
     Ok(())
 }

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

@@ -14,11 +14,13 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
         .event(GridEvent::GetFields, get_fields_handler)
         .event(GridEvent::UpdateField, update_field_handler)
         .event(GridEvent::InsertField, insert_field_handler)
+        .event(GridEvent::UpdateFieldTypeOption, update_field_type_option_handler)
         .event(GridEvent::DeleteField, delete_field_handler)
         .event(GridEvent::SwitchToField, switch_to_field_handler)
         .event(GridEvent::DuplicateField, duplicate_field_handler)
         .event(GridEvent::GetEditFieldContext, get_field_context_handler)
         .event(GridEvent::MoveItem, move_item_handler)
+        .event(GridEvent::GetFieldTypeOption, get_field_type_option_data_handler)
         // Row
         .event(GridEvent::CreateRow, create_row_handler)
         .event(GridEvent::GetRow, get_row_handler)
@@ -27,11 +29,14 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
         // Cell
         .event(GridEvent::GetCell, get_cell_handler)
         .event(GridEvent::UpdateCell, update_cell_handler)
+        .event(GridEvent::GetDateCellData, get_date_cell_data_handler)
         // SelectOption
         .event(GridEvent::NewSelectOption, new_select_option_handler)
         .event(GridEvent::UpdateSelectOption, update_select_option_handler)
-        .event(GridEvent::GetSelectOptionContext, get_select_option_handler)
-        .event(GridEvent::UpdateCellSelectOption, update_cell_select_option_handler);
+        .event(GridEvent::GetSelectOptionCellData, get_select_option_handler)
+        .event(GridEvent::UpdateSelectOptionCell, update_select_option_cell_handler)
+        // Date
+        .event(GridEvent::UpdateDateCell, update_date_cell_handler);
 
     module
 }
@@ -51,29 +56,35 @@ pub enum GridEvent {
     #[event(input = "FieldChangesetPayload")]
     UpdateField = 11,
 
+    #[event(input = "UpdateFieldTypeOptionPayload")]
+    UpdateFieldTypeOption = 12,
+
     #[event(input = "InsertFieldPayload")]
-    InsertField = 12,
+    InsertField = 13,
 
     #[event(input = "FieldIdentifierPayload")]
-    DeleteField = 13,
+    DeleteField = 14,
 
     #[event(input = "EditFieldPayload", output = "EditFieldContext")]
-    SwitchToField = 14,
+    SwitchToField = 20,
 
     #[event(input = "FieldIdentifierPayload")]
-    DuplicateField = 15,
+    DuplicateField = 21,
 
-    #[event(input = "GetEditFieldContextPayload", output = "EditFieldContext")]
-    GetEditFieldContext = 16,
+    #[event(input = "EditFieldPayload", output = "EditFieldContext")]
+    GetEditFieldContext = 22,
 
     #[event(input = "MoveItemPayload")]
-    MoveItem = 17,
+    MoveItem = 23,
+
+    #[event(input = "EditFieldPayload", output = "FieldTypeOptionData")]
+    GetFieldTypeOption = 24,
 
     #[event(input = "CreateSelectOptionPayload", output = "SelectOption")]
     NewSelectOption = 30,
 
-    #[event(input = "CellIdentifierPayload", output = "SelectOptionContext")]
-    GetSelectOptionContext = 31,
+    #[event(input = "CellIdentifierPayload", output = "SelectOptionCellData")]
+    GetSelectOptionCellData = 31,
 
     #[event(input = "SelectOptionChangesetPayload")]
     UpdateSelectOption = 32,
@@ -97,5 +108,11 @@ pub enum GridEvent {
     UpdateCell = 71,
 
     #[event(input = "SelectOptionCellChangesetPayload")]
-    UpdateCellSelectOption = 72,
+    UpdateSelectOptionCell = 72,
+
+    #[event(input = "DateChangesetPayload")]
+    UpdateDateCell = 80,
+
+    #[event(input = "CellIdentifierPayload", output = "DateCellData")]
+    GetDateCellData = 90,
 }

+ 587 - 7
frontend/rust-lib/flowy-grid/src/protobuf/model/date_type_option.rs

@@ -237,6 +237,579 @@ impl ::protobuf::reflect::ProtobufValue for DateTypeOption {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct DateCellData {
+    // message fields
+    pub date: ::std::string::String,
+    pub time: ::std::string::String,
+    pub timestamp: i64,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a DateCellData {
+    fn default() -> &'a DateCellData {
+        <DateCellData as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl DateCellData {
+    pub fn new() -> DateCellData {
+        ::std::default::Default::default()
+    }
+
+    // string date = 1;
+
+
+    pub fn get_date(&self) -> &str {
+        &self.date
+    }
+    pub fn clear_date(&mut self) {
+        self.date.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_date(&mut self, v: ::std::string::String) {
+        self.date = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_date(&mut self) -> &mut ::std::string::String {
+        &mut self.date
+    }
+
+    // Take field
+    pub fn take_date(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.date, ::std::string::String::new())
+    }
+
+    // string time = 2;
+
+
+    pub fn get_time(&self) -> &str {
+        &self.time
+    }
+    pub fn clear_time(&mut self) {
+        self.time.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_time(&mut self, v: ::std::string::String) {
+        self.time = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_time(&mut self) -> &mut ::std::string::String {
+        &mut self.time
+    }
+
+    // Take field
+    pub fn take_time(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.time, ::std::string::String::new())
+    }
+
+    // int64 timestamp = 3;
+
+
+    pub fn get_timestamp(&self) -> i64 {
+        self.timestamp
+    }
+    pub fn clear_timestamp(&mut self) {
+        self.timestamp = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_timestamp(&mut self, v: i64) {
+        self.timestamp = v;
+    }
+}
+
+impl ::protobuf::Message for DateCellData {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.date)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.time)?;
+                },
+                3 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int64()?;
+                    self.timestamp = tmp;
+                },
+                _ => {
+                    ::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.date.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.date);
+        }
+        if !self.time.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.time);
+        }
+        if self.timestamp != 0 {
+            my_size += ::protobuf::rt::value_size(3, self.timestamp, ::protobuf::wire_format::WireTypeVarint);
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.date.is_empty() {
+            os.write_string(1, &self.date)?;
+        }
+        if !self.time.is_empty() {
+            os.write_string(2, &self.time)?;
+        }
+        if self.timestamp != 0 {
+            os.write_int64(3, self.timestamp)?;
+        }
+        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() -> DateCellData {
+        DateCellData::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>(
+                "date",
+                |m: &DateCellData| { &m.date },
+                |m: &mut DateCellData| { &mut m.date },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "time",
+                |m: &DateCellData| { &m.time },
+                |m: &mut DateCellData| { &mut m.time },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>(
+                "timestamp",
+                |m: &DateCellData| { &m.timestamp },
+                |m: &mut DateCellData| { &mut m.timestamp },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<DateCellData>(
+                "DateCellData",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static DateCellData {
+        static instance: ::protobuf::rt::LazyV2<DateCellData> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(DateCellData::new)
+    }
+}
+
+impl ::protobuf::Clear for DateCellData {
+    fn clear(&mut self) {
+        self.date.clear();
+        self.time.clear();
+        self.timestamp = 0;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for DateCellData {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for DateCellData {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct DateChangesetPayload {
+    // message fields
+    pub cell_identifier: ::protobuf::SingularPtrField<super::cell_entities::CellIdentifierPayload>,
+    // message oneof groups
+    pub one_of_date: ::std::option::Option<DateChangesetPayload_oneof_one_of_date>,
+    pub one_of_time: ::std::option::Option<DateChangesetPayload_oneof_one_of_time>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a DateChangesetPayload {
+    fn default() -> &'a DateChangesetPayload {
+        <DateChangesetPayload as ::protobuf::Message>::default_instance()
+    }
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum DateChangesetPayload_oneof_one_of_date {
+    date(::std::string::String),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum DateChangesetPayload_oneof_one_of_time {
+    time(::std::string::String),
+}
+
+impl DateChangesetPayload {
+    pub fn new() -> DateChangesetPayload {
+        ::std::default::Default::default()
+    }
+
+    // .CellIdentifierPayload cell_identifier = 1;
+
+
+    pub fn get_cell_identifier(&self) -> &super::cell_entities::CellIdentifierPayload {
+        self.cell_identifier.as_ref().unwrap_or_else(|| <super::cell_entities::CellIdentifierPayload as ::protobuf::Message>::default_instance())
+    }
+    pub fn clear_cell_identifier(&mut self) {
+        self.cell_identifier.clear();
+    }
+
+    pub fn has_cell_identifier(&self) -> bool {
+        self.cell_identifier.is_some()
+    }
+
+    // Param is passed by value, moved
+    pub fn set_cell_identifier(&mut self, v: super::cell_entities::CellIdentifierPayload) {
+        self.cell_identifier = ::protobuf::SingularPtrField::some(v);
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_cell_identifier(&mut self) -> &mut super::cell_entities::CellIdentifierPayload {
+        if self.cell_identifier.is_none() {
+            self.cell_identifier.set_default();
+        }
+        self.cell_identifier.as_mut().unwrap()
+    }
+
+    // Take field
+    pub fn take_cell_identifier(&mut self) -> super::cell_entities::CellIdentifierPayload {
+        self.cell_identifier.take().unwrap_or_else(|| super::cell_entities::CellIdentifierPayload::new())
+    }
+
+    // string date = 2;
+
+
+    pub fn get_date(&self) -> &str {
+        match self.one_of_date {
+            ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_date::date(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_date(&mut self) {
+        self.one_of_date = ::std::option::Option::None;
+    }
+
+    pub fn has_date(&self) -> bool {
+        match self.one_of_date {
+            ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_date::date(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_date(&mut self, v: ::std::string::String) {
+        self.one_of_date = ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_date::date(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_date(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_date::date(_)) = self.one_of_date {
+        } else {
+            self.one_of_date = ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_date::date(::std::string::String::new()));
+        }
+        match self.one_of_date {
+            ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_date::date(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_date(&mut self) -> ::std::string::String {
+        if self.has_date() {
+            match self.one_of_date.take() {
+                ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_date::date(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+
+    // string time = 3;
+
+
+    pub fn get_time(&self) -> &str {
+        match self.one_of_time {
+            ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_time::time(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_time(&mut self) {
+        self.one_of_time = ::std::option::Option::None;
+    }
+
+    pub fn has_time(&self) -> bool {
+        match self.one_of_time {
+            ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_time::time(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_time(&mut self, v: ::std::string::String) {
+        self.one_of_time = ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_time::time(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_time(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_time::time(_)) = self.one_of_time {
+        } else {
+            self.one_of_time = ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_time::time(::std::string::String::new()));
+        }
+        match self.one_of_time {
+            ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_time::time(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_time(&mut self) -> ::std::string::String {
+        if self.has_time() {
+            match self.one_of_time.take() {
+                ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_time::time(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+}
+
+impl ::protobuf::Message for DateChangesetPayload {
+    fn is_initialized(&self) -> bool {
+        for v in &self.cell_identifier {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.cell_identifier)?;
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_date = ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_date::date(is.read_string()?));
+                },
+                3 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_time = ::std::option::Option::Some(DateChangesetPayload_oneof_one_of_time::time(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 let Some(ref v) = self.cell_identifier.as_ref() {
+            let len = v.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_date {
+            match v {
+                &DateChangesetPayload_oneof_one_of_date::date(ref v) => {
+                    my_size += ::protobuf::rt::string_size(2, &v);
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_time {
+            match v {
+                &DateChangesetPayload_oneof_one_of_time::time(ref v) => {
+                    my_size += ::protobuf::rt::string_size(3, &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 let Some(ref v) = self.cell_identifier.as_ref() {
+            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_date {
+            match v {
+                &DateChangesetPayload_oneof_one_of_date::date(ref v) => {
+                    os.write_string(2, v)?;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_time {
+            match v {
+                &DateChangesetPayload_oneof_one_of_time::time(ref v) => {
+                    os.write_string(3, 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() -> DateChangesetPayload {
+        DateChangesetPayload::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<super::cell_entities::CellIdentifierPayload>>(
+                "cell_identifier",
+                |m: &DateChangesetPayload| { &m.cell_identifier },
+                |m: &mut DateChangesetPayload| { &mut m.cell_identifier },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "date",
+                DateChangesetPayload::has_date,
+                DateChangesetPayload::get_date,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "time",
+                DateChangesetPayload::has_time,
+                DateChangesetPayload::get_time,
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<DateChangesetPayload>(
+                "DateChangesetPayload",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static DateChangesetPayload {
+        static instance: ::protobuf::rt::LazyV2<DateChangesetPayload> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(DateChangesetPayload::new)
+    }
+}
+
+impl ::protobuf::Clear for DateChangesetPayload {
+    fn clear(&mut self) {
+        self.cell_identifier.clear();
+        self.one_of_date = ::std::option::Option::None;
+        self.one_of_time = ::std::option::Option::None;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for DateChangesetPayload {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for DateChangesetPayload {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(Clone,PartialEq,Eq,Debug,Hash)]
 pub enum DateFormat {
     Local = 0,
@@ -344,13 +917,20 @@ impl ::protobuf::reflect::ProtobufValue for TimeFormat {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x16date_type_option.proto\"\x8f\x01\n\x0eDateTypeOption\x12,\n\x0bdat\
-    e_format\x18\x01\x20\x01(\x0e2\x0b.DateFormatR\ndateFormat\x12,\n\x0btim\
-    e_format\x18\x02\x20\x01(\x0e2\x0b.TimeFormatR\ntimeFormat\x12!\n\x0cinc\
-    lude_time\x18\x03\x20\x01(\x08R\x0bincludeTime*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\
+    \n\x16date_type_option.proto\x1a\x13cell_entities.proto\"\x8f\x01\n\x0eD\
+    ateTypeOption\x12,\n\x0bdate_format\x18\x01\x20\x01(\x0e2\x0b.DateFormat\
+    R\ndateFormat\x12,\n\x0btime_format\x18\x02\x20\x01(\x0e2\x0b.TimeFormat\
+    R\ntimeFormat\x12!\n\x0cinclude_time\x18\x03\x20\x01(\x08R\x0bincludeTim\
+    e\"T\n\x0cDateCellData\x12\x12\n\x04date\x18\x01\x20\x01(\tR\x04date\x12\
+    \x12\n\x04time\x18\x02\x20\x01(\tR\x04time\x12\x1c\n\ttimestamp\x18\x03\
+    \x20\x01(\x03R\ttimestamp\"\xa1\x01\n\x14DateChangesetPayload\x12?\n\x0f\
+    cell_identifier\x18\x01\x20\x01(\x0b2\x16.CellIdentifierPayloadR\x0ecell\
+    Identifier\x12\x14\n\x04date\x18\x02\x20\x01(\tH\0R\x04date\x12\x14\n\
+    \x04time\x18\x03\x20\x01(\tH\x01R\x04timeB\r\n\x0bone_of_dateB\r\n\x0bon\
+    e_of_time*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\x06pro\
+    to3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -29,14 +29,16 @@ pub enum GridEvent {
     GetGridBlocks = 1,
     GetFields = 10,
     UpdateField = 11,
-    InsertField = 12,
-    DeleteField = 13,
-    SwitchToField = 14,
-    DuplicateField = 15,
-    GetEditFieldContext = 16,
-    MoveItem = 17,
+    UpdateFieldTypeOption = 12,
+    InsertField = 13,
+    DeleteField = 14,
+    SwitchToField = 20,
+    DuplicateField = 21,
+    GetEditFieldContext = 22,
+    MoveItem = 23,
+    GetFieldTypeOption = 24,
     NewSelectOption = 30,
-    GetSelectOptionContext = 31,
+    GetSelectOptionCellData = 31,
     UpdateSelectOption = 32,
     CreateRow = 50,
     GetRow = 51,
@@ -44,7 +46,9 @@ pub enum GridEvent {
     DuplicateRow = 53,
     GetCell = 70,
     UpdateCell = 71,
-    UpdateCellSelectOption = 72,
+    UpdateSelectOptionCell = 72,
+    UpdateDateCell = 80,
+    GetDateCellData = 90,
 }
 
 impl ::protobuf::ProtobufEnum for GridEvent {
@@ -58,14 +62,16 @@ impl ::protobuf::ProtobufEnum for GridEvent {
             1 => ::std::option::Option::Some(GridEvent::GetGridBlocks),
             10 => ::std::option::Option::Some(GridEvent::GetFields),
             11 => ::std::option::Option::Some(GridEvent::UpdateField),
-            12 => ::std::option::Option::Some(GridEvent::InsertField),
-            13 => ::std::option::Option::Some(GridEvent::DeleteField),
-            14 => ::std::option::Option::Some(GridEvent::SwitchToField),
-            15 => ::std::option::Option::Some(GridEvent::DuplicateField),
-            16 => ::std::option::Option::Some(GridEvent::GetEditFieldContext),
-            17 => ::std::option::Option::Some(GridEvent::MoveItem),
+            12 => ::std::option::Option::Some(GridEvent::UpdateFieldTypeOption),
+            13 => ::std::option::Option::Some(GridEvent::InsertField),
+            14 => ::std::option::Option::Some(GridEvent::DeleteField),
+            20 => ::std::option::Option::Some(GridEvent::SwitchToField),
+            21 => ::std::option::Option::Some(GridEvent::DuplicateField),
+            22 => ::std::option::Option::Some(GridEvent::GetEditFieldContext),
+            23 => ::std::option::Option::Some(GridEvent::MoveItem),
+            24 => ::std::option::Option::Some(GridEvent::GetFieldTypeOption),
             30 => ::std::option::Option::Some(GridEvent::NewSelectOption),
-            31 => ::std::option::Option::Some(GridEvent::GetSelectOptionContext),
+            31 => ::std::option::Option::Some(GridEvent::GetSelectOptionCellData),
             32 => ::std::option::Option::Some(GridEvent::UpdateSelectOption),
             50 => ::std::option::Option::Some(GridEvent::CreateRow),
             51 => ::std::option::Option::Some(GridEvent::GetRow),
@@ -73,7 +79,9 @@ impl ::protobuf::ProtobufEnum for GridEvent {
             53 => ::std::option::Option::Some(GridEvent::DuplicateRow),
             70 => ::std::option::Option::Some(GridEvent::GetCell),
             71 => ::std::option::Option::Some(GridEvent::UpdateCell),
-            72 => ::std::option::Option::Some(GridEvent::UpdateCellSelectOption),
+            72 => ::std::option::Option::Some(GridEvent::UpdateSelectOptionCell),
+            80 => ::std::option::Option::Some(GridEvent::UpdateDateCell),
+            90 => ::std::option::Option::Some(GridEvent::GetDateCellData),
             _ => ::std::option::Option::None
         }
     }
@@ -84,14 +92,16 @@ impl ::protobuf::ProtobufEnum for GridEvent {
             GridEvent::GetGridBlocks,
             GridEvent::GetFields,
             GridEvent::UpdateField,
+            GridEvent::UpdateFieldTypeOption,
             GridEvent::InsertField,
             GridEvent::DeleteField,
             GridEvent::SwitchToField,
             GridEvent::DuplicateField,
             GridEvent::GetEditFieldContext,
             GridEvent::MoveItem,
+            GridEvent::GetFieldTypeOption,
             GridEvent::NewSelectOption,
-            GridEvent::GetSelectOptionContext,
+            GridEvent::GetSelectOptionCellData,
             GridEvent::UpdateSelectOption,
             GridEvent::CreateRow,
             GridEvent::GetRow,
@@ -99,7 +109,9 @@ impl ::protobuf::ProtobufEnum for GridEvent {
             GridEvent::DuplicateRow,
             GridEvent::GetCell,
             GridEvent::UpdateCell,
-            GridEvent::UpdateCellSelectOption,
+            GridEvent::UpdateSelectOptionCell,
+            GridEvent::UpdateDateCell,
+            GridEvent::GetDateCellData,
         ];
         values
     }
@@ -128,16 +140,18 @@ impl ::protobuf::reflect::ProtobufValue for GridEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0fevent_map.proto*\xfd\x02\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\
+    \n\x0fevent_map.proto*\xda\x03\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\
     \0\x12\x11\n\rGetGridBlocks\x10\x01\x12\r\n\tGetFields\x10\n\x12\x0f\n\
-    \x0bUpdateField\x10\x0b\x12\x0f\n\x0bInsertField\x10\x0c\x12\x0f\n\x0bDe\
-    leteField\x10\r\x12\x11\n\rSwitchToField\x10\x0e\x12\x12\n\x0eDuplicateF\
-    ield\x10\x0f\x12\x17\n\x13GetEditFieldContext\x10\x10\x12\x0c\n\x08MoveI\
-    tem\x10\x11\x12\x13\n\x0fNewSelectOption\x10\x1e\x12\x1a\n\x16GetSelectO\
-    ptionContext\x10\x1f\x12\x16\n\x12UpdateSelectOption\x10\x20\x12\r\n\tCr\
-    eateRow\x102\x12\n\n\x06GetRow\x103\x12\r\n\tDeleteRow\x104\x12\x10\n\
-    \x0cDuplicateRow\x105\x12\x0b\n\x07GetCell\x10F\x12\x0e\n\nUpdateCell\
-    \x10G\x12\x1a\n\x16UpdateCellSelectOption\x10Hb\x06proto3\
+    \x0bUpdateField\x10\x0b\x12\x19\n\x15UpdateFieldTypeOption\x10\x0c\x12\
+    \x0f\n\x0bInsertField\x10\r\x12\x0f\n\x0bDeleteField\x10\x0e\x12\x11\n\r\
+    SwitchToField\x10\x14\x12\x12\n\x0eDuplicateField\x10\x15\x12\x17\n\x13G\
+    etEditFieldContext\x10\x16\x12\x0c\n\x08MoveItem\x10\x17\x12\x16\n\x12Ge\
+    tFieldTypeOption\x10\x18\x12\x13\n\x0fNewSelectOption\x10\x1e\x12\x1b\n\
+    \x17GetSelectOptionCellData\x10\x1f\x12\x16\n\x12UpdateSelectOption\x10\
+    \x20\x12\r\n\tCreateRow\x102\x12\n\n\x06GetRow\x103\x12\r\n\tDeleteRow\
+    \x104\x12\x10\n\x0cDuplicateRow\x105\x12\x0b\n\x07GetCell\x10F\x12\x0e\n\
+    \nUpdateCell\x10G\x12\x1a\n\x16UpdateSelectOptionCell\x10H\x12\x12\n\x0e\
+    UpdateDateCell\x10P\x12\x13\n\x0fGetDateCellData\x10Zb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 75 - 145
frontend/rust-lib/flowy-grid/src/protobuf/model/selection_type_option.rs

@@ -1102,9 +1102,7 @@ impl ::protobuf::reflect::ProtobufValue for SelectOptionChangesetPayload {
 #[derive(PartialEq,Clone,Default)]
 pub struct SelectOptionCellChangesetPayload {
     // message fields
-    pub grid_id: ::std::string::String,
-    pub row_id: ::std::string::String,
-    pub field_id: ::std::string::String,
+    pub cell_identifier: ::protobuf::SingularPtrField<super::cell_entities::CellIdentifierPayload>,
     // message oneof groups
     pub one_of_insert_option_id: ::std::option::Option<SelectOptionCellChangesetPayload_oneof_one_of_insert_option_id>,
     pub one_of_delete_option_id: ::std::option::Option<SelectOptionCellChangesetPayload_oneof_one_of_delete_option_id>,
@@ -1134,85 +1132,40 @@ impl SelectOptionCellChangesetPayload {
         ::std::default::Default::default()
     }
 
-    // string grid_id = 1;
-
-
-    pub fn get_grid_id(&self) -> &str {
-        &self.grid_id
-    }
-    pub fn clear_grid_id(&mut self) {
-        self.grid_id.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_grid_id(&mut self, v: ::std::string::String) {
-        self.grid_id = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_grid_id(&mut self) -> &mut ::std::string::String {
-        &mut self.grid_id
-    }
-
-    // Take field
-    pub fn take_grid_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.grid_id, ::std::string::String::new())
-    }
-
-    // string row_id = 2;
-
-
-    pub fn get_row_id(&self) -> &str {
-        &self.row_id
-    }
-    pub fn clear_row_id(&mut self) {
-        self.row_id.clear();
-    }
+    // .CellIdentifierPayload cell_identifier = 1;
 
-    // Param is passed by value, moved
-    pub fn set_row_id(&mut self, v: ::std::string::String) {
-        self.row_id = v;
-    }
 
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_row_id(&mut self) -> &mut ::std::string::String {
-        &mut self.row_id
+    pub fn get_cell_identifier(&self) -> &super::cell_entities::CellIdentifierPayload {
+        self.cell_identifier.as_ref().unwrap_or_else(|| <super::cell_entities::CellIdentifierPayload as ::protobuf::Message>::default_instance())
     }
-
-    // Take field
-    pub fn take_row_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.row_id, ::std::string::String::new())
+    pub fn clear_cell_identifier(&mut self) {
+        self.cell_identifier.clear();
     }
 
-    // string field_id = 3;
-
-
-    pub fn get_field_id(&self) -> &str {
-        &self.field_id
-    }
-    pub fn clear_field_id(&mut self) {
-        self.field_id.clear();
+    pub fn has_cell_identifier(&self) -> bool {
+        self.cell_identifier.is_some()
     }
 
     // Param is passed by value, moved
-    pub fn set_field_id(&mut self, v: ::std::string::String) {
-        self.field_id = v;
+    pub fn set_cell_identifier(&mut self, v: super::cell_entities::CellIdentifierPayload) {
+        self.cell_identifier = ::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_id(&mut self) -> &mut ::std::string::String {
-        &mut self.field_id
+    pub fn mut_cell_identifier(&mut self) -> &mut super::cell_entities::CellIdentifierPayload {
+        if self.cell_identifier.is_none() {
+            self.cell_identifier.set_default();
+        }
+        self.cell_identifier.as_mut().unwrap()
     }
 
     // Take field
-    pub fn take_field_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.field_id, ::std::string::String::new())
+    pub fn take_cell_identifier(&mut self) -> super::cell_entities::CellIdentifierPayload {
+        self.cell_identifier.take().unwrap_or_else(|| super::cell_entities::CellIdentifierPayload::new())
     }
 
-    // string insert_option_id = 4;
+    // string insert_option_id = 2;
 
 
     pub fn get_insert_option_id(&self) -> &str {
@@ -1261,7 +1214,7 @@ impl SelectOptionCellChangesetPayload {
         }
     }
 
-    // string delete_option_id = 5;
+    // string delete_option_id = 3;
 
 
     pub fn get_delete_option_id(&self) -> &str {
@@ -1313,6 +1266,11 @@ impl SelectOptionCellChangesetPayload {
 
 impl ::protobuf::Message for SelectOptionCellChangesetPayload {
     fn is_initialized(&self) -> bool {
+        for v in &self.cell_identifier {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
         true
     }
 
@@ -1321,21 +1279,15 @@ impl ::protobuf::Message for SelectOptionCellChangesetPayload {
             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)?;
+                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.cell_identifier)?;
                 },
                 2 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.row_id)?;
-                },
-                3 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?;
-                },
-                4 => {
                     if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
                         return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
                     }
                     self.one_of_insert_option_id = ::std::option::Option::Some(SelectOptionCellChangesetPayload_oneof_one_of_insert_option_id::insert_option_id(is.read_string()?));
                 },
-                5 => {
+                3 => {
                     if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
                         return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
                     }
@@ -1353,26 +1305,21 @@ impl ::protobuf::Message for SelectOptionCellChangesetPayload {
     #[allow(unused_variables)]
     fn compute_size(&self) -> u32 {
         let mut my_size = 0;
-        if !self.grid_id.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.grid_id);
-        }
-        if !self.row_id.is_empty() {
-            my_size += ::protobuf::rt::string_size(2, &self.row_id);
-        }
-        if !self.field_id.is_empty() {
-            my_size += ::protobuf::rt::string_size(3, &self.field_id);
+        if let Some(ref v) = self.cell_identifier.as_ref() {
+            let len = v.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_insert_option_id {
             match v {
                 &SelectOptionCellChangesetPayload_oneof_one_of_insert_option_id::insert_option_id(ref v) => {
-                    my_size += ::protobuf::rt::string_size(4, &v);
+                    my_size += ::protobuf::rt::string_size(2, &v);
                 },
             };
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_delete_option_id {
             match v {
                 &SelectOptionCellChangesetPayload_oneof_one_of_delete_option_id::delete_option_id(ref v) => {
-                    my_size += ::protobuf::rt::string_size(5, &v);
+                    my_size += ::protobuf::rt::string_size(3, &v);
                 },
             };
         }
@@ -1382,26 +1329,22 @@ impl ::protobuf::Message for SelectOptionCellChangesetPayload {
     }
 
     fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        if !self.grid_id.is_empty() {
-            os.write_string(1, &self.grid_id)?;
-        }
-        if !self.row_id.is_empty() {
-            os.write_string(2, &self.row_id)?;
-        }
-        if !self.field_id.is_empty() {
-            os.write_string(3, &self.field_id)?;
+        if let Some(ref v) = self.cell_identifier.as_ref() {
+            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_insert_option_id {
             match v {
                 &SelectOptionCellChangesetPayload_oneof_one_of_insert_option_id::insert_option_id(ref v) => {
-                    os.write_string(4, v)?;
+                    os.write_string(2, v)?;
                 },
             };
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_delete_option_id {
             match v {
                 &SelectOptionCellChangesetPayload_oneof_one_of_delete_option_id::delete_option_id(ref v) => {
-                    os.write_string(5, v)?;
+                    os.write_string(3, v)?;
                 },
             };
         }
@@ -1443,20 +1386,10 @@ impl ::protobuf::Message for SelectOptionCellChangesetPayload {
         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: &SelectOptionCellChangesetPayload| { &m.grid_id },
-                |m: &mut SelectOptionCellChangesetPayload| { &mut m.grid_id },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "row_id",
-                |m: &SelectOptionCellChangesetPayload| { &m.row_id },
-                |m: &mut SelectOptionCellChangesetPayload| { &mut m.row_id },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "field_id",
-                |m: &SelectOptionCellChangesetPayload| { &m.field_id },
-                |m: &mut SelectOptionCellChangesetPayload| { &mut m.field_id },
+            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<super::cell_entities::CellIdentifierPayload>>(
+                "cell_identifier",
+                |m: &SelectOptionCellChangesetPayload| { &m.cell_identifier },
+                |m: &mut SelectOptionCellChangesetPayload| { &mut m.cell_identifier },
             ));
             fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
                 "insert_option_id",
@@ -1484,9 +1417,7 @@ impl ::protobuf::Message for SelectOptionCellChangesetPayload {
 
 impl ::protobuf::Clear for SelectOptionCellChangesetPayload {
     fn clear(&mut self) {
-        self.grid_id.clear();
-        self.row_id.clear();
-        self.field_id.clear();
+        self.cell_identifier.clear();
         self.one_of_insert_option_id = ::std::option::Option::None;
         self.one_of_delete_option_id = ::std::option::Option::None;
         self.unknown_fields.clear();
@@ -1506,7 +1437,7 @@ impl ::protobuf::reflect::ProtobufValue for SelectOptionCellChangesetPayload {
 }
 
 #[derive(PartialEq,Clone,Default)]
-pub struct SelectOptionContext {
+pub struct SelectOptionCellData {
     // message fields
     pub options: ::protobuf::RepeatedField<SelectOption>,
     pub select_options: ::protobuf::RepeatedField<SelectOption>,
@@ -1515,14 +1446,14 @@ pub struct SelectOptionContext {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a SelectOptionContext {
-    fn default() -> &'a SelectOptionContext {
-        <SelectOptionContext as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a SelectOptionCellData {
+    fn default() -> &'a SelectOptionCellData {
+        <SelectOptionCellData as ::protobuf::Message>::default_instance()
     }
 }
 
-impl SelectOptionContext {
-    pub fn new() -> SelectOptionContext {
+impl SelectOptionCellData {
+    pub fn new() -> SelectOptionCellData {
         ::std::default::Default::default()
     }
 
@@ -1577,7 +1508,7 @@ impl SelectOptionContext {
     }
 }
 
-impl ::protobuf::Message for SelectOptionContext {
+impl ::protobuf::Message for SelectOptionCellData {
     fn is_initialized(&self) -> bool {
         for v in &self.options {
             if !v.is_initialized() {
@@ -1668,8 +1599,8 @@ impl ::protobuf::Message for SelectOptionContext {
         Self::descriptor_static()
     }
 
-    fn new() -> SelectOptionContext {
-        SelectOptionContext::new()
+    fn new() -> SelectOptionCellData {
+        SelectOptionCellData::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -1678,29 +1609,29 @@ impl ::protobuf::Message for SelectOptionContext {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<SelectOption>>(
                 "options",
-                |m: &SelectOptionContext| { &m.options },
-                |m: &mut SelectOptionContext| { &mut m.options },
+                |m: &SelectOptionCellData| { &m.options },
+                |m: &mut SelectOptionCellData| { &mut m.options },
             ));
             fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<SelectOption>>(
                 "select_options",
-                |m: &SelectOptionContext| { &m.select_options },
-                |m: &mut SelectOptionContext| { &mut m.select_options },
+                |m: &SelectOptionCellData| { &m.select_options },
+                |m: &mut SelectOptionCellData| { &mut m.select_options },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SelectOptionContext>(
-                "SelectOptionContext",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SelectOptionCellData>(
+                "SelectOptionCellData",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static SelectOptionContext {
-        static instance: ::protobuf::rt::LazyV2<SelectOptionContext> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(SelectOptionContext::new)
+    fn default_instance() -> &'static SelectOptionCellData {
+        static instance: ::protobuf::rt::LazyV2<SelectOptionCellData> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(SelectOptionCellData::new)
     }
 }
 
-impl ::protobuf::Clear for SelectOptionContext {
+impl ::protobuf::Clear for SelectOptionCellData {
     fn clear(&mut self) {
         self.options.clear();
         self.select_options.clear();
@@ -1708,13 +1639,13 @@ impl ::protobuf::Clear for SelectOptionContext {
     }
 }
 
-impl ::std::fmt::Debug for SelectOptionContext {
+impl ::std::fmt::Debug for SelectOptionCellData {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for SelectOptionContext {
+impl ::protobuf::reflect::ProtobufValue for SelectOptionCellData {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
@@ -1806,19 +1737,18 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     _option\x18\x03\x20\x01(\x0b2\r.SelectOptionH\x01R\x0cupdateOption\x124\
     \n\rdelete_option\x18\x04\x20\x01(\x0b2\r.SelectOptionH\x02R\x0cdeleteOp\
     tionB\x16\n\x14one_of_insert_optionB\x16\n\x14one_of_update_optionB\x16\
-    \n\x14one_of_delete_option\"\xfb\x01\n\x20SelectOptionCellChangesetPaylo\
-    ad\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x15\n\x06row_i\
-    d\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08field_id\x18\x03\x20\x01(\tR\
-    \x07fieldId\x12*\n\x10insert_option_id\x18\x04\x20\x01(\tH\0R\x0einsertO\
-    ptionId\x12*\n\x10delete_option_id\x18\x05\x20\x01(\tH\x01R\x0edeleteOpt\
-    ionIdB\x19\n\x17one_of_insert_option_idB\x19\n\x17one_of_delete_option_i\
-    d\"t\n\x13SelectOptionContext\x12'\n\x07options\x18\x01\x20\x03(\x0b2\r.\
-    SelectOptionR\x07options\x124\n\x0eselect_options\x18\x02\x20\x03(\x0b2\
-    \r.SelectOptionR\rselectOptions*y\n\x11SelectOptionColor\x12\n\n\x06Purp\
-    le\x10\0\x12\x08\n\x04Pink\x10\x01\x12\r\n\tLightPink\x10\x02\x12\n\n\
-    \x06Orange\x10\x03\x12\n\n\x06Yellow\x10\x04\x12\x08\n\x04Lime\x10\x05\
-    \x12\t\n\x05Green\x10\x06\x12\x08\n\x04Aqua\x10\x07\x12\x08\n\x04Blue\
-    \x10\x08b\x06proto3\
+    \n\x14one_of_delete_option\"\xf1\x01\n\x20SelectOptionCellChangesetPaylo\
+    ad\x12?\n\x0fcell_identifier\x18\x01\x20\x01(\x0b2\x16.CellIdentifierPay\
+    loadR\x0ecellIdentifier\x12*\n\x10insert_option_id\x18\x02\x20\x01(\tH\0\
+    R\x0einsertOptionId\x12*\n\x10delete_option_id\x18\x03\x20\x01(\tH\x01R\
+    \x0edeleteOptionIdB\x19\n\x17one_of_insert_option_idB\x19\n\x17one_of_de\
+    lete_option_id\"u\n\x14SelectOptionCellData\x12'\n\x07options\x18\x01\
+    \x20\x03(\x0b2\r.SelectOptionR\x07options\x124\n\x0eselect_options\x18\
+    \x02\x20\x03(\x0b2\r.SelectOptionR\rselectOptions*y\n\x11SelectOptionCol\
+    or\x12\n\n\x06Purple\x10\0\x12\x08\n\x04Pink\x10\x01\x12\r\n\tLightPink\
+    \x10\x02\x12\n\n\x06Orange\x10\x03\x12\n\n\x06Yellow\x10\x04\x12\x08\n\
+    \x04Lime\x10\x05\x12\t\n\x05Green\x10\x06\x12\x08\n\x04Aqua\x10\x07\x12\
+    \x08\n\x04Blue\x10\x08b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 11 - 0
frontend/rust-lib/flowy-grid/src/protobuf/proto/date_type_option.proto

@@ -1,10 +1,21 @@
 syntax = "proto3";
+import "cell_entities.proto";
 
 message DateTypeOption {
     DateFormat date_format = 1;
     TimeFormat time_format = 2;
     bool include_time = 3;
 }
+message DateCellData {
+    string date = 1;
+    string time = 2;
+    int64 timestamp = 3;
+}
+message DateChangesetPayload {
+    CellIdentifierPayload cell_identifier = 1;
+    oneof one_of_date { string date = 2; };
+    oneof one_of_time { string time = 3; };
+}
 enum DateFormat {
     Local = 0;
     US = 1;

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

@@ -5,14 +5,16 @@ enum GridEvent {
     GetGridBlocks = 1;
     GetFields = 10;
     UpdateField = 11;
-    InsertField = 12;
-    DeleteField = 13;
-    SwitchToField = 14;
-    DuplicateField = 15;
-    GetEditFieldContext = 16;
-    MoveItem = 17;
+    UpdateFieldTypeOption = 12;
+    InsertField = 13;
+    DeleteField = 14;
+    SwitchToField = 20;
+    DuplicateField = 21;
+    GetEditFieldContext = 22;
+    MoveItem = 23;
+    GetFieldTypeOption = 24;
     NewSelectOption = 30;
-    GetSelectOptionContext = 31;
+    GetSelectOptionCellData = 31;
     UpdateSelectOption = 32;
     CreateRow = 50;
     GetRow = 51;
@@ -20,5 +22,7 @@ enum GridEvent {
     DuplicateRow = 53;
     GetCell = 70;
     UpdateCell = 71;
-    UpdateCellSelectOption = 72;
+    UpdateSelectOptionCell = 72;
+    UpdateDateCell = 80;
+    GetDateCellData = 90;
 }

+ 4 - 6
frontend/rust-lib/flowy-grid/src/protobuf/proto/selection_type_option.proto

@@ -21,13 +21,11 @@ message SelectOptionChangesetPayload {
     oneof one_of_delete_option { SelectOption delete_option = 4; };
 }
 message SelectOptionCellChangesetPayload {
-    string grid_id = 1;
-    string row_id = 2;
-    string field_id = 3;
-    oneof one_of_insert_option_id { string insert_option_id = 4; };
-    oneof one_of_delete_option_id { string delete_option_id = 5; };
+    CellIdentifierPayload cell_identifier = 1;
+    oneof one_of_insert_option_id { string insert_option_id = 2; };
+    oneof one_of_delete_option_id { string delete_option_id = 3; };
 }
-message SelectOptionContext {
+message SelectOptionCellData {
     repeated SelectOption options = 1;
     repeated SelectOption select_options = 2;
 }

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

@@ -1,6 +1,6 @@
 use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{CellDataChangeset, CellDataOperation, TypeOptionCellData};
+use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData};
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::FlowyError;
@@ -44,21 +44,21 @@ const YES: &str = "Yes";
 const NO: &str = "No";
 
 impl CellDataOperation for CheckboxTypeOption {
-    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String {
+    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData {
         if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
             if !type_option_cell_data.is_checkbox() {
-                return String::new();
+                return DecodedCellData::default();
             }
             let cell_data = type_option_cell_data.data;
             if cell_data == YES || cell_data == NO {
-                return cell_data;
+                return DecodedCellData::from_content(cell_data);
             }
         }
 
-        String::new()
+        DecodedCellData::default()
     }
 
-    fn apply_changeset<T: Into<CellDataChangeset>>(
+    fn apply_changeset<T: Into<CellContentChangeset>>(
         &self,
         changeset: T,
         _cell_meta: Option<CellMeta>,
@@ -99,21 +99,21 @@ mod tests {
         let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
 
         let data = type_option.apply_changeset("true", None).unwrap();
-        assert_eq!(type_option.decode_cell_data(data, &field_meta), YES);
+        assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES);
 
         let data = type_option.apply_changeset("1", None).unwrap();
-        assert_eq!(type_option.decode_cell_data(data, &field_meta), YES);
+        assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES);
 
         let data = type_option.apply_changeset("yes", None).unwrap();
-        assert_eq!(type_option.decode_cell_data(data, &field_meta), YES);
+        assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES);
 
         let data = type_option.apply_changeset("false", None).unwrap();
-        assert_eq!(type_option.decode_cell_data(data, &field_meta), NO);
+        assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO);
 
         let data = type_option.apply_changeset("no", None).unwrap();
-        assert_eq!(type_option.decode_cell_data(data, &field_meta), NO);
+        assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO);
 
         let data = type_option.apply_changeset("123", None).unwrap();
-        assert_eq!(type_option.decode_cell_data(data, &field_meta), NO);
+        assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO);
     }
 }

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

@@ -1,18 +1,17 @@
 use crate::impl_type_option;
-use crate::services::row::{CellDataChangeset, CellDataOperation, TypeOptionCellData};
+use crate::services::entities::{CellIdentifier, CellIdentifierPayload};
+use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
+use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData};
 use bytes::Bytes;
 use chrono::format::strftime::StrftimeItems;
 use chrono::NaiveDateTime;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-use flowy_error::FlowyError;
+use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
-    CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
+    CellChangeset, CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
 };
-
 use serde::{Deserialize, Serialize};
 use std::str::FromStr;
-
-use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
 use strum_macros::EnumIter;
 
 // Date
@@ -30,60 +29,151 @@ pub struct DateTypeOption {
 impl_type_option!(DateTypeOption, FieldType::DateTime);
 
 impl DateTypeOption {
-    #[allow(dead_code)]
-    fn today_from_timestamp(&self, timestamp: i64) -> String {
+    fn today_desc_from_timestamp(&self, timestamp: i64, time: &Option<String>) -> String {
         let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0);
-        self.today_from_native(native)
+        self.today_desc_from_native(native, time)
+    }
+
+    #[allow(dead_code)]
+    fn today_desc_from_str(&self, s: String, time: &Option<String>) -> String {
+        match NaiveDateTime::parse_from_str(&s, &self.date_fmt(time)) {
+            Ok(native) => self.today_desc_from_native(native, time),
+            Err(_) => "".to_owned(),
+        }
     }
 
-    fn today_from_native(&self, naive: chrono::NaiveDateTime) -> String {
-        let utc: chrono::DateTime<chrono::Utc> = chrono::DateTime::from_utc(naive, chrono::Utc);
-        let local: chrono::DateTime<chrono::Local> = chrono::DateTime::from(utc);
-        let output = format!("{}", local.format_with_items(StrftimeItems::new(&self.fmt_str())));
+    fn today_desc_from_native(&self, native: chrono::NaiveDateTime, time: &Option<String>) -> String {
+        let utc = self.utc_date_time_from_native(native);
+        // let china_timezone = FixedOffset::east(8 * 3600);
+        // let a = utc.with_timezone(&china_timezone);
+        let fmt = self.date_fmt(time);
+        let output = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt)));
         output
     }
 
-    fn fmt_str(&self) -> String {
+    fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime<chrono::Utc> {
+        let native = NaiveDateTime::from_timestamp(timestamp, 0);
+        self.utc_date_time_from_native(native)
+    }
+
+    fn utc_date_time_from_native(&self, naive: chrono::NaiveDateTime) -> chrono::DateTime<chrono::Utc> {
+        chrono::DateTime::<chrono::Utc>::from_utc(naive, chrono::Utc)
+    }
+
+    fn date_fmt(&self, time: &Option<String>) -> String {
         if self.include_time {
-            format!("{} {}", self.date_format.format_str(), self.time_format.format_str())
+            match time.as_ref() {
+                None => self.date_format.format_str().to_string(),
+                Some(time_str) => {
+                    if time_str.is_empty() {
+                        self.date_format.format_str().to_string()
+                    } else {
+                        format!("{} {}", self.date_format.format_str(), self.time_format.format_str())
+                    }
+                }
+            }
         } else {
             self.date_format.format_str().to_string()
         }
     }
+
+    pub fn make_date_cell_data(&self, cell_meta: &Option<CellMeta>) -> FlowyResult<DateCellData> {
+        if cell_meta.is_none() {
+            return Ok(DateCellData::default());
+        }
+
+        let json = &cell_meta.as_ref().unwrap().data;
+        let result = TypeOptionCellData::from_str(json);
+        if result.is_err() {
+            return Ok(DateCellData::default());
+        }
+
+        let serde_cell_data = DateCellDataSerde::from_str(&result.unwrap().data)?;
+        let date = self.decode_cell_data_from_timestamp(&serde_cell_data).content;
+        let time = serde_cell_data.time.unwrap_or("".to_owned());
+        let timestamp = serde_cell_data.timestamp;
+
+        return Ok(DateCellData { date, time, timestamp });
+    }
+
+    fn decode_cell_data_from_timestamp(&self, serde_cell_data: &DateCellDataSerde) -> DecodedCellData {
+        if serde_cell_data.timestamp == 0 {
+            return DecodedCellData::default();
+        }
+
+        let cell_content = self.today_desc_from_timestamp(serde_cell_data.timestamp, &serde_cell_data.time);
+        return DecodedCellData::new(serde_cell_data.timestamp.to_string(), cell_content);
+    }
+
+    fn timestamp_from_utc_with_time(
+        &self,
+        utc: &chrono::DateTime<chrono::Utc>,
+        time: &Option<String>,
+    ) -> FlowyResult<i64> {
+        if let Some(time_str) = time.as_ref() {
+            if !time_str.is_empty() {
+                let date_str = format!(
+                    "{}{}",
+                    utc.format_with_items(StrftimeItems::new(self.date_format.format_str())),
+                    &time_str
+                );
+
+                return match NaiveDateTime::parse_from_str(&date_str, &self.date_fmt(time)) {
+                    Ok(native) => {
+                        let utc = self.utc_date_time_from_native(native);
+                        Ok(utc.timestamp())
+                    }
+                    Err(_e) => {
+                        let msg = format!("Parse {} failed", date_str);
+                        Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg))
+                    }
+                };
+            }
+        }
+
+        return Ok(utc.timestamp());
+    }
 }
 
 impl CellDataOperation for DateTypeOption {
-    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String {
+    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData {
         if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
+            // Return default data if the type_option_cell_data is not FieldType::DateTime.
+            // It happens when switching from one field to another.
+            // For example:
+            // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen.
             if !type_option_cell_data.is_date() {
-                return String::new();
-            }
-
-            let cell_data = type_option_cell_data.data;
-            if let Ok(timestamp) = cell_data.parse::<i64>() {
-                let native = NaiveDateTime::from_timestamp(timestamp, 0);
-                return self.today_from_native(native);
-            }
-
-            if NaiveDateTime::parse_from_str(&cell_data, &self.fmt_str()).is_ok() {
-                return cell_data;
+                return DecodedCellData::default();
             }
+            return match DateCellDataSerde::from_str(&type_option_cell_data.data) {
+                Ok(serde_cell_data) => self.decode_cell_data_from_timestamp(&serde_cell_data),
+                Err(_) => DecodedCellData::default(),
+            };
         }
 
-        String::new()
+        DecodedCellData::default()
     }
 
-    fn apply_changeset<T: Into<CellDataChangeset>>(
+    fn apply_changeset<T: Into<CellContentChangeset>>(
         &self,
         changeset: T,
         _cell_meta: Option<CellMeta>,
     ) -> Result<String, FlowyError> {
-        let changeset = changeset.into();
-        if changeset.parse::<f64>().is_err() || changeset.parse::<i64>().is_err() {
-            return Err(FlowyError::internal().context(format!("Parse {} failed", changeset)));
+        let content_changeset: DateCellContentChangeset = serde_json::from_str(&changeset.into())?;
+        let cell_data = match content_changeset.date_timestamp() {
+            None => DateCellDataSerde::default(),
+            Some(date_timestamp) => match (self.include_time, content_changeset.time) {
+                (true, Some(time)) => {
+                    let time = Some(time.trim().to_uppercase());
+                    let utc = self.utc_date_time_from_timestamp(date_timestamp);
+                    let timestamp = self.timestamp_from_utc_with_time(&utc, &time)?;
+                    DateCellDataSerde::new(timestamp, time, &self.time_format)
+                }
+                _ => DateCellDataSerde::from_timestamp(date_timestamp, Some(default_time_str(&self.time_format))),
+            },
         };
 
-        Ok(TypeOptionCellData::new(changeset, self.field_type()).json())
+        Ok(TypeOptionCellData::new(cell_data.to_string(), self.field_type()).json())
     }
 }
 
@@ -183,7 +273,7 @@ impl TimeFormat {
     // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
     pub fn format_str(&self) -> &'static str {
         match self {
-            TimeFormat::TwelveHour => "%r",
+            TimeFormat::TwelveHour => "%I:%M %p",
             TimeFormat::TwentyFourHour => "%R",
         }
     }
@@ -195,10 +285,131 @@ impl std::default::Default for TimeFormat {
     }
 }
 
+#[derive(Clone, Debug, Default, ProtoBuf)]
+pub struct DateCellData {
+    #[pb(index = 1)]
+    pub date: String,
+
+    #[pb(index = 2)]
+    pub time: String,
+
+    #[pb(index = 3)]
+    pub timestamp: i64,
+}
+
+#[derive(Default, Serialize, Deserialize)]
+pub struct DateCellDataSerde {
+    pub timestamp: i64,
+    pub time: Option<String>,
+}
+
+impl DateCellDataSerde {
+    fn new(timestamp: i64, time: Option<String>, time_format: &TimeFormat) -> Self {
+        Self {
+            timestamp,
+            time: Some(time.unwrap_or(default_time_str(time_format))),
+        }
+    }
+
+    pub(crate) fn from_timestamp(timestamp: i64, time: Option<String>) -> Self {
+        Self { timestamp, time }
+    }
+
+    fn to_string(self) -> String {
+        serde_json::to_string(&self).unwrap_or("".to_string())
+    }
+
+    fn from_str(s: &str) -> FlowyResult<Self> {
+        serde_json::from_str::<DateCellDataSerde>(s).map_err(internal_error)
+    }
+}
+
+fn default_time_str(time_format: &TimeFormat) -> String {
+    match time_format {
+        TimeFormat::TwelveHour => "12:00 AM".to_string(),
+        TimeFormat::TwentyFourHour => "00:00".to_string(),
+    }
+}
+
+#[derive(Clone, Debug, Default, ProtoBuf)]
+pub struct DateChangesetPayload {
+    #[pb(index = 1)]
+    pub cell_identifier: CellIdentifierPayload,
+
+    #[pb(index = 2, one_of)]
+    pub date: Option<String>,
+
+    #[pb(index = 3, one_of)]
+    pub time: Option<String>,
+}
+
+pub struct DateChangesetParams {
+    pub cell_identifier: CellIdentifier,
+    pub date: Option<String>,
+    pub time: Option<String>,
+}
+
+impl TryInto<DateChangesetParams> for DateChangesetPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<DateChangesetParams, Self::Error> {
+        let cell_identifier: CellIdentifier = self.cell_identifier.try_into()?;
+        Ok(DateChangesetParams {
+            cell_identifier,
+            date: self.date,
+            time: self.time,
+        })
+    }
+}
+
+impl std::convert::From<DateChangesetParams> for CellChangeset {
+    fn from(params: DateChangesetParams) -> Self {
+        let changeset = DateCellContentChangeset {
+            date: params.date,
+            time: params.time,
+        };
+        let s = serde_json::to_string(&changeset).unwrap();
+        CellChangeset {
+            grid_id: params.cell_identifier.grid_id,
+            row_id: params.cell_identifier.row_id,
+            field_id: params.cell_identifier.field_id,
+            cell_content_changeset: Some(s),
+        }
+    }
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+pub struct DateCellContentChangeset {
+    pub date: Option<String>,
+    pub time: Option<String>,
+}
+
+impl DateCellContentChangeset {
+    pub fn date_timestamp(&self) -> Option<i64> {
+        if let Some(date) = &self.date {
+            match date.parse::<i64>() {
+                Ok(date_timestamp) => Some(date_timestamp),
+                Err(_) => None,
+            }
+        } else {
+            None
+        }
+    }
+}
+
+impl std::convert::From<DateCellContentChangeset> for CellContentChangeset {
+    fn from(changeset: DateCellContentChangeset) -> Self {
+        let s = serde_json::to_string(&changeset).unwrap();
+        CellContentChangeset::from(s)
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use crate::services::field::FieldBuilder;
-    use crate::services::field::{DateFormat, DateTypeOption, TimeFormat};
+    use crate::services::field::{
+        DateCellContentChangeset, DateCellData, DateCellDataSerde, DateFormat, DateTypeOption, TimeFormat,
+    };
     use crate::services::row::{CellDataOperation, TypeOptionCellData};
     use flowy_grid_data_model::entities::FieldType;
     use strum::IntoEnumIterator;
@@ -209,7 +420,7 @@ mod tests {
         let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
         assert_eq!(
             "".to_owned(),
-            type_option.decode_cell_data("1e".to_owned(), &field_meta)
+            type_option.decode_cell_data("1e".to_owned(), &field_meta).content
         );
     }
 
@@ -223,35 +434,25 @@ mod tests {
                 DateFormat::Friendly => {
                     assert_eq!(
                         "Mar 14,2022".to_owned(),
-                        type_option.decode_cell_data(data("1647251762"), &field_meta)
-                    );
-                    assert_eq!(
-                        // "Mar 14,2022".to_owned(),
-                        "".to_owned(),
-                        type_option.decode_cell_data(data("Mar 14,2022 17:56"), &field_meta)
+                        type_option.decode_cell_data(data(1647251762), &field_meta).content
                     );
                 }
                 DateFormat::US => {
                     assert_eq!(
                         "2022/03/14".to_owned(),
-                        type_option.decode_cell_data(data("1647251762"), &field_meta)
-                    );
-                    assert_eq!(
-                        // "2022/03/14".to_owned(),
-                        "".to_owned(),
-                        type_option.decode_cell_data(data("2022/03/14 17:56"), &field_meta)
+                        type_option.decode_cell_data(data(1647251762), &field_meta).content
                     );
                 }
                 DateFormat::ISO => {
                     assert_eq!(
                         "2022-03-14".to_owned(),
-                        type_option.decode_cell_data(data("1647251762"), &field_meta)
+                        type_option.decode_cell_data(data(1647251762), &field_meta).content
                     );
                 }
                 DateFormat::Local => {
                     assert_eq!(
                         "2022/03/14".to_owned(),
-                        type_option.decode_cell_data(data("1647251762"), &field_meta)
+                        type_option.decode_cell_data(data(1647251762), &field_meta).content
                     );
                 }
             }
@@ -266,23 +467,141 @@ mod tests {
             type_option.time_format = time_format;
             match time_format {
                 TimeFormat::TwentyFourHour => {
-                    assert_eq!("Mar 14,2022".to_owned(), type_option.today_from_timestamp(1647251762));
                     assert_eq!(
                         "Mar 14,2022".to_owned(),
-                        type_option.decode_cell_data(data("1647251762"), &field_meta)
+                        type_option.today_desc_from_timestamp(1647251762, &None)
+                    );
+                    assert_eq!(
+                        "Mar 14,2022".to_owned(),
+                        type_option.decode_cell_data(data(1647251762), &field_meta).content
                     );
                 }
                 TimeFormat::TwelveHour => {
-                    assert_eq!("Mar 14,2022".to_owned(), type_option.today_from_timestamp(1647251762));
                     assert_eq!(
                         "Mar 14,2022".to_owned(),
-                        type_option.decode_cell_data(data("1647251762"), &field_meta)
+                        type_option.today_desc_from_timestamp(1647251762, &None)
+                    );
+                    assert_eq!(
+                        "Mar 14,2022".to_owned(),
+                        type_option.decode_cell_data(data(1647251762), &field_meta).content
                     );
                 }
             }
         }
     }
 
+    #[test]
+    fn date_description_time_format_test2() {
+        let mut type_option = DateTypeOption::default();
+        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        for time_format in TimeFormat::iter() {
+            type_option.time_format = time_format;
+            type_option.include_time = true;
+            match time_format {
+                TimeFormat::TwentyFourHour => {
+                    let changeset = DateCellContentChangeset {
+                        date: Some(1653609600.to_string()),
+                        time: None,
+                    };
+                    let result = type_option.apply_changeset(changeset, None).unwrap();
+                    let content = type_option.decode_cell_data(result, &field_meta).content;
+                    assert_eq!("May 27,2022 00:00".to_owned(), content);
+
+                    let changeset = DateCellContentChangeset {
+                        date: Some(1653609600.to_string()),
+                        time: Some("23:00".to_owned()),
+                    };
+
+                    let result = type_option.apply_changeset(changeset, None).unwrap();
+                    let content = type_option.decode_cell_data(result, &field_meta).content;
+                    assert_eq!("May 27,2022 23:00".to_owned(), content);
+                }
+                TimeFormat::TwelveHour => {
+                    let changeset = DateCellContentChangeset {
+                        date: Some(1653609600.to_string()),
+                        time: None,
+                    };
+                    let result = type_option.apply_changeset(changeset, None).unwrap();
+                    let content = type_option.decode_cell_data(result, &field_meta).content;
+                    assert_eq!("May 27,2022 12:00 AM".to_owned(), content);
+
+                    let changeset = DateCellContentChangeset {
+                        date: Some(1653609600.to_string()),
+                        time: Some("".to_owned()),
+                    };
+                    let result = type_option.apply_changeset(changeset, None).unwrap();
+                    let content = type_option.decode_cell_data(result, &field_meta).content;
+                    assert_eq!("May 27,2022".to_owned(), content);
+
+                    let changeset = DateCellContentChangeset {
+                        date: Some(1653609600.to_string()),
+                        time: Some("11:23 pm".to_owned()),
+                    };
+                    let result = type_option.apply_changeset(changeset, None).unwrap();
+                    let content = type_option.decode_cell_data(result, &field_meta).content;
+                    assert_eq!("May 27,2022 11:23 PM".to_owned(), content);
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn date_description_apply_changeset_test() {
+        let mut type_option = DateTypeOption::default();
+        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        let date_timestamp = "1653609600".to_owned();
+
+        let changeset = DateCellContentChangeset {
+            date: Some(date_timestamp.clone()),
+            time: None,
+        };
+        let result = type_option.apply_changeset(changeset, None).unwrap();
+        let content = type_option.decode_cell_data(result.clone(), &field_meta).content;
+        assert_eq!(content, "May 27,2022".to_owned());
+
+        type_option.include_time = true;
+        let content = type_option.decode_cell_data(result, &field_meta).content;
+        assert_eq!(content, "May 27,2022 00:00".to_owned());
+
+        let changeset = DateCellContentChangeset {
+            date: Some(date_timestamp.clone()),
+            time: Some("1:00".to_owned()),
+        };
+        let result = type_option.apply_changeset(changeset, None).unwrap();
+        let content = type_option.decode_cell_data(result, &field_meta).content;
+        assert_eq!(content, "May 27,2022 01:00".to_owned());
+
+        let changeset = DateCellContentChangeset {
+            date: Some(date_timestamp),
+            time: Some("1:00 am".to_owned()),
+        };
+        type_option.time_format = TimeFormat::TwelveHour;
+        let result = type_option.apply_changeset(changeset, None).unwrap();
+        let content = type_option.decode_cell_data(result, &field_meta).content;
+        assert_eq!(content, "May 27,2022 01:00 AM".to_owned());
+    }
+
+    #[test]
+    #[should_panic]
+    fn date_description_apply_changeset_error_test() {
+        let mut type_option = DateTypeOption::default();
+        type_option.include_time = true;
+        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        let date_timestamp = "1653609600".to_owned();
+
+        let changeset = DateCellContentChangeset {
+            date: Some(date_timestamp.clone()),
+            time: Some("1:a0".to_owned()),
+        };
+        let _ = type_option.apply_changeset(changeset, None).unwrap();
+
+        let changeset = DateCellContentChangeset {
+            date: Some(date_timestamp.clone()),
+            time: Some("1:".to_owned()),
+        };
+        let _ = type_option.apply_changeset(changeset, None).unwrap();
+    }
+
     #[test]
     #[should_panic]
     fn date_description_invalid_data_test() {
@@ -290,7 +609,8 @@ mod tests {
         type_option.apply_changeset("he", None).unwrap();
     }
 
-    fn data(s: &str) -> String {
-        TypeOptionCellData::new(s, FieldType::DateTime).json()
+    fn data(s: i64) -> String {
+        let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(s, None)).unwrap();
+        TypeOptionCellData::new(&json, FieldType::DateTime).json()
     }
 }

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

@@ -3,7 +3,7 @@ mod date_type_option;
 mod number_type_option;
 mod selection_type_option;
 mod text_type_option;
-mod type_option_data;
+mod util;
 
 pub use checkbox_type_option::*;
 pub use date_type_option::*;

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

@@ -1,6 +1,6 @@
 use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{CellDataChangeset, CellDataOperation, TypeOptionCellData};
+use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData};
 use bytes::Bytes;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::FlowyError;
@@ -77,34 +77,40 @@ pub struct NumberTypeOption {
 impl_type_option!(NumberTypeOption, FieldType::Number);
 
 impl CellDataOperation for NumberTypeOption {
-    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String {
+    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData {
         if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
             if type_option_cell_data.is_date() {
-                return String::new();
+                return DecodedCellData::default();
             }
 
             let cell_data = type_option_cell_data.data;
             match self.format {
                 NumberFormat::Number => {
                     if let Ok(v) = cell_data.parse::<f64>() {
-                        return v.to_string();
+                        return DecodedCellData::from_content(v.to_string());
                     }
 
                     if let Ok(v) = cell_data.parse::<i64>() {
-                        return v.to_string();
+                        return DecodedCellData::from_content(v.to_string());
                     }
 
-                    return String::new();
+                    DecodedCellData::default()
+                }
+                NumberFormat::Percent => {
+                    let content = cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string());
+                    DecodedCellData::from_content(content)
+                }
+                _ => {
+                    let content = self.money_from_str(&cell_data);
+                    DecodedCellData::from_content(content)
                 }
-                NumberFormat::Percent => cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string()),
-                _ => self.money_from_str(&cell_data),
             }
         } else {
-            String::new()
+            DecodedCellData::default()
         }
     }
 
-    fn apply_changeset<T: Into<CellDataChangeset>>(
+    fn apply_changeset<T: Into<CellContentChangeset>>(
         &self,
         changeset: T,
         _cell_meta: Option<CellMeta>,
@@ -556,7 +562,7 @@ define_currency_set!(
 
 impl NumberFormat {
     pub fn currency(&self) -> &'static number_currency::Currency {
-        let currency = match self {
+        match self {
             NumberFormat::Number => number_currency::NUMBER,
             NumberFormat::USD => number_currency::USD,
             NumberFormat::CanadianDollar => number_currency::CANADIAN_DOLLAR,
@@ -593,8 +599,7 @@ impl NumberFormat {
             NumberFormat::ArgentinePeso => number_currency::ARS,
             NumberFormat::UruguayanPeso => number_currency::UYU,
             NumberFormat::Percent => number_currency::USD,
-        };
-        currency
+        }
     }
 
     pub fn symbol(&self) -> String {
@@ -622,8 +627,14 @@ mod tests {
     fn number_description_invalid_input_test() {
         let type_option = NumberTypeOption::default();
         let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
-        assert_eq!("".to_owned(), type_option.decode_cell_data(data(""), &field_meta));
-        assert_eq!("".to_owned(), type_option.decode_cell_data(data("abc"), &field_meta));
+        assert_eq!(
+            "".to_owned(),
+            type_option.decode_cell_data(data(""), &field_meta).content
+        );
+        assert_eq!(
+            "".to_owned(),
+            type_option.decode_cell_data(data("abc"), &field_meta).content
+        );
     }
 
     #[test]
@@ -639,33 +650,39 @@ mod tests {
             match format {
                 NumberFormat::Number => {
                     assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta),
+                        type_option.decode_cell_data(data("18443"), &field_meta).content,
                         "18443".to_owned()
                     );
                 }
                 NumberFormat::USD => {
                     assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta),
+                        type_option.decode_cell_data(data("18443"), &field_meta).content,
                         "$18,443".to_owned()
                     );
-                    assert_eq!(type_option.decode_cell_data(data(""), &field_meta), "".to_owned());
-                    assert_eq!(type_option.decode_cell_data(data("abc"), &field_meta), "".to_owned());
+                    assert_eq!(
+                        type_option.decode_cell_data(data(""), &field_meta).content,
+                        "".to_owned()
+                    );
+                    assert_eq!(
+                        type_option.decode_cell_data(data("abc"), &field_meta).content,
+                        "".to_owned()
+                    );
                 }
                 NumberFormat::Yen => {
                     assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta),
+                        type_option.decode_cell_data(data("18443"), &field_meta).content,
                         "¥18,443".to_owned()
                     );
                 }
                 NumberFormat::Yuan => {
                     assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta),
+                        type_option.decode_cell_data(data("18443"), &field_meta).content,
                         "CN¥18,443".to_owned()
                     );
                 }
                 NumberFormat::EUR => {
                     assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta),
+                        type_option.decode_cell_data(data("18443"), &field_meta).content,
                         "€18.443".to_owned()
                     );
                 }
@@ -691,25 +708,25 @@ mod tests {
             match format {
                 NumberFormat::Number => {
                     assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta),
+                        type_option.decode_cell_data(data("18443"), &field_meta).content,
                         "18443".to_owned()
                     );
                 }
                 NumberFormat::USD => {
                     assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta),
+                        type_option.decode_cell_data(data("18443"), &field_meta).content,
                         "$1,844.3".to_owned()
                     );
                 }
                 NumberFormat::Yen => {
                     assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta),
+                        type_option.decode_cell_data(data("18443"), &field_meta).content,
                         "¥1,844.3".to_owned()
                     );
                 }
                 NumberFormat::EUR => {
                     assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta),
+                        type_option.decode_cell_data(data("18443"), &field_meta).content,
                         "€1.844,3".to_owned()
                     );
                 }
@@ -731,25 +748,25 @@ mod tests {
             match format {
                 NumberFormat::Number => {
                     assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta),
+                        type_option.decode_cell_data(data("18443"), &field_meta).content,
                         "18443".to_owned()
                     );
                 }
                 NumberFormat::USD => {
                     assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta),
+                        type_option.decode_cell_data(data("18443"), &field_meta).content,
                         "-$18,443".to_owned()
                     );
                 }
                 NumberFormat::Yen => {
                     assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta),
+                        type_option.decode_cell_data(data("18443"), &field_meta).content,
                         "-¥18,443".to_owned()
                     );
                 }
                 NumberFormat::EUR => {
                     assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta),
+                        type_option.decode_cell_data(data("18443"), &field_meta).content,
                         "-€18.443".to_owned()
                     );
                 }

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

@@ -1,7 +1,8 @@
 use crate::impl_type_option;
 use crate::services::entities::{CellIdentifier, CellIdentifierPayload};
+use crate::services::field::type_options::util::get_cell_data;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{CellDataChangeset, CellDataOperation, TypeOptionCellData};
+use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData};
 use bytes::Bytes;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
@@ -41,7 +42,7 @@ pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync {
         SelectOption::with_color(name, color)
     }
 
-    fn option_context(&self, cell_meta: &Option<CellMeta>) -> SelectOptionContext;
+    fn select_option_cell_data(&self, cell_meta: &Option<CellMeta>) -> SelectOptionCellData;
 
     fn options(&self) -> &Vec<SelectOption>;
 
@@ -77,9 +78,9 @@ pub struct SingleSelectTypeOption {
 impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect);
 
 impl SelectOptionOperation for SingleSelectTypeOption {
-    fn option_context(&self, cell_meta: &Option<CellMeta>) -> SelectOptionContext {
+    fn select_option_cell_data(&self, cell_meta: &Option<CellMeta>) -> SelectOptionCellData {
         let select_options = make_select_context_from(cell_meta, &self.options);
-        SelectOptionContext {
+        SelectOptionCellData {
             options: self.options.clone(),
             select_options,
         }
@@ -95,31 +96,30 @@ impl SelectOptionOperation for SingleSelectTypeOption {
 }
 
 impl CellDataOperation for SingleSelectTypeOption {
-    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String {
+    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData {
         if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
             if !type_option_cell_data.is_single_select() {
-                return String::new();
+                return DecodedCellData::default();
             }
 
-            match select_option_ids(type_option_cell_data.data).first() {
-                None => String::new(),
-                Some(option_id) => match self.options.iter().find(|option| &option.id == option_id) {
-                    None => String::new(),
-                    Some(option) => option.name.clone(),
-                },
+            if let Some(option_id) = select_option_ids(type_option_cell_data.data).first() {
+                return match self.options.iter().find(|option| &option.id == option_id) {
+                    None => DecodedCellData::default(),
+                    Some(option) => DecodedCellData::from_content(option.name.clone()),
+                };
             }
-        } else {
-            String::new()
         }
+
+        DecodedCellData::default()
     }
 
-    fn apply_changeset<T: Into<CellDataChangeset>>(
+    fn apply_changeset<T: Into<CellContentChangeset>>(
         &self,
         changeset: T,
         _cell_meta: Option<CellMeta>,
     ) -> Result<String, FlowyError> {
         let changeset = changeset.into();
-        let select_option_changeset: SelectOptionCellChangeset = serde_json::from_str(&changeset)?;
+        let select_option_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset)?;
         let new_cell_data: String;
         if let Some(insert_option_id) = select_option_changeset.insert_option_id {
             tracing::trace!("Insert single select option: {}", &insert_option_id);
@@ -166,19 +166,10 @@ pub struct MultiSelectTypeOption {
 }
 impl_type_option!(MultiSelectTypeOption, FieldType::MultiSelect);
 
-impl MultiSelectTypeOption {
-    pub fn get_cell_data(&self, cell_meta: &CellMeta) -> String {
-        match TypeOptionCellData::from_str(&cell_meta.data) {
-            Ok(type_option) => type_option.data,
-            Err(_) => String::new(),
-        }
-    }
-}
-
 impl SelectOptionOperation for MultiSelectTypeOption {
-    fn option_context(&self, cell_meta: &Option<CellMeta>) -> SelectOptionContext {
+    fn select_option_cell_data(&self, cell_meta: &Option<CellMeta>) -> SelectOptionCellData {
         let select_options = make_select_context_from(cell_meta, &self.options);
-        SelectOptionContext {
+        SelectOptionCellData {
             options: self.options.clone(),
             select_options,
         }
@@ -194,41 +185,40 @@ impl SelectOptionOperation for MultiSelectTypeOption {
 }
 
 impl CellDataOperation for MultiSelectTypeOption {
-    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String {
+    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData {
         if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
             if !type_option_cell_data.is_multi_select() {
-                return String::new();
+                return DecodedCellData::default();
             }
             let option_ids = select_option_ids(type_option_cell_data.data);
-            self.options
+            let content = self
+                .options
                 .iter()
                 .filter(|option| option_ids.contains(&option.id))
                 .map(|option| option.name.clone())
                 .collect::<Vec<String>>()
-                .join(SELECTION_IDS_SEPARATOR)
+                .join(SELECTION_IDS_SEPARATOR);
+            DecodedCellData::from_content(content)
         } else {
-            String::new()
+            DecodedCellData::default()
         }
     }
 
-    fn apply_changeset<T: Into<CellDataChangeset>>(
+    fn apply_changeset<T: Into<CellContentChangeset>>(
         &self,
         changeset: T,
         cell_meta: Option<CellMeta>,
     ) -> Result<String, FlowyError> {
-        let changeset = changeset.into();
-        let select_option_changeset: SelectOptionCellChangeset = serde_json::from_str(&changeset)?;
+        let content_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset.into())?;
         let new_cell_data: String;
         match cell_meta {
             None => {
-                new_cell_data = select_option_changeset
-                    .insert_option_id
-                    .unwrap_or_else(|| "".to_owned());
+                new_cell_data = content_changeset.insert_option_id.unwrap_or_else(|| "".to_owned());
             }
             Some(cell_meta) => {
-                let cell_data = self.get_cell_data(&cell_meta);
+                let cell_data = get_cell_data(&cell_meta);
                 let mut selected_options = select_option_ids(cell_data);
-                if let Some(insert_option_id) = select_option_changeset.insert_option_id {
+                if let Some(insert_option_id) = content_changeset.insert_option_id {
                     tracing::trace!("Insert multi select option: {}", &insert_option_id);
                     if selected_options.contains(&insert_option_id) {
                         selected_options.retain(|id| id != &insert_option_id);
@@ -237,7 +227,7 @@ impl CellDataOperation for MultiSelectTypeOption {
                     }
                 }
 
-                if let Some(delete_option_id) = select_option_changeset.delete_option_id {
+                if let Some(delete_option_id) = content_changeset.delete_option_id {
                     tracing::trace!("Delete multi select option: {}", &delete_option_id);
                     selected_options.retain(|id| id != &delete_option_id);
                 }
@@ -347,68 +337,33 @@ impl TryInto<SelectOptionChangeset> for SelectOptionChangesetPayload {
 #[derive(Clone, Debug, Default, ProtoBuf)]
 pub struct SelectOptionCellChangesetPayload {
     #[pb(index = 1)]
-    pub grid_id: String,
-
-    #[pb(index = 2)]
-    pub row_id: String,
-
-    #[pb(index = 3)]
-    pub field_id: String,
+    pub cell_identifier: CellIdentifierPayload,
 
-    #[pb(index = 4, one_of)]
+    #[pb(index = 2, one_of)]
     pub insert_option_id: Option<String>,
 
-    #[pb(index = 5, one_of)]
+    #[pb(index = 3, one_of)]
     pub delete_option_id: Option<String>,
 }
 
 pub struct SelectOptionCellChangesetParams {
-    pub grid_id: String,
-    pub field_id: String,
-    pub row_id: String,
-    pub insert_option_id: Option<String>,
-
-    pub delete_option_id: Option<String>,
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-pub struct SelectOptionCellChangeset {
+    pub cell_identifier: CellIdentifier,
     pub insert_option_id: Option<String>,
     pub delete_option_id: Option<String>,
 }
 
-impl SelectOptionCellChangeset {
-    pub fn from_insert(option_id: &str) -> Self {
-        SelectOptionCellChangeset {
-            insert_option_id: Some(option_id.to_string()),
-            delete_option_id: None,
-        }
-    }
-
-    pub fn from_delete(option_id: &str) -> Self {
-        SelectOptionCellChangeset {
-            insert_option_id: None,
-            delete_option_id: Some(option_id.to_string()),
-        }
-    }
-
-    pub fn cell_data(&self) -> String {
-        serde_json::to_string(self).unwrap()
-    }
-}
-
 impl std::convert::From<SelectOptionCellChangesetParams> for CellChangeset {
     fn from(params: SelectOptionCellChangesetParams) -> Self {
-        let changeset = SelectOptionCellChangeset {
+        let changeset = SelectOptionCellContentChangeset {
             insert_option_id: params.insert_option_id,
             delete_option_id: params.delete_option_id,
         };
         let s = serde_json::to_string(&changeset).unwrap();
         CellChangeset {
-            grid_id: params.grid_id,
-            row_id: params.row_id,
-            field_id: params.field_id,
-            data: Some(s),
+            grid_id: params.cell_identifier.grid_id,
+            row_id: params.cell_identifier.row_id,
+            field_id: params.cell_identifier.field_id,
+            cell_content_changeset: Some(s),
         }
     }
 }
@@ -417,9 +372,7 @@ impl TryInto<SelectOptionCellChangesetParams> for SelectOptionCellChangesetPaylo
     type Error = ErrorCode;
 
     fn try_into(self) -> Result<SelectOptionCellChangesetParams, Self::Error> {
-        let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
-        let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
-        let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
+        let cell_identifier: CellIdentifier = self.cell_identifier.try_into()?;
         let insert_option_id = match self.insert_option_id {
             None => None,
             Some(insert_option_id) => Some(
@@ -439,17 +392,41 @@ impl TryInto<SelectOptionCellChangesetParams> for SelectOptionCellChangesetPaylo
         };
 
         Ok(SelectOptionCellChangesetParams {
-            grid_id: grid_id.0,
-            row_id: row_id.0,
-            field_id: field_id.0,
+            cell_identifier,
             insert_option_id,
             delete_option_id,
         })
     }
 }
 
+#[derive(Clone, Serialize, Deserialize)]
+pub struct SelectOptionCellContentChangeset {
+    pub insert_option_id: Option<String>,
+    pub delete_option_id: Option<String>,
+}
+
+impl SelectOptionCellContentChangeset {
+    pub fn from_insert(option_id: &str) -> Self {
+        SelectOptionCellContentChangeset {
+            insert_option_id: Some(option_id.to_string()),
+            delete_option_id: None,
+        }
+    }
+
+    pub fn from_delete(option_id: &str) -> Self {
+        SelectOptionCellContentChangeset {
+            insert_option_id: None,
+            delete_option_id: Some(option_id.to_string()),
+        }
+    }
+
+    pub fn to_str(&self) -> String {
+        serde_json::to_string(self).unwrap()
+    }
+}
+
 #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct SelectOptionContext {
+pub struct SelectOptionCellData {
     #[pb(index = 1)]
     pub options: Vec<SelectOption>,
 
@@ -512,7 +489,7 @@ fn make_select_context_from(cell_meta: &Option<CellMeta>, options: &[SelectOptio
 mod tests {
     use crate::services::field::FieldBuilder;
     use crate::services::field::{
-        MultiSelectTypeOption, MultiSelectTypeOptionBuilder, SelectOption, SelectOptionCellChangeset,
+        MultiSelectTypeOption, MultiSelectTypeOptionBuilder, SelectOption, SelectOptionCellContentChangeset,
         SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR,
     };
     use crate::services::row::CellDataOperation;
@@ -535,25 +512,31 @@ mod tests {
         let type_option = SingleSelectTypeOption::from(&field_meta);
 
         let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR);
-        let data = SelectOptionCellChangeset::from_insert(&option_ids).cell_data();
+        let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
         let cell_data = type_option.apply_changeset(data, None).unwrap();
-        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta), google_option.name,);
+        assert_eq!(
+            type_option.decode_cell_data(cell_data, &field_meta).content,
+            google_option.name,
+        );
 
-        let data = SelectOptionCellChangeset::from_insert(&google_option.id).cell_data();
+        let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
         let cell_data = type_option.apply_changeset(data, None).unwrap();
-        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta), google_option.name,);
+        assert_eq!(
+            type_option.decode_cell_data(cell_data, &field_meta).content,
+            google_option.name,
+        );
 
         // Invalid option id
         let cell_data = type_option
-            .apply_changeset(SelectOptionCellChangeset::from_insert("").cell_data(), None)
+            .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
             .unwrap();
-        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta), "",);
+        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",);
 
         // Invalid option id
         let cell_data = type_option
-            .apply_changeset(SelectOptionCellChangeset::from_insert("123").cell_data(), None)
+            .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None)
             .unwrap();
-        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta), "",);
+        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",);
 
         // Invalid changeset
         assert!(type_option.apply_changeset("123", None).is_err());
@@ -577,28 +560,31 @@ mod tests {
         let type_option = MultiSelectTypeOption::from(&field_meta);
 
         let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
-        let data = SelectOptionCellChangeset::from_insert(&option_ids).cell_data();
+        let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
         let cell_data = type_option.apply_changeset(data, None).unwrap();
         assert_eq!(
-            type_option.decode_cell_data(cell_data, &field_meta),
+            type_option.decode_cell_data(cell_data, &field_meta).content,
             vec![google_option.name.clone(), facebook_option.name].join(SELECTION_IDS_SEPARATOR),
         );
 
-        let data = SelectOptionCellChangeset::from_insert(&google_option.id).cell_data();
+        let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
         let cell_data = type_option.apply_changeset(data, None).unwrap();
-        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta), google_option.name,);
+        assert_eq!(
+            type_option.decode_cell_data(cell_data, &field_meta).content,
+            google_option.name,
+        );
 
         // Invalid option id
         let cell_data = type_option
-            .apply_changeset(SelectOptionCellChangeset::from_insert("").cell_data(), None)
+            .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
             .unwrap();
-        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta), "",);
+        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",);
 
         // Invalid option id
         let cell_data = type_option
-            .apply_changeset(SelectOptionCellChangeset::from_insert("123,456").cell_data(), None)
+            .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None)
             .unwrap();
-        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta), "",);
+        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",);
 
         // Invalid changeset
         assert!(type_option.apply_changeset("123", None).is_err());

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

@@ -1,6 +1,8 @@
 use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{decode_cell_data, CellDataChangeset, CellDataOperation, TypeOptionCellData};
+use crate::services::row::{
+    decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData,
+};
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::FlowyError;
@@ -33,23 +35,23 @@ pub struct RichTextTypeOption {
 impl_type_option!(RichTextTypeOption, FieldType::RichText);
 
 impl CellDataOperation for RichTextTypeOption {
-    fn decode_cell_data(&self, data: String, field_meta: &FieldMeta) -> String {
+    fn decode_cell_data(&self, data: String, field_meta: &FieldMeta) -> DecodedCellData {
         if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
             if type_option_cell_data.is_date()
                 || type_option_cell_data.is_single_select()
                 || type_option_cell_data.is_multi_select()
                 || type_option_cell_data.is_number()
             {
-                decode_cell_data(data, field_meta, &type_option_cell_data.field_type).unwrap_or_else(|| "".to_owned())
+                decode_cell_data(data, field_meta, &type_option_cell_data.field_type).unwrap_or_default()
             } else {
-                type_option_cell_data.data
+                DecodedCellData::from_content(type_option_cell_data.data)
             }
         } else {
-            String::new()
+            DecodedCellData::default()
         }
     }
 
-    fn apply_changeset<T: Into<CellDataChangeset>>(
+    fn apply_changeset<T: Into<CellContentChangeset>>(
         &self,
         changeset: T,
         _cell_meta: Option<CellMeta>,
@@ -76,9 +78,10 @@ mod tests {
 
         // date
         let date_time_field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
-        let data = TypeOptionCellData::new("1647251762", FieldType::DateTime).json();
+        let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(1647251762, None)).unwrap();
+        let data = TypeOptionCellData::new(&json, FieldType::DateTime).json();
         assert_eq!(
-            type_option.decode_cell_data(data, &date_time_field_meta),
+            type_option.decode_cell_data(data, &date_time_field_meta).content,
             "Mar 14,2022".to_owned()
         );
 
@@ -89,7 +92,9 @@ mod tests {
         let single_select_field_meta = FieldBuilder::new(single_select).build();
         let cell_data = TypeOptionCellData::new(&done_option_id, FieldType::SingleSelect).json();
         assert_eq!(
-            type_option.decode_cell_data(cell_data, &single_select_field_meta),
+            type_option
+                .decode_cell_data(cell_data, &single_select_field_meta)
+                .content,
             "Done".to_owned()
         );
 
@@ -97,7 +102,7 @@ mod tests {
         let google_option = SelectOption::new("Google");
         let facebook_option = SelectOption::new("Facebook");
         let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
-        let cell_data_changeset = SelectOptionCellChangeset::from_insert(&ids).cell_data();
+        let cell_data_changeset = SelectOptionCellContentChangeset::from_insert(&ids).to_str();
         let multi_select = MultiSelectTypeOptionBuilder::default()
             .option(google_option)
             .option(facebook_option);
@@ -105,7 +110,9 @@ mod tests {
         let multi_type_option = MultiSelectTypeOption::from(&multi_select_field_meta);
         let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap();
         assert_eq!(
-            type_option.decode_cell_data(cell_data, &multi_select_field_meta),
+            type_option
+                .decode_cell_data(cell_data, &multi_select_field_meta)
+                .content,
             "Google,Facebook".to_owned()
         );
 
@@ -114,7 +121,7 @@ mod tests {
         let number_field_meta = FieldBuilder::new(number).build();
         let data = TypeOptionCellData::new("18443", FieldType::Number).json();
         assert_eq!(
-            type_option.decode_cell_data(data, &number_field_meta),
+            type_option.decode_cell_data(data, &number_field_meta).content,
             "$18,443".to_owned()
         );
     }

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

@@ -1 +0,0 @@
-

+ 10 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/util.rs

@@ -0,0 +1,10 @@
+use crate::services::row::TypeOptionCellData;
+use flowy_grid_data_model::entities::CellMeta;
+use std::str::FromStr;
+
+pub fn get_cell_data(cell_meta: &CellMeta) -> String {
+    match TypeOptionCellData::from_str(&cell_meta.data) {
+        Ok(type_option) => type_option.data,
+        Err(_) => String::new(),
+    }
+}

+ 56 - 13
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -93,6 +93,34 @@ impl ClientGridEditor {
         Ok(())
     }
 
+    pub async fn update_field_type_option(
+        &self,
+        grid_id: &str,
+        field_id: &str,
+        type_option_data: Vec<u8>,
+    ) -> FlowyResult<()> {
+        let result = self.get_field_meta(field_id).await;
+        if result.is_none() {
+            tracing::warn!("Can't find the field with id: {}", field_id);
+            return Ok(());
+        }
+        let field_meta = result.unwrap();
+        let _ = self
+            .modify(|grid| {
+                let deserializer = TypeOptionJsonDeserializer(field_meta.field_type.clone());
+                let changeset = FieldChangesetParams {
+                    field_id: field_id.to_owned(),
+                    grid_id: grid_id.to_owned(),
+                    type_option_data: Some(type_option_data),
+                    ..Default::default()
+                };
+                Ok(grid.update_field_meta(changeset, deserializer)?)
+            })
+            .await?;
+        let _ = self.notify_did_update_grid_field(field_id).await?;
+        Ok(())
+    }
+
     pub async fn create_next_field_meta(&self, field_type: &FieldType) -> FlowyResult<FieldMeta> {
         let name = format!("Property {}", self.grid_pad.read().await.fields().len() + 1);
         let field_meta = FieldBuilder::from_field_type(field_type).name(&name).build();
@@ -309,30 +337,45 @@ impl ClientGridEditor {
     }
 
     #[tracing::instrument(level = "trace", skip_all, err)]
-    pub async fn update_cell(&self, mut changeset: CellChangeset) -> FlowyResult<()> {
-        if changeset.data.as_ref().is_none() {
+    pub async fn update_cell(&self, cell_changeset: CellChangeset) -> FlowyResult<()> {
+        if cell_changeset.cell_content_changeset.as_ref().is_none() {
             return Ok(());
         }
 
-        let cell_data_changeset = changeset.data.unwrap();
-        let cell_meta = self.get_cell_meta(&changeset.row_id, &changeset.field_id).await?;
-        tracing::trace!(
-            "field changeset: id:{} / value:{}",
-            &changeset.field_id,
-            cell_data_changeset
-        );
-        match self.grid_pad.read().await.get_field_meta(&changeset.field_id) {
+        let CellChangeset {
+            grid_id,
+            row_id,
+            field_id,
+            mut cell_content_changeset,
+        } = cell_changeset;
+
+        match self.grid_pad.read().await.get_field_meta(&field_id) {
             None => {
-                let msg = format!("Field not found with id: {}", &changeset.field_id);
+                let msg = format!("Field not found with id: {}", &field_id);
                 Err(FlowyError::internal().context(msg))
             }
             Some((_, field_meta)) => {
+                tracing::trace!("field changeset: id:{} / value:{:?}", &field_id, cell_content_changeset);
+
+                let cell_meta = self.get_cell_meta(&row_id, &field_id).await?;
                 // Update the changeset.data property with the return value.
-                changeset.data = Some(apply_cell_data_changeset(cell_data_changeset, cell_meta, field_meta)?);
+                cell_content_changeset = Some(apply_cell_data_changeset(
+                    cell_content_changeset.unwrap(),
+                    cell_meta,
+                    field_meta,
+                )?);
                 let field_metas = self.get_field_metas::<FieldOrder>(None).await?;
+                let cell_changeset = CellChangeset {
+                    grid_id,
+                    row_id,
+                    field_id,
+                    cell_content_changeset,
+                };
                 let _ = self
                     .block_meta_manager
-                    .update_cell(changeset, |row_meta| make_row_from_row_meta(&field_metas, row_meta))
+                    .update_cell(cell_changeset, |row_meta| {
+                        make_row_from_row_meta(&field_metas, row_meta)
+                    })
                     .await?;
                 Ok(())
             }

+ 38 - 28
frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs

@@ -1,13 +1,12 @@
 use crate::services::field::*;
-use std::fmt::Formatter;
-
 use flowy_error::FlowyError;
 use flowy_grid_data_model::entities::{CellMeta, FieldMeta, FieldType};
 use serde::{Deserialize, Serialize};
+use std::fmt::Formatter;
 
 pub trait CellDataOperation {
-    fn decode_cell_data(&self, data: String, field_meta: &FieldMeta) -> String;
-    fn apply_changeset<T: Into<CellDataChangeset>>(
+    fn decode_cell_data(&self, data: String, field_meta: &FieldMeta) -> DecodedCellData;
+    fn apply_changeset<T: Into<CellContentChangeset>>(
         &self,
         changeset: T,
         cell_meta: Option<CellMeta>,
@@ -15,22 +14,22 @@ pub trait CellDataOperation {
 }
 
 #[derive(Debug)]
-pub struct CellDataChangeset(String);
+pub struct CellContentChangeset(String);
 
-impl std::fmt::Display for CellDataChangeset {
+impl std::fmt::Display for CellContentChangeset {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
         write!(f, "{}", &self.0)
     }
 }
 
-impl<T: AsRef<str>> std::convert::From<T> for CellDataChangeset {
+impl<T: AsRef<str>> std::convert::From<T> for CellContentChangeset {
     fn from(s: T) -> Self {
         let s = s.as_ref().to_owned();
-        CellDataChangeset(s)
+        CellContentChangeset(s)
     }
 }
 
-impl std::ops::Deref for CellDataChangeset {
+impl std::ops::Deref for CellContentChangeset {
     type Target = str;
 
     fn deref(&self) -> &Self::Target {
@@ -92,7 +91,7 @@ impl TypeOptionCellData {
 
 /// The changeset will be deserialized into specific data base on the FieldType.
 /// For example, it's String on FieldType::RichText, and SelectOptionChangeset on FieldType::SingleSelect
-pub fn apply_cell_data_changeset<T: Into<CellDataChangeset>>(
+pub fn apply_cell_data_changeset<T: Into<CellContentChangeset>>(
     changeset: T,
     cell_meta: Option<CellMeta>,
     field_meta: &FieldMeta,
@@ -106,23 +105,8 @@ pub fn apply_cell_data_changeset<T: Into<CellDataChangeset>>(
         FieldType::Checkbox => CheckboxTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
     }
 }
-//
-// #[tracing::instrument(level = "trace", skip(field_meta, data), fields(content), err)]
-// pub fn decode_cell_data(data: String, field_meta: &FieldMeta, field_type: &FieldType) -> Result<String, FlowyError> {
-//     let s = match field_meta.field_type {
-//         FieldType::RichText => RichTextTypeOption::from(field_meta).decode_cell_data(data, field_meta),
-//         FieldType::Number => NumberTypeOption::from(field_meta).decode_cell_data(data, field_meta),
-//         FieldType::DateTime => DateTypeOption::from(field_meta).decode_cell_data(data, field_meta),
-//         FieldType::SingleSelect => SingleSelectTypeOption::from(field_meta).decode_cell_data(data, field_meta),
-//         FieldType::MultiSelect => MultiSelectTypeOption::from(field_meta).decode_cell_data(data, field_meta),
-//         FieldType::Checkbox => CheckboxTypeOption::from(field_meta).decode_cell_data(data, field_meta),
-//     };
-//     tracing::Span::current().record("content", &format!("{:?}: {}", field_meta.field_type, s).as_str());
-//     Ok(s)
-// }
-
-#[tracing::instrument(level = "trace", skip(field_meta, data), fields(content))]
-pub fn decode_cell_data(data: String, field_meta: &FieldMeta, field_type: &FieldType) -> Option<String> {
+
+pub fn decode_cell_data(data: String, field_meta: &FieldMeta, field_type: &FieldType) -> Option<DecodedCellData> {
     let s = match field_type {
         FieldType::RichText => field_meta
             .get_type_option_entry::<RichTextTypeOption>(field_type)?
@@ -143,6 +127,32 @@ pub fn decode_cell_data(data: String, field_meta: &FieldMeta, field_type: &Field
             .get_type_option_entry::<CheckboxTypeOption>(field_type)?
             .decode_cell_data(data, field_meta),
     };
-    tracing::Span::current().record("content", &format!("{:?}: {}", field_meta.field_type, s).as_str());
+    tracing::Span::current().record(
+        "content",
+        &format!("{:?}: {}", field_meta.field_type, s.content).as_str(),
+    );
     Some(s)
 }
+
+#[derive(Default)]
+pub struct DecodedCellData {
+    pub raw: String,
+    pub content: String,
+}
+
+impl DecodedCellData {
+    pub fn from_content(content: String) -> Self {
+        Self {
+            raw: content.clone(),
+            content,
+        }
+    }
+
+    pub fn new(raw: String, content: String) -> Self {
+        Self { raw, content }
+    }
+
+    pub fn split(self) -> (String, String) {
+        (self.raw, self.content)
+    }
+}

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

@@ -1,4 +1,4 @@
-use crate::services::field::SelectOptionCellChangeset;
+use crate::services::field::SelectOptionCellContentChangeset;
 use crate::services::row::apply_cell_data_changeset;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{gen_row_id, CellMeta, FieldMeta, RowMeta, DEFAULT_ROW_HEIGHT};
@@ -52,7 +52,7 @@ impl<'a> CreateRowMetaBuilder<'a> {
                 Err(FlowyError::internal().context(msg))
             }
             Some(field_meta) => {
-                let cell_data = SelectOptionCellChangeset::from_insert(&data).cell_data();
+                let cell_data = SelectOptionCellContentChangeset::from_insert(&data).to_str();
                 let data = apply_cell_data_changeset(&cell_data, None, field_meta)?;
                 let cell = CellMeta::new(data);
                 self.payload.cell_by_field_id.insert(field_id.to_owned(), cell);

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

@@ -31,16 +31,15 @@ pub fn make_cell_by_field_id(
     cell_meta: CellMeta,
 ) -> Option<(String, Cell)> {
     let field_meta = field_map.get(&field_id)?;
-    let content = decode_cell_data(cell_meta.data, field_meta, &field_meta.field_type)?;
-    let cell = Cell::new(&field_id, content);
+    let (raw, content) = decode_cell_data(cell_meta.data, field_meta, &field_meta.field_type)?.split();
+    let cell = Cell::new(&field_id, content, raw);
     Some((field_id, cell))
 }
 
-#[allow(dead_code)]
 pub fn make_cell(field_id: &str, field_meta: &FieldMeta, row_meta: &RowMeta) -> Option<Cell> {
     let cell_meta = row_meta.cells.get(field_id)?.clone();
-    let content = decode_cell_data(cell_meta.data, field_meta, &field_meta.field_type)?;
-    Some(Cell::new(field_id, content))
+    let (raw, content) = decode_cell_data(cell_meta.data, field_meta, &field_meta.field_type)?.split();
+    Some(Cell::new(field_id, content, raw))
 }
 
 pub(crate) fn make_row_orders_from_row_metas(row_metas: &[Arc<RowMeta>]) -> Vec<RowOrder> {

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

@@ -2,7 +2,8 @@ use crate::grid::script::EditorScript::*;
 use crate::grid::script::*;
 use chrono::NaiveDateTime;
 use flowy_grid::services::field::{
-    MultiSelectTypeOption, SelectOption, SelectOptionCellChangeset, SingleSelectTypeOption, SELECTION_IDS_SEPARATOR,
+    DateCellContentChangeset, MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset,
+    SingleSelectTypeOption, SELECTION_IDS_SEPARATOR,
 };
 use flowy_grid::services::row::{decode_cell_data, CreateRowMetaBuilder};
 use flowy_grid_data_model::entities::{
@@ -239,7 +240,9 @@ async fn grid_row_add_cells_test() {
                 builder.add_cell(&field.id, "18,443".to_owned()).unwrap();
             }
             FieldType::DateTime => {
-                builder.add_cell(&field.id, "1647251762".to_owned()).unwrap();
+                builder
+                    .add_cell(&field.id, make_date_cell_string("1647251762"))
+                    .unwrap();
             }
             FieldType::SingleSelect => {
                 let type_option = SingleSelectTypeOption::from(field);
@@ -277,17 +280,21 @@ async fn grid_row_add_date_cell_test() {
             date_field = Some(field.clone());
             NaiveDateTime::from_timestamp(123, 0);
             // The data should not be empty
-            assert!(builder.add_cell(&field.id, "".to_owned()).is_err());
-
-            assert!(builder.add_cell(&field.id, "123".to_owned()).is_ok());
-            assert!(builder.add_cell(&field.id, format!("{}", timestamp)).is_ok());
+            assert!(builder.add_cell(&field.id, "".to_string()).is_err());
+            assert!(builder.add_cell(&field.id, make_date_cell_string("123")).is_ok());
+            assert!(builder
+                .add_cell(&field.id, make_date_cell_string(&timestamp.to_string()))
+                .is_ok());
         }
     }
     let context = builder.build();
     let date_field = date_field.unwrap();
     let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone();
     assert_eq!(
-        decode_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type).unwrap(),
+        decode_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type)
+            .unwrap()
+            .split()
+            .1,
         "2022/03/16",
     );
     let scripts = vec![CreateRow { context }];
@@ -311,14 +318,14 @@ async fn grid_cell_update() {
                 let data = match field_meta.field_type {
                     FieldType::RichText => "".to_string(),
                     FieldType::Number => "123".to_string(),
-                    FieldType::DateTime => "123".to_string(),
+                    FieldType::DateTime => make_date_cell_string("123"),
                     FieldType::SingleSelect => {
                         let type_option = SingleSelectTypeOption::from(field_meta);
-                        SelectOptionCellChangeset::from_insert(&type_option.options.first().unwrap().id).cell_data()
+                        SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str()
                     }
                     FieldType::MultiSelect => {
                         let type_option = MultiSelectTypeOption::from(field_meta);
-                        SelectOptionCellChangeset::from_insert(&type_option.options.first().unwrap().id).cell_data()
+                        SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str()
                     }
                     FieldType::Checkbox => "1".to_string(),
                 };
@@ -328,7 +335,7 @@ async fn grid_cell_update() {
                         grid_id: block_id.to_string(),
                         row_id: row_meta.id.clone(),
                         field_id: field_meta.id.clone(),
-                        data: Some(data),
+                        cell_content_changeset: Some(data),
                     },
                     is_err: false,
                 });
@@ -339,8 +346,8 @@ async fn grid_cell_update() {
                     FieldType::RichText => ("1".to_string().repeat(10001), true),
                     FieldType::Number => ("abc".to_string(), true),
                     FieldType::DateTime => ("abc".to_string(), true),
-                    FieldType::SingleSelect => (SelectOptionCellChangeset::from_insert("abc").cell_data(), false),
-                    FieldType::MultiSelect => (SelectOptionCellChangeset::from_insert("abc").cell_data(), false),
+                    FieldType::SingleSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false),
+                    FieldType::MultiSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false),
                     FieldType::Checkbox => ("2".to_string(), false),
                 };
 
@@ -349,7 +356,7 @@ async fn grid_cell_update() {
                         grid_id: block_id.to_string(),
                         row_id: row_meta.id.clone(),
                         field_id: field_meta.id.clone(),
-                        data: Some(data),
+                        cell_content_changeset: Some(data),
                     },
                     is_err,
                 });
@@ -359,3 +366,11 @@ async fn grid_cell_update() {
 
     test.run_scripts(scripts).await;
 }
+
+fn make_date_cell_string(s: &str) -> String {
+    serde_json::to_string(&DateCellContentChangeset {
+        date: Some(s.to_string()),
+        time: None,
+    })
+    .unwrap()
+}

+ 4 - 1
shared-lib/flowy-error-code/src/code.rs

@@ -111,8 +111,11 @@ pub enum ErrorCode {
     #[display(fmt = "Field's type option data should not be empty")]
     TypeOptionDataIsEmpty = 450,
 
+    #[display(fmt = "Invalid date time format")]
+    InvalidDateTimeFormat = 500,
+
     #[display(fmt = "Invalid data")]
-    InvalidData = 500,
+    InvalidData = 1000,
 }
 
 impl ErrorCode {

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

@@ -67,7 +67,8 @@ pub enum ErrorCode {
     FieldNotExists = 443,
     FieldInvalidOperation = 444,
     TypeOptionDataIsEmpty = 450,
-    InvalidData = 500,
+    InvalidDateTimeFormat = 500,
+    InvalidData = 1000,
 }
 
 impl ::protobuf::ProtobufEnum for ErrorCode {
@@ -119,7 +120,8 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             443 => ::std::option::Option::Some(ErrorCode::FieldNotExists),
             444 => ::std::option::Option::Some(ErrorCode::FieldInvalidOperation),
             450 => ::std::option::Option::Some(ErrorCode::TypeOptionDataIsEmpty),
-            500 => ::std::option::Option::Some(ErrorCode::InvalidData),
+            500 => ::std::option::Option::Some(ErrorCode::InvalidDateTimeFormat),
+            1000 => ::std::option::Option::Some(ErrorCode::InvalidData),
             _ => ::std::option::Option::None
         }
     }
@@ -168,6 +170,7 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             ErrorCode::FieldNotExists,
             ErrorCode::FieldInvalidOperation,
             ErrorCode::TypeOptionDataIsEmpty,
+            ErrorCode::InvalidDateTimeFormat,
             ErrorCode::InvalidData,
         ];
         values
@@ -197,7 +200,7 @@ impl ::protobuf::reflect::ProtobufValue for ErrorCode {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\ncode.proto*\xe5\x07\n\tErrorCode\x12\x0c\n\x08Internal\x10\0\x12\x14\
+    \n\ncode.proto*\x81\x08\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\
@@ -220,8 +223,9 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x12\x13\n\x0eFieldIdIsEmpty\x10\xb8\x03\x12\x16\n\x11FieldDoesNotExist\
     \x10\xb9\x03\x12\x1c\n\x17SelectOptionNameIsEmpty\x10\xba\x03\x12\x13\n\
     \x0eFieldNotExists\x10\xbb\x03\x12\x1a\n\x15FieldInvalidOperation\x10\
-    \xbc\x03\x12\x1a\n\x15TypeOptionDataIsEmpty\x10\xc2\x03\x12\x10\n\x0bInv\
-    alidData\x10\xf4\x03b\x06proto3\
+    \xbc\x03\x12\x1a\n\x15TypeOptionDataIsEmpty\x10\xc2\x03\x12\x1a\n\x15Inv\
+    alidDateTimeFormat\x10\xf4\x03\x12\x10\n\x0bInvalidData\x10\xe8\x07b\x06\
+    proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -43,5 +43,6 @@ enum ErrorCode {
     FieldNotExists = 443;
     FieldInvalidOperation = 444;
     TypeOptionDataIsEmpty = 450;
-    InvalidData = 500;
+    InvalidDateTimeFormat = 500;
+    InvalidData = 1000;
 }

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

@@ -167,8 +167,8 @@ pub struct EditFieldPayload {
     #[pb(index = 1)]
     pub grid_id: String,
 
-    #[pb(index = 2)]
-    pub field_id: String,
+    #[pb(index = 2, one_of)]
+    pub field_id: Option<String>,
 
     #[pb(index = 3)]
     pub field_type: FieldType,
@@ -176,7 +176,7 @@ pub struct EditFieldPayload {
 
 pub struct EditFieldParams {
     pub grid_id: String,
-    pub field_id: String,
+    pub field_id: Option<String>,
     pub field_type: FieldType,
 }
 
@@ -185,10 +185,10 @@ impl TryInto<EditFieldParams> for EditFieldPayload {
 
     fn try_into(self) -> Result<EditFieldParams, Self::Error> {
         let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
-        let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
+        // let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
         Ok(EditFieldParams {
             grid_id: grid_id.0,
-            field_id: field_id.0,
+            field_id: self.field_id,
             field_type: self.field_type,
         })
     }
@@ -206,6 +206,15 @@ pub struct EditFieldContext {
     pub type_option_data: Vec<u8>,
 }
 
+#[derive(Debug, Default, ProtoBuf)]
+pub struct FieldTypeOptionData {
+    #[pb(index = 1)]
+    pub field_id: String,
+
+    #[pb(index = 2)]
+    pub type_option_data: Vec<u8>,
+}
+
 #[derive(Debug, Default, ProtoBuf)]
 pub struct RepeatedField {
     #[pb(index = 1)]
@@ -452,13 +461,25 @@ pub struct Cell {
 
     #[pb(index = 2)]
     pub content: String,
+
+    #[pb(index = 3)]
+    pub data: String,
 }
 
 impl Cell {
-    pub fn new(field_id: &str, content: String) -> Self {
+    pub fn new(field_id: &str, content: String, data: String) -> Self {
         Self {
             field_id: field_id.to_owned(),
             content,
+            data,
+        }
+    }
+
+    pub fn empty(field_id: &str) -> Self {
+        Self {
+            field_id: field_id.to_owned(),
+            content: "".to_string(),
+            data: "".to_string(),
         }
     }
 }
@@ -595,6 +616,40 @@ impl TryInto<InsertFieldParams> for InsertFieldPayload {
     }
 }
 
+#[derive(ProtoBuf, Default)]
+pub struct UpdateFieldTypeOptionPayload {
+    #[pb(index = 1)]
+    pub grid_id: String,
+
+    #[pb(index = 2)]
+    pub field_id: String,
+
+    #[pb(index = 3)]
+    pub type_option_data: Vec<u8>,
+}
+
+#[derive(Clone)]
+pub struct UpdateFieldTypeOptionParams {
+    pub grid_id: String,
+    pub field_id: String,
+    pub type_option_data: Vec<u8>,
+}
+
+impl TryInto<UpdateFieldTypeOptionParams> for UpdateFieldTypeOptionPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<UpdateFieldTypeOptionParams, Self::Error> {
+        let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+        let _ = NotEmptyStr::parse(self.field_id.clone()).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
+
+        Ok(UpdateFieldTypeOptionParams {
+            grid_id: grid_id.0,
+            field_id: self.field_id,
+            type_option_data: self.type_option_data,
+        })
+    }
+}
+
 #[derive(ProtoBuf, Default)]
 pub struct QueryFieldPayload {
     #[pb(index = 1)]
@@ -847,7 +902,7 @@ pub struct CellChangeset {
     pub field_id: String,
 
     #[pb(index = 4, one_of)]
-    pub data: Option<String>,
+    pub cell_content_changeset: Option<String>,
 }
 
 impl std::convert::From<CellChangeset> for RowMetaChangeset {
@@ -855,7 +910,7 @@ impl std::convert::From<CellChangeset> for RowMetaChangeset {
         let mut cell_by_field_id = HashMap::with_capacity(1);
         let field_id = changeset.field_id;
         let cell_meta = CellMeta {
-            data: changeset.data.unwrap_or_else(|| "".to_owned()),
+            data: changeset.cell_content_changeset.unwrap_or_else(|| "".to_owned()),
         };
         cell_by_field_id.insert(field_id, cell_meta);
 

+ 1 - 3
shared-lib/flowy-grid-data-model/src/entities/meta.rs

@@ -95,7 +95,6 @@ pub struct FieldMeta {
 
     pub width: i32,
 
-    // #[pb(index = 8)]
     /// type_options contains key/value pairs
     /// key: id of the FieldType
     /// value: type option data that can be parsed into specified TypeOptionStruct.
@@ -144,8 +143,7 @@ impl FieldMeta {
         self.type_options.insert(field_type.type_id(), json_str);
     }
 
-    pub fn get_type_option_str(&self, field_type: Option<FieldType>) -> Option<String> {
-        let field_type = field_type.as_ref().unwrap_or(&self.field_type);
+    pub fn get_type_option_str(&self, field_type: &FieldType) -> Option<String> {
         self.type_options.get(&field_type.type_id()).map(|s| s.to_owned())
     }
 }

+ 653 - 120
shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs

@@ -1644,8 +1644,9 @@ impl ::protobuf::reflect::ProtobufValue for GetEditFieldContextPayload {
 pub struct EditFieldPayload {
     // message fields
     pub grid_id: ::std::string::String,
-    pub field_id: ::std::string::String,
     pub field_type: FieldType,
+    // message oneof groups
+    pub one_of_field_id: ::std::option::Option<EditFieldPayload_oneof_one_of_field_id>,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -1657,6 +1658,11 @@ impl<'a> ::std::default::Default for &'a EditFieldPayload {
     }
 }
 
+#[derive(Clone,PartialEq,Debug)]
+pub enum EditFieldPayload_oneof_one_of_field_id {
+    field_id(::std::string::String),
+}
+
 impl EditFieldPayload {
     pub fn new() -> EditFieldPayload {
         ::std::default::Default::default()
@@ -1692,26 +1698,49 @@ impl EditFieldPayload {
 
 
     pub fn get_field_id(&self) -> &str {
-        &self.field_id
+        match self.one_of_field_id {
+            ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(ref v)) => v,
+            _ => "",
+        }
     }
     pub fn clear_field_id(&mut self) {
-        self.field_id.clear();
+        self.one_of_field_id = ::std::option::Option::None;
+    }
+
+    pub fn has_field_id(&self) -> bool {
+        match self.one_of_field_id {
+            ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(..)) => true,
+            _ => false,
+        }
     }
 
     // Param is passed by value, moved
     pub fn set_field_id(&mut self, v: ::std::string::String) {
-        self.field_id = v;
+        self.one_of_field_id = ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(v))
     }
 
     // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
     pub fn mut_field_id(&mut self) -> &mut ::std::string::String {
-        &mut self.field_id
+        if let ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(_)) = self.one_of_field_id {
+        } else {
+            self.one_of_field_id = ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(::std::string::String::new()));
+        }
+        match self.one_of_field_id {
+            ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(ref mut v)) => v,
+            _ => panic!(),
+        }
     }
 
     // Take field
     pub fn take_field_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.field_id, ::std::string::String::new())
+        if self.has_field_id() {
+            match self.one_of_field_id.take() {
+                ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
     }
 
     // .FieldType field_type = 3;
@@ -1743,7 +1772,10 @@ impl ::protobuf::Message for EditFieldPayload {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?;
                 },
                 2 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?;
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_field_id = ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(is.read_string()?));
                 },
                 3 => {
                     ::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.field_type, 3, &mut self.unknown_fields)?
@@ -1763,12 +1795,16 @@ impl ::protobuf::Message for EditFieldPayload {
         if !self.grid_id.is_empty() {
             my_size += ::protobuf::rt::string_size(1, &self.grid_id);
         }
-        if !self.field_id.is_empty() {
-            my_size += ::protobuf::rt::string_size(2, &self.field_id);
-        }
         if self.field_type != FieldType::RichText {
             my_size += ::protobuf::rt::enum_size(3, self.field_type);
         }
+        if let ::std::option::Option::Some(ref v) = self.one_of_field_id {
+            match v {
+                &EditFieldPayload_oneof_one_of_field_id::field_id(ref v) => {
+                    my_size += ::protobuf::rt::string_size(2, &v);
+                },
+            };
+        }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
         my_size
@@ -1778,12 +1814,16 @@ impl ::protobuf::Message for EditFieldPayload {
         if !self.grid_id.is_empty() {
             os.write_string(1, &self.grid_id)?;
         }
-        if !self.field_id.is_empty() {
-            os.write_string(2, &self.field_id)?;
-        }
         if self.field_type != FieldType::RichText {
             os.write_enum(3, ::protobuf::ProtobufEnum::value(&self.field_type))?;
         }
+        if let ::std::option::Option::Some(ref v) = self.one_of_field_id {
+            match v {
+                &EditFieldPayload_oneof_one_of_field_id::field_id(ref v) => {
+                    os.write_string(2, v)?;
+                },
+            };
+        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -1827,10 +1867,10 @@ impl ::protobuf::Message for EditFieldPayload {
                 |m: &EditFieldPayload| { &m.grid_id },
                 |m: &mut EditFieldPayload| { &mut m.grid_id },
             ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
                 "field_id",
-                |m: &EditFieldPayload| { &m.field_id },
-                |m: &mut EditFieldPayload| { &mut m.field_id },
+                EditFieldPayload::has_field_id,
+                EditFieldPayload::get_field_id,
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum<FieldType>>(
                 "field_type",
@@ -1854,7 +1894,7 @@ impl ::protobuf::Message for EditFieldPayload {
 impl ::protobuf::Clear for EditFieldPayload {
     fn clear(&mut self) {
         self.grid_id.clear();
-        self.field_id.clear();
+        self.one_of_field_id = ::std::option::Option::None;
         self.field_type = FieldType::RichText;
         self.unknown_fields.clear();
     }
@@ -2130,6 +2170,207 @@ impl ::protobuf::reflect::ProtobufValue for EditFieldContext {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct FieldTypeOptionData {
+    // message fields
+    pub field_id: ::std::string::String,
+    pub type_option_data: ::std::vec::Vec<u8>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a FieldTypeOptionData {
+    fn default() -> &'a FieldTypeOptionData {
+        <FieldTypeOptionData as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl FieldTypeOptionData {
+    pub fn new() -> FieldTypeOptionData {
+        ::std::default::Default::default()
+    }
+
+    // string field_id = 1;
+
+
+    pub fn get_field_id(&self) -> &str {
+        &self.field_id
+    }
+    pub fn clear_field_id(&mut self) {
+        self.field_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_field_id(&mut self, v: ::std::string::String) {
+        self.field_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_field_id(&mut self) -> &mut ::std::string::String {
+        &mut self.field_id
+    }
+
+    // Take field
+    pub fn take_field_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.field_id, ::std::string::String::new())
+    }
+
+    // bytes type_option_data = 2;
+
+
+    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())
+    }
+}
+
+impl ::protobuf::Message for FieldTypeOptionData {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.type_option_data)?;
+                },
+                _ => {
+                    ::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.field_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.field_id);
+        }
+        if !self.type_option_data.is_empty() {
+            my_size += ::protobuf::rt::bytes_size(2, &self.type_option_data);
+        }
+        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.field_id.is_empty() {
+            os.write_string(1, &self.field_id)?;
+        }
+        if !self.type_option_data.is_empty() {
+            os.write_bytes(2, &self.type_option_data)?;
+        }
+        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() -> FieldTypeOptionData {
+        FieldTypeOptionData::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>(
+                "field_id",
+                |m: &FieldTypeOptionData| { &m.field_id },
+                |m: &mut FieldTypeOptionData| { &mut m.field_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
+                "type_option_data",
+                |m: &FieldTypeOptionData| { &m.type_option_data },
+                |m: &mut FieldTypeOptionData| { &mut m.type_option_data },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<FieldTypeOptionData>(
+                "FieldTypeOptionData",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static FieldTypeOptionData {
+        static instance: ::protobuf::rt::LazyV2<FieldTypeOptionData> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(FieldTypeOptionData::new)
+    }
+}
+
+impl ::protobuf::Clear for FieldTypeOptionData {
+    fn clear(&mut self) {
+        self.field_id.clear();
+        self.type_option_data.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for FieldTypeOptionData {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for FieldTypeOptionData {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(PartialEq,Clone,Default)]
 pub struct RepeatedField {
     // message fields
@@ -4451,6 +4692,7 @@ pub struct Cell {
     // message fields
     pub field_id: ::std::string::String,
     pub content: ::std::string::String,
+    pub data: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -4518,6 +4760,32 @@ impl Cell {
     pub fn take_content(&mut self) -> ::std::string::String {
         ::std::mem::replace(&mut self.content, ::std::string::String::new())
     }
+
+    // string data = 3;
+
+
+    pub fn get_data(&self) -> &str {
+        &self.data
+    }
+    pub fn clear_data(&mut self) {
+        self.data.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_data(&mut self, v: ::std::string::String) {
+        self.data = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_data(&mut self) -> &mut ::std::string::String {
+        &mut self.data
+    }
+
+    // Take field
+    pub fn take_data(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.data, ::std::string::String::new())
+    }
 }
 
 impl ::protobuf::Message for Cell {
@@ -4535,6 +4803,9 @@ impl ::protobuf::Message for Cell {
                 2 => {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.content)?;
                 },
+                3 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?;
+                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -4553,6 +4824,9 @@ impl ::protobuf::Message for Cell {
         if !self.content.is_empty() {
             my_size += ::protobuf::rt::string_size(2, &self.content);
         }
+        if !self.data.is_empty() {
+            my_size += ::protobuf::rt::string_size(3, &self.data);
+        }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
         my_size
@@ -4565,6 +4839,9 @@ impl ::protobuf::Message for Cell {
         if !self.content.is_empty() {
             os.write_string(2, &self.content)?;
         }
+        if !self.data.is_empty() {
+            os.write_string(3, &self.data)?;
+        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -4613,6 +4890,11 @@ impl ::protobuf::Message for Cell {
                 |m: &Cell| { &m.content },
                 |m: &mut Cell| { &mut m.content },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "data",
+                |m: &Cell| { &m.data },
+                |m: &mut Cell| { &mut m.data },
+            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<Cell>(
                 "Cell",
                 fields,
@@ -4631,6 +4913,7 @@ impl ::protobuf::Clear for Cell {
     fn clear(&mut self) {
         self.field_id.clear();
         self.content.clear();
+        self.data.clear();
         self.unknown_fields.clear();
     }
 }
@@ -5871,6 +6154,249 @@ impl ::protobuf::reflect::ProtobufValue for InsertFieldPayload {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct UpdateFieldTypeOptionPayload {
+    // message fields
+    pub grid_id: ::std::string::String,
+    pub field_id: ::std::string::String,
+    pub type_option_data: ::std::vec::Vec<u8>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a UpdateFieldTypeOptionPayload {
+    fn default() -> &'a UpdateFieldTypeOptionPayload {
+        <UpdateFieldTypeOptionPayload as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl UpdateFieldTypeOptionPayload {
+    pub fn new() -> UpdateFieldTypeOptionPayload {
+        ::std::default::Default::default()
+    }
+
+    // string grid_id = 1;
+
+
+    pub fn get_grid_id(&self) -> &str {
+        &self.grid_id
+    }
+    pub fn clear_grid_id(&mut self) {
+        self.grid_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_grid_id(&mut self, v: ::std::string::String) {
+        self.grid_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_grid_id(&mut self) -> &mut ::std::string::String {
+        &mut self.grid_id
+    }
+
+    // Take field
+    pub fn take_grid_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.grid_id, ::std::string::String::new())
+    }
+
+    // string field_id = 2;
+
+
+    pub fn get_field_id(&self) -> &str {
+        &self.field_id
+    }
+    pub fn clear_field_id(&mut self) {
+        self.field_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_field_id(&mut self, v: ::std::string::String) {
+        self.field_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_field_id(&mut self) -> &mut ::std::string::String {
+        &mut self.field_id
+    }
+
+    // Take field
+    pub fn take_field_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.field_id, ::std::string::String::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())
+    }
+}
+
+impl ::protobuf::Message for UpdateFieldTypeOptionPayload {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?;
+                },
+                3 => {
+                    ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.type_option_data)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.grid_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.grid_id);
+        }
+        if !self.field_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.field_id);
+        }
+        if !self.type_option_data.is_empty() {
+            my_size += ::protobuf::rt::bytes_size(3, &self.type_option_data);
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.grid_id.is_empty() {
+            os.write_string(1, &self.grid_id)?;
+        }
+        if !self.field_id.is_empty() {
+            os.write_string(2, &self.field_id)?;
+        }
+        if !self.type_option_data.is_empty() {
+            os.write_bytes(3, &self.type_option_data)?;
+        }
+        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() -> UpdateFieldTypeOptionPayload {
+        UpdateFieldTypeOptionPayload::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: &UpdateFieldTypeOptionPayload| { &m.grid_id },
+                |m: &mut UpdateFieldTypeOptionPayload| { &mut m.grid_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "field_id",
+                |m: &UpdateFieldTypeOptionPayload| { &m.field_id },
+                |m: &mut UpdateFieldTypeOptionPayload| { &mut m.field_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
+                "type_option_data",
+                |m: &UpdateFieldTypeOptionPayload| { &m.type_option_data },
+                |m: &mut UpdateFieldTypeOptionPayload| { &mut m.type_option_data },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<UpdateFieldTypeOptionPayload>(
+                "UpdateFieldTypeOptionPayload",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static UpdateFieldTypeOptionPayload {
+        static instance: ::protobuf::rt::LazyV2<UpdateFieldTypeOptionPayload> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(UpdateFieldTypeOptionPayload::new)
+    }
+}
+
+impl ::protobuf::Clear for UpdateFieldTypeOptionPayload {
+    fn clear(&mut self) {
+        self.grid_id.clear();
+        self.field_id.clear();
+        self.type_option_data.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for UpdateFieldTypeOptionPayload {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for UpdateFieldTypeOptionPayload {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(PartialEq,Clone,Default)]
 pub struct QueryFieldPayload {
     // message fields
@@ -7277,7 +7803,7 @@ pub struct CellChangeset {
     pub row_id: ::std::string::String,
     pub field_id: ::std::string::String,
     // message oneof groups
-    pub one_of_data: ::std::option::Option<CellChangeset_oneof_one_of_data>,
+    pub one_of_cell_content_changeset: ::std::option::Option<CellChangeset_oneof_one_of_cell_content_changeset>,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -7290,8 +7816,8 @@ impl<'a> ::std::default::Default for &'a CellChangeset {
 }
 
 #[derive(Clone,PartialEq,Debug)]
-pub enum CellChangeset_oneof_one_of_data {
-    data(::std::string::String),
+pub enum CellChangeset_oneof_one_of_cell_content_changeset {
+    cell_content_changeset(::std::string::String),
 }
 
 impl CellChangeset {
@@ -7377,48 +7903,48 @@ impl CellChangeset {
         ::std::mem::replace(&mut self.field_id, ::std::string::String::new())
     }
 
-    // string data = 4;
+    // string cell_content_changeset = 4;
 
 
-    pub fn get_data(&self) -> &str {
-        match self.one_of_data {
-            ::std::option::Option::Some(CellChangeset_oneof_one_of_data::data(ref v)) => v,
+    pub fn get_cell_content_changeset(&self) -> &str {
+        match self.one_of_cell_content_changeset {
+            ::std::option::Option::Some(CellChangeset_oneof_one_of_cell_content_changeset::cell_content_changeset(ref v)) => v,
             _ => "",
         }
     }
-    pub fn clear_data(&mut self) {
-        self.one_of_data = ::std::option::Option::None;
+    pub fn clear_cell_content_changeset(&mut self) {
+        self.one_of_cell_content_changeset = ::std::option::Option::None;
     }
 
-    pub fn has_data(&self) -> bool {
-        match self.one_of_data {
-            ::std::option::Option::Some(CellChangeset_oneof_one_of_data::data(..)) => true,
+    pub fn has_cell_content_changeset(&self) -> bool {
+        match self.one_of_cell_content_changeset {
+            ::std::option::Option::Some(CellChangeset_oneof_one_of_cell_content_changeset::cell_content_changeset(..)) => true,
             _ => false,
         }
     }
 
     // Param is passed by value, moved
-    pub fn set_data(&mut self, v: ::std::string::String) {
-        self.one_of_data = ::std::option::Option::Some(CellChangeset_oneof_one_of_data::data(v))
+    pub fn set_cell_content_changeset(&mut self, v: ::std::string::String) {
+        self.one_of_cell_content_changeset = ::std::option::Option::Some(CellChangeset_oneof_one_of_cell_content_changeset::cell_content_changeset(v))
     }
 
     // Mutable pointer to the field.
-    pub fn mut_data(&mut self) -> &mut ::std::string::String {
-        if let ::std::option::Option::Some(CellChangeset_oneof_one_of_data::data(_)) = self.one_of_data {
+    pub fn mut_cell_content_changeset(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(CellChangeset_oneof_one_of_cell_content_changeset::cell_content_changeset(_)) = self.one_of_cell_content_changeset {
         } else {
-            self.one_of_data = ::std::option::Option::Some(CellChangeset_oneof_one_of_data::data(::std::string::String::new()));
+            self.one_of_cell_content_changeset = ::std::option::Option::Some(CellChangeset_oneof_one_of_cell_content_changeset::cell_content_changeset(::std::string::String::new()));
         }
-        match self.one_of_data {
-            ::std::option::Option::Some(CellChangeset_oneof_one_of_data::data(ref mut v)) => v,
+        match self.one_of_cell_content_changeset {
+            ::std::option::Option::Some(CellChangeset_oneof_one_of_cell_content_changeset::cell_content_changeset(ref mut v)) => v,
             _ => panic!(),
         }
     }
 
     // Take field
-    pub fn take_data(&mut self) -> ::std::string::String {
-        if self.has_data() {
-            match self.one_of_data.take() {
-                ::std::option::Option::Some(CellChangeset_oneof_one_of_data::data(v)) => v,
+    pub fn take_cell_content_changeset(&mut self) -> ::std::string::String {
+        if self.has_cell_content_changeset() {
+            match self.one_of_cell_content_changeset.take() {
+                ::std::option::Option::Some(CellChangeset_oneof_one_of_cell_content_changeset::cell_content_changeset(v)) => v,
                 _ => panic!(),
             }
         } else {
@@ -7449,7 +7975,7 @@ impl ::protobuf::Message for CellChangeset {
                     if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
                         return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
                     }
-                    self.one_of_data = ::std::option::Option::Some(CellChangeset_oneof_one_of_data::data(is.read_string()?));
+                    self.one_of_cell_content_changeset = ::std::option::Option::Some(CellChangeset_oneof_one_of_cell_content_changeset::cell_content_changeset(is.read_string()?));
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -7472,9 +7998,9 @@ impl ::protobuf::Message for CellChangeset {
         if !self.field_id.is_empty() {
             my_size += ::protobuf::rt::string_size(3, &self.field_id);
         }
-        if let ::std::option::Option::Some(ref v) = self.one_of_data {
+        if let ::std::option::Option::Some(ref v) = self.one_of_cell_content_changeset {
             match v {
-                &CellChangeset_oneof_one_of_data::data(ref v) => {
+                &CellChangeset_oneof_one_of_cell_content_changeset::cell_content_changeset(ref v) => {
                     my_size += ::protobuf::rt::string_size(4, &v);
                 },
             };
@@ -7494,9 +8020,9 @@ impl ::protobuf::Message for CellChangeset {
         if !self.field_id.is_empty() {
             os.write_string(3, &self.field_id)?;
         }
-        if let ::std::option::Option::Some(ref v) = self.one_of_data {
+        if let ::std::option::Option::Some(ref v) = self.one_of_cell_content_changeset {
             match v {
-                &CellChangeset_oneof_one_of_data::data(ref v) => {
+                &CellChangeset_oneof_one_of_cell_content_changeset::cell_content_changeset(ref v) => {
                     os.write_string(4, v)?;
                 },
             };
@@ -7555,9 +8081,9 @@ impl ::protobuf::Message for CellChangeset {
                 |m: &mut CellChangeset| { &mut m.field_id },
             ));
             fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
-                "data",
-                CellChangeset::has_data,
-                CellChangeset::get_data,
+                "cell_content_changeset",
+                CellChangeset::has_cell_content_changeset,
+                CellChangeset::get_cell_content_changeset,
             ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<CellChangeset>(
                 "CellChangeset",
@@ -7578,7 +8104,7 @@ impl ::protobuf::Clear for CellChangeset {
         self.grid_id.clear();
         self.row_id.clear();
         self.field_id.clear();
-        self.one_of_data = ::std::option::Option::None;
+        self.one_of_cell_content_changeset = ::std::option::Option::None;
         self.unknown_fields.clear();
     }
 }
@@ -7727,76 +8253,83 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x05index\x18\x02\x20\x01(\x05R\x05index\"\x90\x01\n\x1aGetEditFieldCont\
     extPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x1b\n\
     \x08field_id\x18\x02\x20\x01(\tH\0R\x07fieldId\x12)\n\nfield_type\x18\
-    \x03\x20\x01(\x0e2\n.FieldTypeR\tfieldTypeB\x11\n\x0fone_of_field_id\"q\
-    \n\x10EditFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridI\
-    d\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\x07fieldId\x12)\n\nfield_typ\
-    e\x18\x03\x20\x01(\x0e2\n.FieldTypeR\tfieldType\"|\n\x10EditFieldContext\
-    \x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12%\n\ngrid_field\
-    \x18\x02\x20\x01(\x0b2\x06.FieldR\tgridField\x12(\n\x10type_option_data\
-    \x18\x03\x20\x01(\x0cR\x0etypeOptionData\"-\n\rRepeatedField\x12\x1c\n\
-    \x05items\x18\x01\x20\x03(\x0b2\x06.FieldR\x05items\"7\n\x12RepeatedFiel\
-    dOrder\x12!\n\x05items\x18\x01\x20\x03(\x0b2\x0b.FieldOrderR\x05items\"T\
-    \n\x08RowOrder\x12\x15\n\x06row_id\x18\x01\x20\x01(\tR\x05rowId\x12\x19\
-    \n\x08block_id\x18\x02\x20\x01(\tR\x07blockId\x12\x16\n\x06height\x18\
-    \x03\x20\x01(\x05R\x06height\"\xb8\x01\n\x03Row\x12\x0e\n\x02id\x18\x01\
-    \x20\x01(\tR\x02id\x12@\n\x10cell_by_field_id\x18\x02\x20\x03(\x0b2\x17.\
-    Row.CellByFieldIdEntryR\rcellByFieldId\x12\x16\n\x06height\x18\x03\x20\
-    \x01(\x05R\x06height\x1aG\n\x12CellByFieldIdEntry\x12\x10\n\x03key\x18\
-    \x01\x20\x01(\tR\x03key\x12\x1b\n\x05value\x18\x02\x20\x01(\x0b2\x05.Cel\
-    lR\x05value:\x028\x01\")\n\x0bRepeatedRow\x12\x1a\n\x05items\x18\x01\x20\
-    \x03(\x0b2\x04.RowR\x05items\"5\n\x11RepeatedGridBlock\x12\x20\n\x05item\
-    s\x18\x01\x20\x03(\x0b2\n.GridBlockR\x05items\"U\n\x0eGridBlockOrder\x12\
-    \x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\x12(\n\nrow_orders\x18\
-    \x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\"_\n\rIndexRowOrder\x12&\n\tro\
-    w_order\x18\x01\x20\x01(\x0b2\t.RowOrderR\x08rowOrder\x12\x16\n\x05index\
-    \x18\x02\x20\x01(\x05H\0R\x05indexB\x0e\n\x0cone_of_index\"Q\n\x0fUpdate\
-    dRowOrder\x12&\n\trow_order\x18\x01\x20\x01(\x0b2\t.RowOrderR\x08rowOrde\
-    r\x12\x16\n\x03row\x18\x02\x20\x01(\x0b2\x04.RowR\x03row\"\xc6\x01\n\x11\
-    GridRowsChangeset\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\
-    \x123\n\rinserted_rows\x18\x02\x20\x03(\x0b2\x0e.IndexRowOrderR\x0cinser\
-    tedRows\x12,\n\x0cdeleted_rows\x18\x03\x20\x03(\x0b2\t.RowOrderR\x0bdele\
-    tedRows\x123\n\x0cupdated_rows\x18\x04\x20\x03(\x0b2\x10.UpdatedRowOrder\
-    R\x0bupdatedRows\"E\n\tGridBlock\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02\
-    id\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\";\n\
-    \x04Cell\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x18\n\
-    \x07content\x18\x02\x20\x01(\tR\x07content\"+\n\x0cRepeatedCell\x12\x1b\
-    \n\x05items\x18\x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11CreateGridP\
-    ayload\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\
-    \x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlockId\x12\
-    \x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayload\
-    \x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_row\
-    _id\x18\x02\x20\x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\"\
-    \xb6\x01\n\x12InsertFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\t\
-    R\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\x15on\
-    e_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_orde\
-    rs\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrders\"\xa8\x03\n\
-    \x15FieldChangesetPayload\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07f\
-    ieldId\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04n\
-    ame\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\x10type_option_data\x18\
-    \t\x20\x01(\x0cH\x06R\x0etypeOptionDataB\r\n\x0bone_of_nameB\r\n\x0bone_\
-    of_descB\x13\n\x11one_of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_\
-    of_visibilityB\x0e\n\x0cone_of_widthB\x19\n\x17one_of_type_option_data\"\
-    \x9c\x01\n\x0fMoveItemPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
-    \x06gridId\x12\x17\n\x07item_id\x18\x02\x20\x01(\tR\x06itemId\x12\x1d\n\
-    \nfrom_index\x18\x03\x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_index\x18\
-    \x04\x20\x01(\x05R\x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\x0e2\r.Mo\
-    veItemTypeR\x02ty\"\x7f\n\rCellChangeset\x12\x17\n\x07grid_id\x18\x01\
-    \x20\x01(\tR\x06gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\
-    \x12\x19\n\x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x12\x14\n\x04data\
-    \x18\x04\x20\x01(\tH\0R\x04dataB\r\n\x0bone_of_data**\n\x0cMoveItemType\
-    \x12\r\n\tMoveField\x10\0\x12\x0b\n\x07MoveRow\x10\x01*d\n\tFieldType\
-    \x12\x0c\n\x08RichText\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08Date\
-    Time\x10\x02\x12\x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\
-    \x10\x04\x12\x0c\n\x08Checkbox\x10\x05b\x06proto3\
+    \x03\x20\x01(\x0e2\n.FieldTypeR\tfieldTypeB\x11\n\x0fone_of_field_id\"\
+    \x86\x01\n\x10EditFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
+    \x06gridId\x12\x1b\n\x08field_id\x18\x02\x20\x01(\tH\0R\x07fieldId\x12)\
+    \n\nfield_type\x18\x03\x20\x01(\x0e2\n.FieldTypeR\tfieldTypeB\x11\n\x0fo\
+    ne_of_field_id\"|\n\x10EditFieldContext\x12\x17\n\x07grid_id\x18\x01\x20\
+    \x01(\tR\x06gridId\x12%\n\ngrid_field\x18\x02\x20\x01(\x0b2\x06.FieldR\t\
+    gridField\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOption\
+    Data\"Z\n\x13FieldTypeOptionData\x12\x19\n\x08field_id\x18\x01\x20\x01(\
+    \tR\x07fieldId\x12(\n\x10type_option_data\x18\x02\x20\x01(\x0cR\x0etypeO\
+    ptionData\"-\n\rRepeatedField\x12\x1c\n\x05items\x18\x01\x20\x03(\x0b2\
+    \x06.FieldR\x05items\"7\n\x12RepeatedFieldOrder\x12!\n\x05items\x18\x01\
+    \x20\x03(\x0b2\x0b.FieldOrderR\x05items\"T\n\x08RowOrder\x12\x15\n\x06ro\
+    w_id\x18\x01\x20\x01(\tR\x05rowId\x12\x19\n\x08block_id\x18\x02\x20\x01(\
+    \tR\x07blockId\x12\x16\n\x06height\x18\x03\x20\x01(\x05R\x06height\"\xb8\
+    \x01\n\x03Row\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12@\n\x10cell_b\
+    y_field_id\x18\x02\x20\x03(\x0b2\x17.Row.CellByFieldIdEntryR\rcellByFiel\
+    dId\x12\x16\n\x06height\x18\x03\x20\x01(\x05R\x06height\x1aG\n\x12CellBy\
+    FieldIdEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x1b\n\x05va\
+    lue\x18\x02\x20\x01(\x0b2\x05.CellR\x05value:\x028\x01\")\n\x0bRepeatedR\
+    ow\x12\x1a\n\x05items\x18\x01\x20\x03(\x0b2\x04.RowR\x05items\"5\n\x11Re\
+    peatedGridBlock\x12\x20\n\x05items\x18\x01\x20\x03(\x0b2\n.GridBlockR\
+    \x05items\"U\n\x0eGridBlockOrder\x12\x19\n\x08block_id\x18\x01\x20\x01(\
+    \tR\x07blockId\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trow\
+    Orders\"_\n\rIndexRowOrder\x12&\n\trow_order\x18\x01\x20\x01(\x0b2\t.Row\
+    OrderR\x08rowOrder\x12\x16\n\x05index\x18\x02\x20\x01(\x05H\0R\x05indexB\
+    \x0e\n\x0cone_of_index\"Q\n\x0fUpdatedRowOrder\x12&\n\trow_order\x18\x01\
+    \x20\x01(\x0b2\t.RowOrderR\x08rowOrder\x12\x16\n\x03row\x18\x02\x20\x01(\
+    \x0b2\x04.RowR\x03row\"\xc6\x01\n\x11GridRowsChangeset\x12\x19\n\x08bloc\
+    k_id\x18\x01\x20\x01(\tR\x07blockId\x123\n\rinserted_rows\x18\x02\x20\
+    \x03(\x0b2\x0e.IndexRowOrderR\x0cinsertedRows\x12,\n\x0cdeleted_rows\x18\
+    \x03\x20\x03(\x0b2\t.RowOrderR\x0bdeletedRows\x123\n\x0cupdated_rows\x18\
+    \x04\x20\x03(\x0b2\x10.UpdatedRowOrderR\x0bupdatedRows\"E\n\tGridBlock\
+    \x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12(\n\nrow_orders\x18\x02\
+    \x20\x03(\x0b2\t.RowOrderR\trowOrders\"O\n\x04Cell\x12\x19\n\x08field_id\
+    \x18\x01\x20\x01(\tR\x07fieldId\x12\x18\n\x07content\x18\x02\x20\x01(\tR\
+    \x07content\x12\x12\n\x04data\x18\x03\x20\x01(\tR\x04data\"+\n\x0cRepeat\
+    edCell\x12\x1b\n\x05items\x18\x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\
+    \x11CreateGridPayload\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\"\
+    \x1e\n\x06GridId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"#\n\
+    \x0bGridBlockId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"f\n\x10\
+    CreateRowPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\
+    \"\n\x0cstart_row_id\x18\x02\x20\x01(\tH\0R\nstartRowIdB\x15\n\x13one_of\
+    _start_row_id\"\xb6\x01\n\x12InsertFieldPayload\x12\x17\n\x07grid_id\x18\
+    \x01\x20\x01(\tR\x06gridId\x12\x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.\
+    FieldR\x05field\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etype\
+    OptionData\x12&\n\x0estart_field_id\x18\x04\x20\x01(\tH\0R\x0cstartField\
+    IdB\x17\n\x15one_of_start_field_id\"|\n\x1cUpdateFieldTypeOptionPayload\
+    \x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_i\
+    d\x18\x02\x20\x01(\tR\x07fieldId\x12(\n\x10type_option_data\x18\x03\x20\
+    \x01(\x0cR\x0etypeOptionData\"d\n\x11QueryFieldPayload\x12\x17\n\x07grid\
+    _id\x18\x01\x20\x01(\tR\x06gridId\x126\n\x0cfield_orders\x18\x02\x20\x01\
+    (\x0b2\x13.RepeatedFieldOrderR\x0bfieldOrders\"e\n\x16QueryGridBlocksPay\
+    load\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_\
+    orders\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrders\"\xa8\
+    \x03\n\x15FieldChangesetPayload\x12\x19\n\x08field_id\x18\x01\x20\x01(\t\
+    R\x07fieldId\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\
+    \n\x04name\x18\x03\x20\x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\x04\x20\
+    \x01(\tH\x01R\x04desc\x12+\n\nfield_type\x18\x05\x20\x01(\x0e2\n.FieldTy\
+    peH\x02R\tfieldType\x12\x18\n\x06frozen\x18\x06\x20\x01(\x08H\x03R\x06fr\
+    ozen\x12\x20\n\nvisibility\x18\x07\x20\x01(\x08H\x04R\nvisibility\x12\
+    \x16\n\x05width\x18\x08\x20\x01(\x05H\x05R\x05width\x12*\n\x10type_optio\
+    n_data\x18\t\x20\x01(\x0cH\x06R\x0etypeOptionDataB\r\n\x0bone_of_nameB\r\
+    \n\x0bone_of_descB\x13\n\x11one_of_field_typeB\x0f\n\rone_of_frozenB\x13\
+    \n\x11one_of_visibilityB\x0e\n\x0cone_of_widthB\x19\n\x17one_of_type_opt\
+    ion_data\"\x9c\x01\n\x0fMoveItemPayload\x12\x17\n\x07grid_id\x18\x01\x20\
+    \x01(\tR\x06gridId\x12\x17\n\x07item_id\x18\x02\x20\x01(\tR\x06itemId\
+    \x12\x1d\n\nfrom_index\x18\x03\x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_\
+    index\x18\x04\x20\x01(\x05R\x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\
+    \x0e2\r.MoveItemTypeR\x02ty\"\xb3\x01\n\rCellChangeset\x12\x17\n\x07grid\
+    _id\x18\x01\x20\x01(\tR\x06gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\t\
+    R\x05rowId\x12\x19\n\x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x126\n\
+    \x16cell_content_changeset\x18\x04\x20\x01(\tH\0R\x14cellContentChangese\
+    tB\x1f\n\x1done_of_cell_content_changeset**\n\x0cMoveItemType\x12\r\n\tM\
+    oveField\x10\0\x12\x0b\n\x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\x08\
+    RichText\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\
+    \x12\x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\
+    \x0c\n\x08Checkbox\x10\x05b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -35,7 +35,7 @@ message GetEditFieldContextPayload {
 }
 message EditFieldPayload {
     string grid_id = 1;
-    string field_id = 2;
+    oneof one_of_field_id { string field_id = 2; };
     FieldType field_type = 3;
 }
 message EditFieldContext {
@@ -43,6 +43,10 @@ message EditFieldContext {
     Field grid_field = 2;
     bytes type_option_data = 3;
 }
+message FieldTypeOptionData {
+    string field_id = 1;
+    bytes type_option_data = 2;
+}
 message RepeatedField {
     repeated Field items = 1;
 }
@@ -90,6 +94,7 @@ message GridBlock {
 message Cell {
     string field_id = 1;
     string content = 2;
+    string data = 3;
 }
 message RepeatedCell {
     repeated Cell items = 1;
@@ -113,6 +118,11 @@ message InsertFieldPayload {
     bytes type_option_data = 3;
     oneof one_of_start_field_id { string start_field_id = 4; };
 }
+message UpdateFieldTypeOptionPayload {
+    string grid_id = 1;
+    string field_id = 2;
+    bytes type_option_data = 3;
+}
 message QueryFieldPayload {
     string grid_id = 1;
     RepeatedFieldOrder field_orders = 2;
@@ -143,7 +153,7 @@ message CellChangeset {
     string grid_id = 1;
     string row_id = 2;
     string field_id = 3;
-    oneof one_of_data { string data = 4; };
+    oneof one_of_cell_content_changeset { string cell_content_changeset = 4; };
 }
 enum MoveItemType {
     MoveField = 0;

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

@@ -118,7 +118,7 @@ impl GridMetaPad {
                     Ok(None)
                 }
                 Some(field_meta) => {
-                    if field_meta.get_type_option_str(Some(field_type.clone())).is_none() {
+                    if field_meta.get_type_option_str(&field_type).is_none() {
                         let type_option_json = type_option_json_builder(&field_type);
                         field_meta.insert_type_option_str(&field_type, type_option_json);
                     }

+ 1 - 0
shared-lib/flowy-user-data-model/src/parser/user_email.rs

@@ -56,6 +56,7 @@ mod tests {
     impl quickcheck::Arbitrary for ValidEmailFixture {
         fn arbitrary(g: &mut quickcheck::Gen) -> Self {
             let mut rand_slice: [u8; 32] = [0; 32];
+            #[allow(clippy::needless_range_loop)]
             for i in 0..32 {
                 rand_slice[i] = u8::arbitrary(g);
             }