瀏覽代碼

chore: add row test

appflowy 3 年之前
父節點
當前提交
196d254278
共有 66 個文件被更改,包括 4180 次插入2418 次删除
  1. 1 1
      frontend/app_flowy/lib/plugin/plugin.dart
  2. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart
  3. 88 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pb.dart
  4. 15 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pbjson.dart
  5. 58 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_description.pb.dart
  6. 7 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_description.pbenum.dart
  7. 20 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_description.pbjson.dart
  8. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_description.pbserver.dart
  9. 76 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/date_description.pb.dart
  10. 45 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/date_description.pbenum.dart
  11. 45 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/date_description.pbjson.dart
  12. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/date_description.pbserver.dart
  13. 118 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_description.pb.dart
  14. 28 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_description.pbenum.dart
  15. 36 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_description.pbjson.dart
  16. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_description.pbserver.dart
  17. 5 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/protobuf.dart
  18. 196 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_description.pb.dart
  19. 7 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_description.pbenum.dart
  20. 44 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_description.pbjson.dart
  21. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_description.pbserver.dart
  22. 58 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_description.pb.dart
  23. 7 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_description.pbenum.dart
  24. 20 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_description.pbjson.dart
  25. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_description.pbserver.dart
  26. 1 1
      frontend/rust-lib/flowy-grid/Flowy.toml
  27. 1 1
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  28. 193 0
      frontend/rust-lib/flowy-grid/src/protobuf/model/checkbox_description.rs
  29. 330 0
      frontend/rust-lib/flowy-grid/src/protobuf/model/date_description.rs
  30. 14 2
      frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs
  31. 401 0
      frontend/rust-lib/flowy-grid/src/protobuf/model/number_description.rs
  32. 692 0
      frontend/rust-lib/flowy-grid/src/protobuf/model/selection_description.rs
  33. 200 0
      frontend/rust-lib/flowy-grid/src/protobuf/model/text_description.rs
  34. 0 1656
      frontend/rust-lib/flowy-grid/src/protobuf/model/type_options.rs
  35. 5 0
      frontend/rust-lib/flowy-grid/src/protobuf/proto/checkbox_description.proto
  36. 16 0
      frontend/rust-lib/flowy-grid/src/protobuf/proto/date_description.proto
  37. 14 0
      frontend/rust-lib/flowy-grid/src/protobuf/proto/number_description.proto
  38. 15 0
      frontend/rust-lib/flowy-grid/src/protobuf/proto/selection_description.proto
  39. 5 0
      frontend/rust-lib/flowy-grid/src/protobuf/proto/text_description.proto
  40. 0 47
      frontend/rust-lib/flowy-grid/src/protobuf/proto/type_options.proto
  41. 137 0
      frontend/rust-lib/flowy-grid/src/services/cell/builder/mod.rs
  42. 40 0
      frontend/rust-lib/flowy-grid/src/services/cell/description/checkbox_description.rs
  43. 145 0
      frontend/rust-lib/flowy-grid/src/services/cell/description/date_description.rs
  44. 11 0
      frontend/rust-lib/flowy-grid/src/services/cell/description/mod.rs
  45. 150 0
      frontend/rust-lib/flowy-grid/src/services/cell/description/number_description.rs
  46. 70 0
      frontend/rust-lib/flowy-grid/src/services/cell/description/selection_description.rs
  47. 24 0
      frontend/rust-lib/flowy-grid/src/services/cell/description/text_description.rs
  48. 5 0
      frontend/rust-lib/flowy-grid/src/services/cell/mod.rs
  49. 0 158
      frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs
  50. 0 2
      frontend/rust-lib/flowy-grid/src/services/field/mod.rs
  51. 0 390
      frontend/rust-lib/flowy-grid/src/services/field/type_options.rs
  52. 75 44
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  53. 105 27
      frontend/rust-lib/flowy-grid/src/services/grid_meta_editor.rs
  54. 1 0
      frontend/rust-lib/flowy-grid/src/services/mod.rs
  55. 1 2
      frontend/rust-lib/flowy-grid/src/services/row/cell_stringify.rs
  56. 47 11
      frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs
  57. 4 4
      frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs
  58. 1 26
      frontend/rust-lib/flowy-grid/src/services/util.rs
  59. 3 2
      frontend/rust-lib/flowy-grid/src/util.rs
  60. 101 8
      frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs
  61. 111 17
      frontend/rust-lib/flowy-grid/tests/grid/script.rs
  62. 1 0
      shared-lib/flowy-collaboration/src/client_grid/grid_builder.rs
  63. 13 13
      shared-lib/flowy-collaboration/src/client_grid/grid_pad.rs
  64. 33 0
      shared-lib/flowy-grid-data-model/src/entities/meta.rs
  65. 290 4
      shared-lib/flowy-grid-data-model/src/protobuf/model/meta.rs
  66. 5 0
      shared-lib/flowy-grid-data-model/src/protobuf/proto/meta.proto

+ 1 - 1
frontend/app_flowy/lib/plugin/plugin.dart

@@ -52,7 +52,7 @@ abstract class PluginBuilder {
 
   PluginType get pluginType;
 
-  ViewDataType get dataType => ViewDataType.Block;
+  ViewDataType get dataType => ViewDataType.TextBlock;
 }
 
 abstract class PluginConfig {

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart

@@ -50,7 +50,7 @@ class DocumentPluginBuilder extends PluginBuilder {
   PluginType get pluginType => DefaultPlugin.quill.type();
 
   @override
-  ViewDataType get dataType => ViewDataType.Block;
+  ViewDataType get dataType => ViewDataType.TextBlock;
 }
 
 class DocumentPlugin implements Plugin {

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

@@ -956,3 +956,91 @@ class CellMeta extends $pb.GeneratedMessage {
   void clearData() => clearField(2);
 }
 
+enum CellMetaChangeset_OneOfData {
+  data, 
+  notSet
+}
+
+class CellMetaChangeset extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, CellMetaChangeset_OneOfData> _CellMetaChangeset_OneOfDataByTag = {
+    3 : CellMetaChangeset_OneOfData.data,
+    0 : CellMetaChangeset_OneOfData.notSet
+  };
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CellMetaChangeset', createEmptyInstance: create)
+    ..oo(0, [3])
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowId')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
+    ..hasRequiredFields = false
+  ;
+
+  CellMetaChangeset._() : super();
+  factory CellMetaChangeset({
+    $core.String? rowId,
+    $core.String? fieldId,
+    $core.String? data,
+  }) {
+    final _result = create();
+    if (rowId != null) {
+      _result.rowId = rowId;
+    }
+    if (fieldId != null) {
+      _result.fieldId = fieldId;
+    }
+    if (data != null) {
+      _result.data = data;
+    }
+    return _result;
+  }
+  factory CellMetaChangeset.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory CellMetaChangeset.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')
+  CellMetaChangeset clone() => CellMetaChangeset()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  CellMetaChangeset copyWith(void Function(CellMetaChangeset) updates) => super.copyWith((message) => updates(message as CellMetaChangeset)) as CellMetaChangeset; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static CellMetaChangeset create() => CellMetaChangeset._();
+  CellMetaChangeset createEmptyInstance() => create();
+  static $pb.PbList<CellMetaChangeset> createRepeated() => $pb.PbList<CellMetaChangeset>();
+  @$core.pragma('dart2js:noInline')
+  static CellMetaChangeset getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CellMetaChangeset>(create);
+  static CellMetaChangeset? _defaultInstance;
+
+  CellMetaChangeset_OneOfData whichOneOfData() => _CellMetaChangeset_OneOfDataByTag[$_whichOneof(0)]!;
+  void clearOneOfData() => clearField($_whichOneof(0));
+
+  @$pb.TagNumber(1)
+  $core.String get rowId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set rowId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasRowId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearRowId() => 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.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);
+}
+

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

@@ -186,3 +186,18 @@ const CellMeta$json = const {
 
 /// Descriptor for `CellMeta`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List cellMetaDescriptor = $convert.base64Decode('CghDZWxsTWV0YRIZCghmaWVsZF9pZBgBIAEoCVIHZmllbGRJZBISCgRkYXRhGAIgASgJUgRkYXRh');
+@$core.Deprecated('Use cellMetaChangesetDescriptor instead')
+const CellMetaChangeset$json = const {
+  '1': 'CellMetaChangeset',
+  '2': const [
+    const {'1': 'row_id', '3': 1, '4': 1, '5': 9, '10': 'rowId'},
+    const {'1': 'field_id', '3': 2, '4': 1, '5': 9, '10': 'fieldId'},
+    const {'1': 'data', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'data'},
+  ],
+  '8': const [
+    const {'1': 'one_of_data'},
+  ],
+};
+
+/// Descriptor for `CellMetaChangeset`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List cellMetaChangesetDescriptor = $convert.base64Decode('ChFDZWxsTWV0YUNoYW5nZXNldBIVCgZyb3dfaWQYASABKAlSBXJvd0lkEhkKCGZpZWxkX2lkGAIgASgJUgdmaWVsZElkEhQKBGRhdGEYAyABKAlIAFIEZGF0YUINCgtvbmVfb2ZfZGF0YQ==');

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 28 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_description.pbenum.dart

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

+ 36 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_description.pbjson.dart

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

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

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

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

@@ -1,3 +1,7 @@
 // Auto-generated, do not edit 
-export './type_options.pb.dart';
+export './date_description.pb.dart';
+export './text_description.pb.dart';
+export './checkbox_description.pb.dart';
+export './selection_description.pb.dart';
 export './event_map.pb.dart';
+export './number_description.pb.dart';

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -24,7 +24,7 @@ pub(crate) async fn get_rows_handler(
 ) -> DataResult<RepeatedRow, FlowyError> {
     let payload: QueryRowPayload = data.into_inner();
     let editor = manager.get_grid_editor(&payload.grid_id)?;
-    let repeated_row: RepeatedRow = editor.get_rows(payload.row_orders).await?.into();
+    let repeated_row: RepeatedRow = editor.get_rows(Some(payload.row_orders)).await?.into();
     data_result(repeated_row)
 }
 

+ 193 - 0
frontend/rust-lib/flowy-grid/src/protobuf/model/checkbox_description.rs

@@ -0,0 +1,193 @@
+// This file is generated by rust-protobuf 2.25.2. Do not edit
+// @generated
+
+// https://github.com/rust-lang/rust-clippy/issues/702
+#![allow(unknown_lints)]
+#![allow(clippy::all)]
+
+#![allow(unused_attributes)]
+#![cfg_attr(rustfmt, rustfmt::skip)]
+
+#![allow(box_pointers)]
+#![allow(dead_code)]
+#![allow(missing_docs)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(non_upper_case_globals)]
+#![allow(trivial_casts)]
+#![allow(unused_imports)]
+#![allow(unused_results)]
+//! Generated file from `checkbox_description.proto`
+
+/// Generated files are compatible only with the same version
+/// of protobuf runtime.
+// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
+
+#[derive(PartialEq,Clone,Default)]
+pub struct CheckboxDescription {
+    // message fields
+    pub is_selected: bool,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a CheckboxDescription {
+    fn default() -> &'a CheckboxDescription {
+        <CheckboxDescription as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl CheckboxDescription {
+    pub fn new() -> CheckboxDescription {
+        ::std::default::Default::default()
+    }
+
+    // bool is_selected = 1;
+
+
+    pub fn get_is_selected(&self) -> bool {
+        self.is_selected
+    }
+    pub fn clear_is_selected(&mut self) {
+        self.is_selected = false;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_is_selected(&mut self, v: bool) {
+        self.is_selected = v;
+    }
+}
+
+impl ::protobuf::Message for CheckboxDescription {
+    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 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_bool()?;
+                    self.is_selected = 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.is_selected != false {
+            my_size += 2;
+        }
+        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.is_selected != false {
+            os.write_bool(1, self.is_selected)?;
+        }
+        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() -> CheckboxDescription {
+        CheckboxDescription::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::ProtobufTypeBool>(
+                "is_selected",
+                |m: &CheckboxDescription| { &m.is_selected },
+                |m: &mut CheckboxDescription| { &mut m.is_selected },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CheckboxDescription>(
+                "CheckboxDescription",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static CheckboxDescription {
+        static instance: ::protobuf::rt::LazyV2<CheckboxDescription> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(CheckboxDescription::new)
+    }
+}
+
+impl ::protobuf::Clear for CheckboxDescription {
+    fn clear(&mut self) {
+        self.is_selected = false;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for CheckboxDescription {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for CheckboxDescription {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+static file_descriptor_proto_data: &'static [u8] = b"\
+    \n\x1acheckbox_description.proto\"6\n\x13CheckboxDescription\x12\x1f\n\
+    \x0bis_selected\x18\x01\x20\x01(\x08R\nisSelectedb\x06proto3\
+";
+
+static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
+
+fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
+    ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
+}
+
+pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
+    file_descriptor_proto_lazy.get(|| {
+        parse_descriptor_proto()
+    })
+}

+ 330 - 0
frontend/rust-lib/flowy-grid/src/protobuf/model/date_description.rs

@@ -0,0 +1,330 @@
+// This file is generated by rust-protobuf 2.25.2. Do not edit
+// @generated
+
+// https://github.com/rust-lang/rust-clippy/issues/702
+#![allow(unknown_lints)]
+#![allow(clippy::all)]
+
+#![allow(unused_attributes)]
+#![cfg_attr(rustfmt, rustfmt::skip)]
+
+#![allow(box_pointers)]
+#![allow(dead_code)]
+#![allow(missing_docs)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(non_upper_case_globals)]
+#![allow(trivial_casts)]
+#![allow(unused_imports)]
+#![allow(unused_results)]
+//! Generated file from `date_description.proto`
+
+/// Generated files are compatible only with the same version
+/// of protobuf runtime.
+// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
+
+#[derive(PartialEq,Clone,Default)]
+pub struct DateDescription {
+    // message fields
+    pub date_format: DateFormat,
+    pub time_format: TimeFormat,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a DateDescription {
+    fn default() -> &'a DateDescription {
+        <DateDescription as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl DateDescription {
+    pub fn new() -> DateDescription {
+        ::std::default::Default::default()
+    }
+
+    // .DateFormat date_format = 1;
+
+
+    pub fn get_date_format(&self) -> DateFormat {
+        self.date_format
+    }
+    pub fn clear_date_format(&mut self) {
+        self.date_format = DateFormat::Local;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_date_format(&mut self, v: DateFormat) {
+        self.date_format = v;
+    }
+
+    // .TimeFormat time_format = 2;
+
+
+    pub fn get_time_format(&self) -> TimeFormat {
+        self.time_format
+    }
+    pub fn clear_time_format(&mut self) {
+        self.time_format = TimeFormat::TwelveHour;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_time_format(&mut self, v: TimeFormat) {
+        self.time_format = v;
+    }
+}
+
+impl ::protobuf::Message for DateDescription {
+    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_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.date_format, 1, &mut self.unknown_fields)?
+                },
+                2 => {
+                    ::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.time_format, 2, &mut self.unknown_fields)?
+                },
+                _ => {
+                    ::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_format != DateFormat::Local {
+            my_size += ::protobuf::rt::enum_size(1, self.date_format);
+        }
+        if self.time_format != TimeFormat::TwelveHour {
+            my_size += ::protobuf::rt::enum_size(2, self.time_format);
+        }
+        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_format != DateFormat::Local {
+            os.write_enum(1, ::protobuf::ProtobufEnum::value(&self.date_format))?;
+        }
+        if self.time_format != TimeFormat::TwelveHour {
+            os.write_enum(2, ::protobuf::ProtobufEnum::value(&self.time_format))?;
+        }
+        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() -> DateDescription {
+        DateDescription::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::ProtobufTypeEnum<DateFormat>>(
+                "date_format",
+                |m: &DateDescription| { &m.date_format },
+                |m: &mut DateDescription| { &mut m.date_format },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum<TimeFormat>>(
+                "time_format",
+                |m: &DateDescription| { &m.time_format },
+                |m: &mut DateDescription| { &mut m.time_format },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<DateDescription>(
+                "DateDescription",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static DateDescription {
+        static instance: ::protobuf::rt::LazyV2<DateDescription> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(DateDescription::new)
+    }
+}
+
+impl ::protobuf::Clear for DateDescription {
+    fn clear(&mut self) {
+        self.date_format = DateFormat::Local;
+        self.time_format = TimeFormat::TwelveHour;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for DateDescription {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for DateDescription {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(Clone,PartialEq,Eq,Debug,Hash)]
+pub enum DateFormat {
+    Local = 0,
+    US = 1,
+    ISO = 2,
+    Friendly = 3,
+}
+
+impl ::protobuf::ProtobufEnum for DateFormat {
+    fn value(&self) -> i32 {
+        *self as i32
+    }
+
+    fn from_i32(value: i32) -> ::std::option::Option<DateFormat> {
+        match value {
+            0 => ::std::option::Option::Some(DateFormat::Local),
+            1 => ::std::option::Option::Some(DateFormat::US),
+            2 => ::std::option::Option::Some(DateFormat::ISO),
+            3 => ::std::option::Option::Some(DateFormat::Friendly),
+            _ => ::std::option::Option::None
+        }
+    }
+
+    fn values() -> &'static [Self] {
+        static values: &'static [DateFormat] = &[
+            DateFormat::Local,
+            DateFormat::US,
+            DateFormat::ISO,
+            DateFormat::Friendly,
+        ];
+        values
+    }
+
+    fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            ::protobuf::reflect::EnumDescriptor::new_pb_name::<DateFormat>("DateFormat", file_descriptor_proto())
+        })
+    }
+}
+
+impl ::std::marker::Copy for DateFormat {
+}
+
+impl ::std::default::Default for DateFormat {
+    fn default() -> Self {
+        DateFormat::Local
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for DateFormat {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
+    }
+}
+
+#[derive(Clone,PartialEq,Eq,Debug,Hash)]
+pub enum TimeFormat {
+    TwelveHour = 0,
+    TwentyFourHour = 1,
+}
+
+impl ::protobuf::ProtobufEnum for TimeFormat {
+    fn value(&self) -> i32 {
+        *self as i32
+    }
+
+    fn from_i32(value: i32) -> ::std::option::Option<TimeFormat> {
+        match value {
+            0 => ::std::option::Option::Some(TimeFormat::TwelveHour),
+            1 => ::std::option::Option::Some(TimeFormat::TwentyFourHour),
+            _ => ::std::option::Option::None
+        }
+    }
+
+    fn values() -> &'static [Self] {
+        static values: &'static [TimeFormat] = &[
+            TimeFormat::TwelveHour,
+            TimeFormat::TwentyFourHour,
+        ];
+        values
+    }
+
+    fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            ::protobuf::reflect::EnumDescriptor::new_pb_name::<TimeFormat>("TimeFormat", file_descriptor_proto())
+        })
+    }
+}
+
+impl ::std::marker::Copy for TimeFormat {
+}
+
+impl ::std::default::Default for TimeFormat {
+    fn default() -> Self {
+        TimeFormat::TwelveHour
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for TimeFormat {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
+    }
+}
+
+static file_descriptor_proto_data: &'static [u8] = b"\
+    \n\x16date_description.proto\"m\n\x0fDateDescription\x12,\n\x0bdate_form\
+    at\x18\x01\x20\x01(\x0e2\x0b.DateFormatR\ndateFormat\x12,\n\x0btime_form\
+    at\x18\x02\x20\x01(\x0e2\x0b.TimeFormatR\ntimeFormat*6\n\nDateFormat\x12\
+    \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\
+";
+
+static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
+
+fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
+    ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
+}
+
+pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
+    file_descriptor_proto_lazy.get(|| {
+        parse_descriptor_proto()
+    })
+}

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

@@ -1,8 +1,20 @@
 #![cfg_attr(rustfmt, rustfmt::skip)]
 // Auto-generated, do not edit
 
-mod type_options;
-pub use type_options::*;
+mod date_description;
+pub use date_description::*;
+
+mod text_description;
+pub use text_description::*;
+
+mod checkbox_description;
+pub use checkbox_description::*;
+
+mod selection_description;
+pub use selection_description::*;
 
 mod event_map;
 pub use event_map::*;
+
+mod number_description;
+pub use number_description::*;

+ 401 - 0
frontend/rust-lib/flowy-grid/src/protobuf/model/number_description.rs

@@ -0,0 +1,401 @@
+// This file is generated by rust-protobuf 2.25.2. Do not edit
+// @generated
+
+// https://github.com/rust-lang/rust-clippy/issues/702
+#![allow(unknown_lints)]
+#![allow(clippy::all)]
+
+#![allow(unused_attributes)]
+#![cfg_attr(rustfmt, rustfmt::skip)]
+
+#![allow(box_pointers)]
+#![allow(dead_code)]
+#![allow(missing_docs)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(non_upper_case_globals)]
+#![allow(trivial_casts)]
+#![allow(unused_imports)]
+#![allow(unused_results)]
+//! Generated file from `number_description.proto`
+
+/// Generated files are compatible only with the same version
+/// of protobuf runtime.
+// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
+
+#[derive(PartialEq,Clone,Default)]
+pub struct NumberDescription {
+    // message fields
+    pub money: MoneySymbol,
+    pub scale: u32,
+    pub symbol: ::std::string::String,
+    pub sign_positive: bool,
+    pub name: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a NumberDescription {
+    fn default() -> &'a NumberDescription {
+        <NumberDescription as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl NumberDescription {
+    pub fn new() -> NumberDescription {
+        ::std::default::Default::default()
+    }
+
+    // .MoneySymbol money = 1;
+
+
+    pub fn get_money(&self) -> MoneySymbol {
+        self.money
+    }
+    pub fn clear_money(&mut self) {
+        self.money = MoneySymbol::CNY;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_money(&mut self, v: MoneySymbol) {
+        self.money = v;
+    }
+
+    // uint32 scale = 2;
+
+
+    pub fn get_scale(&self) -> u32 {
+        self.scale
+    }
+    pub fn clear_scale(&mut self) {
+        self.scale = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_scale(&mut self, v: u32) {
+        self.scale = v;
+    }
+
+    // string symbol = 3;
+
+
+    pub fn get_symbol(&self) -> &str {
+        &self.symbol
+    }
+    pub fn clear_symbol(&mut self) {
+        self.symbol.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_symbol(&mut self, v: ::std::string::String) {
+        self.symbol = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_symbol(&mut self) -> &mut ::std::string::String {
+        &mut self.symbol
+    }
+
+    // Take field
+    pub fn take_symbol(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.symbol, ::std::string::String::new())
+    }
+
+    // bool sign_positive = 4;
+
+
+    pub fn get_sign_positive(&self) -> bool {
+        self.sign_positive
+    }
+    pub fn clear_sign_positive(&mut self) {
+        self.sign_positive = false;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_sign_positive(&mut self, v: bool) {
+        self.sign_positive = v;
+    }
+
+    // string name = 5;
+
+
+    pub fn get_name(&self) -> &str {
+        &self.name
+    }
+    pub fn clear_name(&mut self) {
+        self.name.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_name(&mut self, v: ::std::string::String) {
+        self.name = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_name(&mut self) -> &mut ::std::string::String {
+        &mut self.name
+    }
+
+    // Take field
+    pub fn take_name(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.name, ::std::string::String::new())
+    }
+}
+
+impl ::protobuf::Message for NumberDescription {
+    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_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.money, 1, &mut self.unknown_fields)?
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_uint32()?;
+                    self.scale = tmp;
+                },
+                3 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.symbol)?;
+                },
+                4 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_bool()?;
+                    self.sign_positive = tmp;
+                },
+                5 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name)?;
+                },
+                _ => {
+                    ::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.money != MoneySymbol::CNY {
+            my_size += ::protobuf::rt::enum_size(1, self.money);
+        }
+        if self.scale != 0 {
+            my_size += ::protobuf::rt::value_size(2, self.scale, ::protobuf::wire_format::WireTypeVarint);
+        }
+        if !self.symbol.is_empty() {
+            my_size += ::protobuf::rt::string_size(3, &self.symbol);
+        }
+        if self.sign_positive != false {
+            my_size += 2;
+        }
+        if !self.name.is_empty() {
+            my_size += ::protobuf::rt::string_size(5, &self.name);
+        }
+        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.money != MoneySymbol::CNY {
+            os.write_enum(1, ::protobuf::ProtobufEnum::value(&self.money))?;
+        }
+        if self.scale != 0 {
+            os.write_uint32(2, self.scale)?;
+        }
+        if !self.symbol.is_empty() {
+            os.write_string(3, &self.symbol)?;
+        }
+        if self.sign_positive != false {
+            os.write_bool(4, self.sign_positive)?;
+        }
+        if !self.name.is_empty() {
+            os.write_string(5, &self.name)?;
+        }
+        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() -> NumberDescription {
+        NumberDescription::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::ProtobufTypeEnum<MoneySymbol>>(
+                "money",
+                |m: &NumberDescription| { &m.money },
+                |m: &mut NumberDescription| { &mut m.money },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeUint32>(
+                "scale",
+                |m: &NumberDescription| { &m.scale },
+                |m: &mut NumberDescription| { &mut m.scale },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "symbol",
+                |m: &NumberDescription| { &m.symbol },
+                |m: &mut NumberDescription| { &mut m.symbol },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
+                "sign_positive",
+                |m: &NumberDescription| { &m.sign_positive },
+                |m: &mut NumberDescription| { &mut m.sign_positive },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "name",
+                |m: &NumberDescription| { &m.name },
+                |m: &mut NumberDescription| { &mut m.name },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<NumberDescription>(
+                "NumberDescription",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static NumberDescription {
+        static instance: ::protobuf::rt::LazyV2<NumberDescription> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(NumberDescription::new)
+    }
+}
+
+impl ::protobuf::Clear for NumberDescription {
+    fn clear(&mut self) {
+        self.money = MoneySymbol::CNY;
+        self.scale = 0;
+        self.symbol.clear();
+        self.sign_positive = false;
+        self.name.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for NumberDescription {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for NumberDescription {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(Clone,PartialEq,Eq,Debug,Hash)]
+pub enum MoneySymbol {
+    CNY = 0,
+    EUR = 1,
+    USD = 2,
+}
+
+impl ::protobuf::ProtobufEnum for MoneySymbol {
+    fn value(&self) -> i32 {
+        *self as i32
+    }
+
+    fn from_i32(value: i32) -> ::std::option::Option<MoneySymbol> {
+        match value {
+            0 => ::std::option::Option::Some(MoneySymbol::CNY),
+            1 => ::std::option::Option::Some(MoneySymbol::EUR),
+            2 => ::std::option::Option::Some(MoneySymbol::USD),
+            _ => ::std::option::Option::None
+        }
+    }
+
+    fn values() -> &'static [Self] {
+        static values: &'static [MoneySymbol] = &[
+            MoneySymbol::CNY,
+            MoneySymbol::EUR,
+            MoneySymbol::USD,
+        ];
+        values
+    }
+
+    fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            ::protobuf::reflect::EnumDescriptor::new_pb_name::<MoneySymbol>("MoneySymbol", file_descriptor_proto())
+        })
+    }
+}
+
+impl ::std::marker::Copy for MoneySymbol {
+}
+
+impl ::std::default::Default for MoneySymbol {
+    fn default() -> Self {
+        MoneySymbol::CNY
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for MoneySymbol {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
+    }
+}
+
+static file_descriptor_proto_data: &'static [u8] = b"\
+    \n\x18number_description.proto\"\x9e\x01\n\x11NumberDescription\x12\"\n\
+    \x05money\x18\x01\x20\x01(\x0e2\x0c.MoneySymbolR\x05money\x12\x14\n\x05s\
+    cale\x18\x02\x20\x01(\rR\x05scale\x12\x16\n\x06symbol\x18\x03\x20\x01(\t\
+    R\x06symbol\x12#\n\rsign_positive\x18\x04\x20\x01(\x08R\x0csignPositive\
+    \x12\x12\n\x04name\x18\x05\x20\x01(\tR\x04name*(\n\x0bMoneySymbol\x12\
+    \x07\n\x03CNY\x10\0\x12\x07\n\x03EUR\x10\x01\x12\x07\n\x03USD\x10\x02b\
+    \x06proto3\
+";
+
+static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
+
+fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
+    ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
+}
+
+pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
+    file_descriptor_proto_lazy.get(|| {
+        parse_descriptor_proto()
+    })
+}

+ 692 - 0
frontend/rust-lib/flowy-grid/src/protobuf/model/selection_description.rs

@@ -0,0 +1,692 @@
+// This file is generated by rust-protobuf 2.25.2. Do not edit
+// @generated
+
+// https://github.com/rust-lang/rust-clippy/issues/702
+#![allow(unknown_lints)]
+#![allow(clippy::all)]
+
+#![allow(unused_attributes)]
+#![cfg_attr(rustfmt, rustfmt::skip)]
+
+#![allow(box_pointers)]
+#![allow(dead_code)]
+#![allow(missing_docs)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(non_upper_case_globals)]
+#![allow(trivial_casts)]
+#![allow(unused_imports)]
+#![allow(unused_results)]
+//! Generated file from `selection_description.proto`
+
+/// Generated files are compatible only with the same version
+/// of protobuf runtime.
+// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
+
+#[derive(PartialEq,Clone,Default)]
+pub struct SingleSelectDescription {
+    // message fields
+    pub options: ::protobuf::RepeatedField<SelectOption>,
+    pub disable_color: bool,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a SingleSelectDescription {
+    fn default() -> &'a SingleSelectDescription {
+        <SingleSelectDescription as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl SingleSelectDescription {
+    pub fn new() -> SingleSelectDescription {
+        ::std::default::Default::default()
+    }
+
+    // repeated .SelectOption options = 1;
+
+
+    pub fn get_options(&self) -> &[SelectOption] {
+        &self.options
+    }
+    pub fn clear_options(&mut self) {
+        self.options.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_options(&mut self, v: ::protobuf::RepeatedField<SelectOption>) {
+        self.options = v;
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_options(&mut self) -> &mut ::protobuf::RepeatedField<SelectOption> {
+        &mut self.options
+    }
+
+    // Take field
+    pub fn take_options(&mut self) -> ::protobuf::RepeatedField<SelectOption> {
+        ::std::mem::replace(&mut self.options, ::protobuf::RepeatedField::new())
+    }
+
+    // bool disable_color = 2;
+
+
+    pub fn get_disable_color(&self) -> bool {
+        self.disable_color
+    }
+    pub fn clear_disable_color(&mut self) {
+        self.disable_color = false;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_disable_color(&mut self, v: bool) {
+        self.disable_color = v;
+    }
+}
+
+impl ::protobuf::Message for SingleSelectDescription {
+    fn is_initialized(&self) -> bool {
+        for v in &self.options {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.options)?;
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_bool()?;
+                    self.disable_color = 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;
+        for value in &self.options {
+            let len = value.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        };
+        if self.disable_color != false {
+            my_size += 2;
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        for v in &self.options {
+            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        };
+        if self.disable_color != false {
+            os.write_bool(2, self.disable_color)?;
+        }
+        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() -> SingleSelectDescription {
+        SingleSelectDescription::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<SelectOption>>(
+                "options",
+                |m: &SingleSelectDescription| { &m.options },
+                |m: &mut SingleSelectDescription| { &mut m.options },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
+                "disable_color",
+                |m: &SingleSelectDescription| { &m.disable_color },
+                |m: &mut SingleSelectDescription| { &mut m.disable_color },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SingleSelectDescription>(
+                "SingleSelectDescription",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static SingleSelectDescription {
+        static instance: ::protobuf::rt::LazyV2<SingleSelectDescription> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(SingleSelectDescription::new)
+    }
+}
+
+impl ::protobuf::Clear for SingleSelectDescription {
+    fn clear(&mut self) {
+        self.options.clear();
+        self.disable_color = false;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for SingleSelectDescription {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for SingleSelectDescription {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct MultiSelectDescription {
+    // message fields
+    pub options: ::protobuf::RepeatedField<SelectOption>,
+    pub disable_color: bool,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a MultiSelectDescription {
+    fn default() -> &'a MultiSelectDescription {
+        <MultiSelectDescription as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl MultiSelectDescription {
+    pub fn new() -> MultiSelectDescription {
+        ::std::default::Default::default()
+    }
+
+    // repeated .SelectOption options = 1;
+
+
+    pub fn get_options(&self) -> &[SelectOption] {
+        &self.options
+    }
+    pub fn clear_options(&mut self) {
+        self.options.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_options(&mut self, v: ::protobuf::RepeatedField<SelectOption>) {
+        self.options = v;
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_options(&mut self) -> &mut ::protobuf::RepeatedField<SelectOption> {
+        &mut self.options
+    }
+
+    // Take field
+    pub fn take_options(&mut self) -> ::protobuf::RepeatedField<SelectOption> {
+        ::std::mem::replace(&mut self.options, ::protobuf::RepeatedField::new())
+    }
+
+    // bool disable_color = 2;
+
+
+    pub fn get_disable_color(&self) -> bool {
+        self.disable_color
+    }
+    pub fn clear_disable_color(&mut self) {
+        self.disable_color = false;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_disable_color(&mut self, v: bool) {
+        self.disable_color = v;
+    }
+}
+
+impl ::protobuf::Message for MultiSelectDescription {
+    fn is_initialized(&self) -> bool {
+        for v in &self.options {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.options)?;
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_bool()?;
+                    self.disable_color = 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;
+        for value in &self.options {
+            let len = value.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        };
+        if self.disable_color != false {
+            my_size += 2;
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        for v in &self.options {
+            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        };
+        if self.disable_color != false {
+            os.write_bool(2, self.disable_color)?;
+        }
+        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() -> MultiSelectDescription {
+        MultiSelectDescription::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<SelectOption>>(
+                "options",
+                |m: &MultiSelectDescription| { &m.options },
+                |m: &mut MultiSelectDescription| { &mut m.options },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
+                "disable_color",
+                |m: &MultiSelectDescription| { &m.disable_color },
+                |m: &mut MultiSelectDescription| { &mut m.disable_color },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<MultiSelectDescription>(
+                "MultiSelectDescription",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static MultiSelectDescription {
+        static instance: ::protobuf::rt::LazyV2<MultiSelectDescription> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(MultiSelectDescription::new)
+    }
+}
+
+impl ::protobuf::Clear for MultiSelectDescription {
+    fn clear(&mut self) {
+        self.options.clear();
+        self.disable_color = false;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for MultiSelectDescription {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for MultiSelectDescription {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct SelectOption {
+    // message fields
+    pub id: ::std::string::String,
+    pub name: ::std::string::String,
+    pub color: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a SelectOption {
+    fn default() -> &'a SelectOption {
+        <SelectOption as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl SelectOption {
+    pub fn new() -> SelectOption {
+        ::std::default::Default::default()
+    }
+
+    // string id = 1;
+
+
+    pub fn get_id(&self) -> &str {
+        &self.id
+    }
+    pub fn clear_id(&mut self) {
+        self.id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_id(&mut self, v: ::std::string::String) {
+        self.id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_id(&mut self) -> &mut ::std::string::String {
+        &mut self.id
+    }
+
+    // Take field
+    pub fn take_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.id, ::std::string::String::new())
+    }
+
+    // string name = 2;
+
+
+    pub fn get_name(&self) -> &str {
+        &self.name
+    }
+    pub fn clear_name(&mut self) {
+        self.name.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_name(&mut self, v: ::std::string::String) {
+        self.name = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_name(&mut self) -> &mut ::std::string::String {
+        &mut self.name
+    }
+
+    // Take field
+    pub fn take_name(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.name, ::std::string::String::new())
+    }
+
+    // string color = 3;
+
+
+    pub fn get_color(&self) -> &str {
+        &self.color
+    }
+    pub fn clear_color(&mut self) {
+        self.color.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_color(&mut self, v: ::std::string::String) {
+        self.color = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_color(&mut self) -> &mut ::std::string::String {
+        &mut self.color
+    }
+
+    // Take field
+    pub fn take_color(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.color, ::std::string::String::new())
+    }
+}
+
+impl ::protobuf::Message for SelectOption {
+    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.id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name)?;
+                },
+                3 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.color)?;
+                },
+                _ => {
+                    ::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.id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.id);
+        }
+        if !self.name.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.name);
+        }
+        if !self.color.is_empty() {
+            my_size += ::protobuf::rt::string_size(3, &self.color);
+        }
+        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.id.is_empty() {
+            os.write_string(1, &self.id)?;
+        }
+        if !self.name.is_empty() {
+            os.write_string(2, &self.name)?;
+        }
+        if !self.color.is_empty() {
+            os.write_string(3, &self.color)?;
+        }
+        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() -> SelectOption {
+        SelectOption::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>(
+                "id",
+                |m: &SelectOption| { &m.id },
+                |m: &mut SelectOption| { &mut m.id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "name",
+                |m: &SelectOption| { &m.name },
+                |m: &mut SelectOption| { &mut m.name },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "color",
+                |m: &SelectOption| { &m.color },
+                |m: &mut SelectOption| { &mut m.color },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SelectOption>(
+                "SelectOption",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static SelectOption {
+        static instance: ::protobuf::rt::LazyV2<SelectOption> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(SelectOption::new)
+    }
+}
+
+impl ::protobuf::Clear for SelectOption {
+    fn clear(&mut self) {
+        self.id.clear();
+        self.name.clear();
+        self.color.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for SelectOption {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for SelectOption {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+static file_descriptor_proto_data: &'static [u8] = b"\
+    \n\x1bselection_description.proto\"g\n\x17SingleSelectDescription\x12'\n\
+    \x07options\x18\x01\x20\x03(\x0b2\r.SelectOptionR\x07options\x12#\n\rdis\
+    able_color\x18\x02\x20\x01(\x08R\x0cdisableColor\"f\n\x16MultiSelectDesc\
+    ription\x12'\n\x07options\x18\x01\x20\x03(\x0b2\r.SelectOptionR\x07optio\
+    ns\x12#\n\rdisable_color\x18\x02\x20\x01(\x08R\x0cdisableColor\"H\n\x0cS\
+    electOption\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x12\n\x04name\
+    \x18\x02\x20\x01(\tR\x04name\x12\x14\n\x05color\x18\x03\x20\x01(\tR\x05c\
+    olorb\x06proto3\
+";
+
+static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
+
+fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
+    ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
+}
+
+pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
+    file_descriptor_proto_lazy.get(|| {
+        parse_descriptor_proto()
+    })
+}

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

@@ -0,0 +1,200 @@
+// This file is generated by rust-protobuf 2.25.2. Do not edit
+// @generated
+
+// https://github.com/rust-lang/rust-clippy/issues/702
+#![allow(unknown_lints)]
+#![allow(clippy::all)]
+
+#![allow(unused_attributes)]
+#![cfg_attr(rustfmt, rustfmt::skip)]
+
+#![allow(box_pointers)]
+#![allow(dead_code)]
+#![allow(missing_docs)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(non_upper_case_globals)]
+#![allow(trivial_casts)]
+#![allow(unused_imports)]
+#![allow(unused_results)]
+//! Generated file from `text_description.proto`
+
+/// Generated files are compatible only with the same version
+/// of protobuf runtime.
+// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
+
+#[derive(PartialEq,Clone,Default)]
+pub struct RichTextDescription {
+    // message fields
+    pub format: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a RichTextDescription {
+    fn default() -> &'a RichTextDescription {
+        <RichTextDescription as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl RichTextDescription {
+    pub fn new() -> RichTextDescription {
+        ::std::default::Default::default()
+    }
+
+    // string format = 1;
+
+
+    pub fn get_format(&self) -> &str {
+        &self.format
+    }
+    pub fn clear_format(&mut self) {
+        self.format.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_format(&mut self, v: ::std::string::String) {
+        self.format = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_format(&mut self) -> &mut ::std::string::String {
+        &mut self.format
+    }
+
+    // Take field
+    pub fn take_format(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.format, ::std::string::String::new())
+    }
+}
+
+impl ::protobuf::Message for RichTextDescription {
+    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.format)?;
+                },
+                _ => {
+                    ::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.format.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.format);
+        }
+        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.format.is_empty() {
+            os.write_string(1, &self.format)?;
+        }
+        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() -> RichTextDescription {
+        RichTextDescription::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>(
+                "format",
+                |m: &RichTextDescription| { &m.format },
+                |m: &mut RichTextDescription| { &mut m.format },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<RichTextDescription>(
+                "RichTextDescription",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static RichTextDescription {
+        static instance: ::protobuf::rt::LazyV2<RichTextDescription> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(RichTextDescription::new)
+    }
+}
+
+impl ::protobuf::Clear for RichTextDescription {
+    fn clear(&mut self) {
+        self.format.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for RichTextDescription {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for RichTextDescription {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+static file_descriptor_proto_data: &'static [u8] = b"\
+    \n\x16text_description.proto\"-\n\x13RichTextDescription\x12\x16\n\x06fo\
+    rmat\x18\x01\x20\x01(\tR\x06formatb\x06proto3\
+";
+
+static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
+
+fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
+    ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
+}
+
+pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
+    file_descriptor_proto_lazy.get(|| {
+        parse_descriptor_proto()
+    })
+}

+ 0 - 1656
frontend/rust-lib/flowy-grid/src/protobuf/model/type_options.rs

@@ -1,1656 +0,0 @@
-// This file is generated by rust-protobuf 2.25.2. Do not edit
-// @generated
-
-// https://github.com/rust-lang/rust-clippy/issues/702
-#![allow(unknown_lints)]
-#![allow(clippy::all)]
-
-#![allow(unused_attributes)]
-#![cfg_attr(rustfmt, rustfmt::skip)]
-
-#![allow(box_pointers)]
-#![allow(dead_code)]
-#![allow(missing_docs)]
-#![allow(non_camel_case_types)]
-#![allow(non_snake_case)]
-#![allow(non_upper_case_globals)]
-#![allow(trivial_casts)]
-#![allow(unused_imports)]
-#![allow(unused_results)]
-//! Generated file from `type_options.proto`
-
-/// Generated files are compatible only with the same version
-/// of protobuf runtime.
-// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
-
-#[derive(PartialEq,Clone,Default)]
-pub struct RichTextDescription {
-    // message fields
-    pub format: ::std::string::String,
-    // special fields
-    pub unknown_fields: ::protobuf::UnknownFields,
-    pub cached_size: ::protobuf::CachedSize,
-}
-
-impl<'a> ::std::default::Default for &'a RichTextDescription {
-    fn default() -> &'a RichTextDescription {
-        <RichTextDescription as ::protobuf::Message>::default_instance()
-    }
-}
-
-impl RichTextDescription {
-    pub fn new() -> RichTextDescription {
-        ::std::default::Default::default()
-    }
-
-    // string format = 1;
-
-
-    pub fn get_format(&self) -> &str {
-        &self.format
-    }
-    pub fn clear_format(&mut self) {
-        self.format.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_format(&mut self, v: ::std::string::String) {
-        self.format = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_format(&mut self) -> &mut ::std::string::String {
-        &mut self.format
-    }
-
-    // Take field
-    pub fn take_format(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.format, ::std::string::String::new())
-    }
-}
-
-impl ::protobuf::Message for RichTextDescription {
-    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.format)?;
-                },
-                _ => {
-                    ::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.format.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.format);
-        }
-        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.format.is_empty() {
-            os.write_string(1, &self.format)?;
-        }
-        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() -> RichTextDescription {
-        RichTextDescription::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>(
-                "format",
-                |m: &RichTextDescription| { &m.format },
-                |m: &mut RichTextDescription| { &mut m.format },
-            ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<RichTextDescription>(
-                "RichTextDescription",
-                fields,
-                file_descriptor_proto()
-            )
-        })
-    }
-
-    fn default_instance() -> &'static RichTextDescription {
-        static instance: ::protobuf::rt::LazyV2<RichTextDescription> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(RichTextDescription::new)
-    }
-}
-
-impl ::protobuf::Clear for RichTextDescription {
-    fn clear(&mut self) {
-        self.format.clear();
-        self.unknown_fields.clear();
-    }
-}
-
-impl ::std::fmt::Debug for RichTextDescription {
-    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
-        ::protobuf::text_format::fmt(self, f)
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for RichTextDescription {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Message(self)
-    }
-}
-
-#[derive(PartialEq,Clone,Default)]
-pub struct CheckboxDescription {
-    // message fields
-    pub is_selected: bool,
-    // special fields
-    pub unknown_fields: ::protobuf::UnknownFields,
-    pub cached_size: ::protobuf::CachedSize,
-}
-
-impl<'a> ::std::default::Default for &'a CheckboxDescription {
-    fn default() -> &'a CheckboxDescription {
-        <CheckboxDescription as ::protobuf::Message>::default_instance()
-    }
-}
-
-impl CheckboxDescription {
-    pub fn new() -> CheckboxDescription {
-        ::std::default::Default::default()
-    }
-
-    // bool is_selected = 1;
-
-
-    pub fn get_is_selected(&self) -> bool {
-        self.is_selected
-    }
-    pub fn clear_is_selected(&mut self) {
-        self.is_selected = false;
-    }
-
-    // Param is passed by value, moved
-    pub fn set_is_selected(&mut self, v: bool) {
-        self.is_selected = v;
-    }
-}
-
-impl ::protobuf::Message for CheckboxDescription {
-    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 => {
-                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
-                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
-                    }
-                    let tmp = is.read_bool()?;
-                    self.is_selected = 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.is_selected != false {
-            my_size += 2;
-        }
-        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.is_selected != false {
-            os.write_bool(1, self.is_selected)?;
-        }
-        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() -> CheckboxDescription {
-        CheckboxDescription::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::ProtobufTypeBool>(
-                "is_selected",
-                |m: &CheckboxDescription| { &m.is_selected },
-                |m: &mut CheckboxDescription| { &mut m.is_selected },
-            ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CheckboxDescription>(
-                "CheckboxDescription",
-                fields,
-                file_descriptor_proto()
-            )
-        })
-    }
-
-    fn default_instance() -> &'static CheckboxDescription {
-        static instance: ::protobuf::rt::LazyV2<CheckboxDescription> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(CheckboxDescription::new)
-    }
-}
-
-impl ::protobuf::Clear for CheckboxDescription {
-    fn clear(&mut self) {
-        self.is_selected = false;
-        self.unknown_fields.clear();
-    }
-}
-
-impl ::std::fmt::Debug for CheckboxDescription {
-    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
-        ::protobuf::text_format::fmt(self, f)
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for CheckboxDescription {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Message(self)
-    }
-}
-
-#[derive(PartialEq,Clone,Default)]
-pub struct DateDescription {
-    // message fields
-    pub date_format: DateFormat,
-    pub time_format: TimeFormat,
-    // special fields
-    pub unknown_fields: ::protobuf::UnknownFields,
-    pub cached_size: ::protobuf::CachedSize,
-}
-
-impl<'a> ::std::default::Default for &'a DateDescription {
-    fn default() -> &'a DateDescription {
-        <DateDescription as ::protobuf::Message>::default_instance()
-    }
-}
-
-impl DateDescription {
-    pub fn new() -> DateDescription {
-        ::std::default::Default::default()
-    }
-
-    // .DateFormat date_format = 1;
-
-
-    pub fn get_date_format(&self) -> DateFormat {
-        self.date_format
-    }
-    pub fn clear_date_format(&mut self) {
-        self.date_format = DateFormat::Local;
-    }
-
-    // Param is passed by value, moved
-    pub fn set_date_format(&mut self, v: DateFormat) {
-        self.date_format = v;
-    }
-
-    // .TimeFormat time_format = 2;
-
-
-    pub fn get_time_format(&self) -> TimeFormat {
-        self.time_format
-    }
-    pub fn clear_time_format(&mut self) {
-        self.time_format = TimeFormat::TwelveHour;
-    }
-
-    // Param is passed by value, moved
-    pub fn set_time_format(&mut self, v: TimeFormat) {
-        self.time_format = v;
-    }
-}
-
-impl ::protobuf::Message for DateDescription {
-    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_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.date_format, 1, &mut self.unknown_fields)?
-                },
-                2 => {
-                    ::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.time_format, 2, &mut self.unknown_fields)?
-                },
-                _ => {
-                    ::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_format != DateFormat::Local {
-            my_size += ::protobuf::rt::enum_size(1, self.date_format);
-        }
-        if self.time_format != TimeFormat::TwelveHour {
-            my_size += ::protobuf::rt::enum_size(2, self.time_format);
-        }
-        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_format != DateFormat::Local {
-            os.write_enum(1, ::protobuf::ProtobufEnum::value(&self.date_format))?;
-        }
-        if self.time_format != TimeFormat::TwelveHour {
-            os.write_enum(2, ::protobuf::ProtobufEnum::value(&self.time_format))?;
-        }
-        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() -> DateDescription {
-        DateDescription::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::ProtobufTypeEnum<DateFormat>>(
-                "date_format",
-                |m: &DateDescription| { &m.date_format },
-                |m: &mut DateDescription| { &mut m.date_format },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum<TimeFormat>>(
-                "time_format",
-                |m: &DateDescription| { &m.time_format },
-                |m: &mut DateDescription| { &mut m.time_format },
-            ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<DateDescription>(
-                "DateDescription",
-                fields,
-                file_descriptor_proto()
-            )
-        })
-    }
-
-    fn default_instance() -> &'static DateDescription {
-        static instance: ::protobuf::rt::LazyV2<DateDescription> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(DateDescription::new)
-    }
-}
-
-impl ::protobuf::Clear for DateDescription {
-    fn clear(&mut self) {
-        self.date_format = DateFormat::Local;
-        self.time_format = TimeFormat::TwelveHour;
-        self.unknown_fields.clear();
-    }
-}
-
-impl ::std::fmt::Debug for DateDescription {
-    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
-        ::protobuf::text_format::fmt(self, f)
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for DateDescription {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Message(self)
-    }
-}
-
-#[derive(PartialEq,Clone,Default)]
-pub struct SingleSelectDescription {
-    // message fields
-    pub options: ::protobuf::RepeatedField<SelectOption>,
-    pub disable_color: bool,
-    // special fields
-    pub unknown_fields: ::protobuf::UnknownFields,
-    pub cached_size: ::protobuf::CachedSize,
-}
-
-impl<'a> ::std::default::Default for &'a SingleSelectDescription {
-    fn default() -> &'a SingleSelectDescription {
-        <SingleSelectDescription as ::protobuf::Message>::default_instance()
-    }
-}
-
-impl SingleSelectDescription {
-    pub fn new() -> SingleSelectDescription {
-        ::std::default::Default::default()
-    }
-
-    // repeated .SelectOption options = 1;
-
-
-    pub fn get_options(&self) -> &[SelectOption] {
-        &self.options
-    }
-    pub fn clear_options(&mut self) {
-        self.options.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_options(&mut self, v: ::protobuf::RepeatedField<SelectOption>) {
-        self.options = v;
-    }
-
-    // Mutable pointer to the field.
-    pub fn mut_options(&mut self) -> &mut ::protobuf::RepeatedField<SelectOption> {
-        &mut self.options
-    }
-
-    // Take field
-    pub fn take_options(&mut self) -> ::protobuf::RepeatedField<SelectOption> {
-        ::std::mem::replace(&mut self.options, ::protobuf::RepeatedField::new())
-    }
-
-    // bool disable_color = 2;
-
-
-    pub fn get_disable_color(&self) -> bool {
-        self.disable_color
-    }
-    pub fn clear_disable_color(&mut self) {
-        self.disable_color = false;
-    }
-
-    // Param is passed by value, moved
-    pub fn set_disable_color(&mut self, v: bool) {
-        self.disable_color = v;
-    }
-}
-
-impl ::protobuf::Message for SingleSelectDescription {
-    fn is_initialized(&self) -> bool {
-        for v in &self.options {
-            if !v.is_initialized() {
-                return false;
-            }
-        };
-        true
-    }
-
-    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        while !is.eof()? {
-            let (field_number, wire_type) = is.read_tag_unpack()?;
-            match field_number {
-                1 => {
-                    ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.options)?;
-                },
-                2 => {
-                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
-                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
-                    }
-                    let tmp = is.read_bool()?;
-                    self.disable_color = 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;
-        for value in &self.options {
-            let len = value.compute_size();
-            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
-        };
-        if self.disable_color != false {
-            my_size += 2;
-        }
-        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
-        self.cached_size.set(my_size);
-        my_size
-    }
-
-    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        for v in &self.options {
-            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
-            os.write_raw_varint32(v.get_cached_size())?;
-            v.write_to_with_cached_sizes(os)?;
-        };
-        if self.disable_color != false {
-            os.write_bool(2, self.disable_color)?;
-        }
-        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() -> SingleSelectDescription {
-        SingleSelectDescription::new()
-    }
-
-    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
-        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
-        descriptor.get(|| {
-            let mut fields = ::std::vec::Vec::new();
-            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<SelectOption>>(
-                "options",
-                |m: &SingleSelectDescription| { &m.options },
-                |m: &mut SingleSelectDescription| { &mut m.options },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
-                "disable_color",
-                |m: &SingleSelectDescription| { &m.disable_color },
-                |m: &mut SingleSelectDescription| { &mut m.disable_color },
-            ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SingleSelectDescription>(
-                "SingleSelectDescription",
-                fields,
-                file_descriptor_proto()
-            )
-        })
-    }
-
-    fn default_instance() -> &'static SingleSelectDescription {
-        static instance: ::protobuf::rt::LazyV2<SingleSelectDescription> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(SingleSelectDescription::new)
-    }
-}
-
-impl ::protobuf::Clear for SingleSelectDescription {
-    fn clear(&mut self) {
-        self.options.clear();
-        self.disable_color = false;
-        self.unknown_fields.clear();
-    }
-}
-
-impl ::std::fmt::Debug for SingleSelectDescription {
-    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
-        ::protobuf::text_format::fmt(self, f)
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for SingleSelectDescription {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Message(self)
-    }
-}
-
-#[derive(PartialEq,Clone,Default)]
-pub struct MultiSelectDescription {
-    // message fields
-    pub options: ::protobuf::RepeatedField<SelectOption>,
-    pub disable_color: bool,
-    // special fields
-    pub unknown_fields: ::protobuf::UnknownFields,
-    pub cached_size: ::protobuf::CachedSize,
-}
-
-impl<'a> ::std::default::Default for &'a MultiSelectDescription {
-    fn default() -> &'a MultiSelectDescription {
-        <MultiSelectDescription as ::protobuf::Message>::default_instance()
-    }
-}
-
-impl MultiSelectDescription {
-    pub fn new() -> MultiSelectDescription {
-        ::std::default::Default::default()
-    }
-
-    // repeated .SelectOption options = 1;
-
-
-    pub fn get_options(&self) -> &[SelectOption] {
-        &self.options
-    }
-    pub fn clear_options(&mut self) {
-        self.options.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_options(&mut self, v: ::protobuf::RepeatedField<SelectOption>) {
-        self.options = v;
-    }
-
-    // Mutable pointer to the field.
-    pub fn mut_options(&mut self) -> &mut ::protobuf::RepeatedField<SelectOption> {
-        &mut self.options
-    }
-
-    // Take field
-    pub fn take_options(&mut self) -> ::protobuf::RepeatedField<SelectOption> {
-        ::std::mem::replace(&mut self.options, ::protobuf::RepeatedField::new())
-    }
-
-    // bool disable_color = 2;
-
-
-    pub fn get_disable_color(&self) -> bool {
-        self.disable_color
-    }
-    pub fn clear_disable_color(&mut self) {
-        self.disable_color = false;
-    }
-
-    // Param is passed by value, moved
-    pub fn set_disable_color(&mut self, v: bool) {
-        self.disable_color = v;
-    }
-}
-
-impl ::protobuf::Message for MultiSelectDescription {
-    fn is_initialized(&self) -> bool {
-        for v in &self.options {
-            if !v.is_initialized() {
-                return false;
-            }
-        };
-        true
-    }
-
-    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        while !is.eof()? {
-            let (field_number, wire_type) = is.read_tag_unpack()?;
-            match field_number {
-                1 => {
-                    ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.options)?;
-                },
-                2 => {
-                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
-                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
-                    }
-                    let tmp = is.read_bool()?;
-                    self.disable_color = 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;
-        for value in &self.options {
-            let len = value.compute_size();
-            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
-        };
-        if self.disable_color != false {
-            my_size += 2;
-        }
-        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
-        self.cached_size.set(my_size);
-        my_size
-    }
-
-    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        for v in &self.options {
-            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
-            os.write_raw_varint32(v.get_cached_size())?;
-            v.write_to_with_cached_sizes(os)?;
-        };
-        if self.disable_color != false {
-            os.write_bool(2, self.disable_color)?;
-        }
-        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() -> MultiSelectDescription {
-        MultiSelectDescription::new()
-    }
-
-    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
-        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
-        descriptor.get(|| {
-            let mut fields = ::std::vec::Vec::new();
-            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<SelectOption>>(
-                "options",
-                |m: &MultiSelectDescription| { &m.options },
-                |m: &mut MultiSelectDescription| { &mut m.options },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
-                "disable_color",
-                |m: &MultiSelectDescription| { &m.disable_color },
-                |m: &mut MultiSelectDescription| { &mut m.disable_color },
-            ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<MultiSelectDescription>(
-                "MultiSelectDescription",
-                fields,
-                file_descriptor_proto()
-            )
-        })
-    }
-
-    fn default_instance() -> &'static MultiSelectDescription {
-        static instance: ::protobuf::rt::LazyV2<MultiSelectDescription> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(MultiSelectDescription::new)
-    }
-}
-
-impl ::protobuf::Clear for MultiSelectDescription {
-    fn clear(&mut self) {
-        self.options.clear();
-        self.disable_color = false;
-        self.unknown_fields.clear();
-    }
-}
-
-impl ::std::fmt::Debug for MultiSelectDescription {
-    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
-        ::protobuf::text_format::fmt(self, f)
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for MultiSelectDescription {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Message(self)
-    }
-}
-
-#[derive(PartialEq,Clone,Default)]
-pub struct SelectOption {
-    // message fields
-    pub id: ::std::string::String,
-    pub name: ::std::string::String,
-    pub color: ::std::string::String,
-    // special fields
-    pub unknown_fields: ::protobuf::UnknownFields,
-    pub cached_size: ::protobuf::CachedSize,
-}
-
-impl<'a> ::std::default::Default for &'a SelectOption {
-    fn default() -> &'a SelectOption {
-        <SelectOption as ::protobuf::Message>::default_instance()
-    }
-}
-
-impl SelectOption {
-    pub fn new() -> SelectOption {
-        ::std::default::Default::default()
-    }
-
-    // string id = 1;
-
-
-    pub fn get_id(&self) -> &str {
-        &self.id
-    }
-    pub fn clear_id(&mut self) {
-        self.id.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_id(&mut self, v: ::std::string::String) {
-        self.id = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_id(&mut self) -> &mut ::std::string::String {
-        &mut self.id
-    }
-
-    // Take field
-    pub fn take_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.id, ::std::string::String::new())
-    }
-
-    // string name = 2;
-
-
-    pub fn get_name(&self) -> &str {
-        &self.name
-    }
-    pub fn clear_name(&mut self) {
-        self.name.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_name(&mut self, v: ::std::string::String) {
-        self.name = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_name(&mut self) -> &mut ::std::string::String {
-        &mut self.name
-    }
-
-    // Take field
-    pub fn take_name(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.name, ::std::string::String::new())
-    }
-
-    // string color = 3;
-
-
-    pub fn get_color(&self) -> &str {
-        &self.color
-    }
-    pub fn clear_color(&mut self) {
-        self.color.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_color(&mut self, v: ::std::string::String) {
-        self.color = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_color(&mut self) -> &mut ::std::string::String {
-        &mut self.color
-    }
-
-    // Take field
-    pub fn take_color(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.color, ::std::string::String::new())
-    }
-}
-
-impl ::protobuf::Message for SelectOption {
-    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.id)?;
-                },
-                2 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name)?;
-                },
-                3 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.color)?;
-                },
-                _ => {
-                    ::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.id.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.id);
-        }
-        if !self.name.is_empty() {
-            my_size += ::protobuf::rt::string_size(2, &self.name);
-        }
-        if !self.color.is_empty() {
-            my_size += ::protobuf::rt::string_size(3, &self.color);
-        }
-        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.id.is_empty() {
-            os.write_string(1, &self.id)?;
-        }
-        if !self.name.is_empty() {
-            os.write_string(2, &self.name)?;
-        }
-        if !self.color.is_empty() {
-            os.write_string(3, &self.color)?;
-        }
-        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() -> SelectOption {
-        SelectOption::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>(
-                "id",
-                |m: &SelectOption| { &m.id },
-                |m: &mut SelectOption| { &mut m.id },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "name",
-                |m: &SelectOption| { &m.name },
-                |m: &mut SelectOption| { &mut m.name },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "color",
-                |m: &SelectOption| { &m.color },
-                |m: &mut SelectOption| { &mut m.color },
-            ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SelectOption>(
-                "SelectOption",
-                fields,
-                file_descriptor_proto()
-            )
-        })
-    }
-
-    fn default_instance() -> &'static SelectOption {
-        static instance: ::protobuf::rt::LazyV2<SelectOption> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(SelectOption::new)
-    }
-}
-
-impl ::protobuf::Clear for SelectOption {
-    fn clear(&mut self) {
-        self.id.clear();
-        self.name.clear();
-        self.color.clear();
-        self.unknown_fields.clear();
-    }
-}
-
-impl ::std::fmt::Debug for SelectOption {
-    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
-        ::protobuf::text_format::fmt(self, f)
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for SelectOption {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Message(self)
-    }
-}
-
-#[derive(PartialEq,Clone,Default)]
-pub struct NumberDescription {
-    // message fields
-    pub money: MoneySymbol,
-    pub scale: u32,
-    pub symbol: ::std::string::String,
-    pub sign_positive: bool,
-    pub name: ::std::string::String,
-    // special fields
-    pub unknown_fields: ::protobuf::UnknownFields,
-    pub cached_size: ::protobuf::CachedSize,
-}
-
-impl<'a> ::std::default::Default for &'a NumberDescription {
-    fn default() -> &'a NumberDescription {
-        <NumberDescription as ::protobuf::Message>::default_instance()
-    }
-}
-
-impl NumberDescription {
-    pub fn new() -> NumberDescription {
-        ::std::default::Default::default()
-    }
-
-    // .MoneySymbol money = 1;
-
-
-    pub fn get_money(&self) -> MoneySymbol {
-        self.money
-    }
-    pub fn clear_money(&mut self) {
-        self.money = MoneySymbol::CNY;
-    }
-
-    // Param is passed by value, moved
-    pub fn set_money(&mut self, v: MoneySymbol) {
-        self.money = v;
-    }
-
-    // uint32 scale = 2;
-
-
-    pub fn get_scale(&self) -> u32 {
-        self.scale
-    }
-    pub fn clear_scale(&mut self) {
-        self.scale = 0;
-    }
-
-    // Param is passed by value, moved
-    pub fn set_scale(&mut self, v: u32) {
-        self.scale = v;
-    }
-
-    // string symbol = 3;
-
-
-    pub fn get_symbol(&self) -> &str {
-        &self.symbol
-    }
-    pub fn clear_symbol(&mut self) {
-        self.symbol.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_symbol(&mut self, v: ::std::string::String) {
-        self.symbol = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_symbol(&mut self) -> &mut ::std::string::String {
-        &mut self.symbol
-    }
-
-    // Take field
-    pub fn take_symbol(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.symbol, ::std::string::String::new())
-    }
-
-    // bool sign_positive = 4;
-
-
-    pub fn get_sign_positive(&self) -> bool {
-        self.sign_positive
-    }
-    pub fn clear_sign_positive(&mut self) {
-        self.sign_positive = false;
-    }
-
-    // Param is passed by value, moved
-    pub fn set_sign_positive(&mut self, v: bool) {
-        self.sign_positive = v;
-    }
-
-    // string name = 5;
-
-
-    pub fn get_name(&self) -> &str {
-        &self.name
-    }
-    pub fn clear_name(&mut self) {
-        self.name.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_name(&mut self, v: ::std::string::String) {
-        self.name = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_name(&mut self) -> &mut ::std::string::String {
-        &mut self.name
-    }
-
-    // Take field
-    pub fn take_name(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.name, ::std::string::String::new())
-    }
-}
-
-impl ::protobuf::Message for NumberDescription {
-    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_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.money, 1, &mut self.unknown_fields)?
-                },
-                2 => {
-                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
-                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
-                    }
-                    let tmp = is.read_uint32()?;
-                    self.scale = tmp;
-                },
-                3 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.symbol)?;
-                },
-                4 => {
-                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
-                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
-                    }
-                    let tmp = is.read_bool()?;
-                    self.sign_positive = tmp;
-                },
-                5 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name)?;
-                },
-                _ => {
-                    ::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.money != MoneySymbol::CNY {
-            my_size += ::protobuf::rt::enum_size(1, self.money);
-        }
-        if self.scale != 0 {
-            my_size += ::protobuf::rt::value_size(2, self.scale, ::protobuf::wire_format::WireTypeVarint);
-        }
-        if !self.symbol.is_empty() {
-            my_size += ::protobuf::rt::string_size(3, &self.symbol);
-        }
-        if self.sign_positive != false {
-            my_size += 2;
-        }
-        if !self.name.is_empty() {
-            my_size += ::protobuf::rt::string_size(5, &self.name);
-        }
-        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.money != MoneySymbol::CNY {
-            os.write_enum(1, ::protobuf::ProtobufEnum::value(&self.money))?;
-        }
-        if self.scale != 0 {
-            os.write_uint32(2, self.scale)?;
-        }
-        if !self.symbol.is_empty() {
-            os.write_string(3, &self.symbol)?;
-        }
-        if self.sign_positive != false {
-            os.write_bool(4, self.sign_positive)?;
-        }
-        if !self.name.is_empty() {
-            os.write_string(5, &self.name)?;
-        }
-        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() -> NumberDescription {
-        NumberDescription::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::ProtobufTypeEnum<MoneySymbol>>(
-                "money",
-                |m: &NumberDescription| { &m.money },
-                |m: &mut NumberDescription| { &mut m.money },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeUint32>(
-                "scale",
-                |m: &NumberDescription| { &m.scale },
-                |m: &mut NumberDescription| { &mut m.scale },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "symbol",
-                |m: &NumberDescription| { &m.symbol },
-                |m: &mut NumberDescription| { &mut m.symbol },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
-                "sign_positive",
-                |m: &NumberDescription| { &m.sign_positive },
-                |m: &mut NumberDescription| { &mut m.sign_positive },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "name",
-                |m: &NumberDescription| { &m.name },
-                |m: &mut NumberDescription| { &mut m.name },
-            ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<NumberDescription>(
-                "NumberDescription",
-                fields,
-                file_descriptor_proto()
-            )
-        })
-    }
-
-    fn default_instance() -> &'static NumberDescription {
-        static instance: ::protobuf::rt::LazyV2<NumberDescription> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(NumberDescription::new)
-    }
-}
-
-impl ::protobuf::Clear for NumberDescription {
-    fn clear(&mut self) {
-        self.money = MoneySymbol::CNY;
-        self.scale = 0;
-        self.symbol.clear();
-        self.sign_positive = false;
-        self.name.clear();
-        self.unknown_fields.clear();
-    }
-}
-
-impl ::std::fmt::Debug for NumberDescription {
-    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
-        ::protobuf::text_format::fmt(self, f)
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for NumberDescription {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Message(self)
-    }
-}
-
-#[derive(Clone,PartialEq,Eq,Debug,Hash)]
-pub enum DateFormat {
-    Local = 0,
-    US = 1,
-    ISO = 2,
-    Friendly = 3,
-}
-
-impl ::protobuf::ProtobufEnum for DateFormat {
-    fn value(&self) -> i32 {
-        *self as i32
-    }
-
-    fn from_i32(value: i32) -> ::std::option::Option<DateFormat> {
-        match value {
-            0 => ::std::option::Option::Some(DateFormat::Local),
-            1 => ::std::option::Option::Some(DateFormat::US),
-            2 => ::std::option::Option::Some(DateFormat::ISO),
-            3 => ::std::option::Option::Some(DateFormat::Friendly),
-            _ => ::std::option::Option::None
-        }
-    }
-
-    fn values() -> &'static [Self] {
-        static values: &'static [DateFormat] = &[
-            DateFormat::Local,
-            DateFormat::US,
-            DateFormat::ISO,
-            DateFormat::Friendly,
-        ];
-        values
-    }
-
-    fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
-        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
-        descriptor.get(|| {
-            ::protobuf::reflect::EnumDescriptor::new_pb_name::<DateFormat>("DateFormat", file_descriptor_proto())
-        })
-    }
-}
-
-impl ::std::marker::Copy for DateFormat {
-}
-
-impl ::std::default::Default for DateFormat {
-    fn default() -> Self {
-        DateFormat::Local
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for DateFormat {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
-    }
-}
-
-#[derive(Clone,PartialEq,Eq,Debug,Hash)]
-pub enum TimeFormat {
-    TwelveHour = 0,
-    TwentyFourHour = 1,
-}
-
-impl ::protobuf::ProtobufEnum for TimeFormat {
-    fn value(&self) -> i32 {
-        *self as i32
-    }
-
-    fn from_i32(value: i32) -> ::std::option::Option<TimeFormat> {
-        match value {
-            0 => ::std::option::Option::Some(TimeFormat::TwelveHour),
-            1 => ::std::option::Option::Some(TimeFormat::TwentyFourHour),
-            _ => ::std::option::Option::None
-        }
-    }
-
-    fn values() -> &'static [Self] {
-        static values: &'static [TimeFormat] = &[
-            TimeFormat::TwelveHour,
-            TimeFormat::TwentyFourHour,
-        ];
-        values
-    }
-
-    fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
-        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
-        descriptor.get(|| {
-            ::protobuf::reflect::EnumDescriptor::new_pb_name::<TimeFormat>("TimeFormat", file_descriptor_proto())
-        })
-    }
-}
-
-impl ::std::marker::Copy for TimeFormat {
-}
-
-impl ::std::default::Default for TimeFormat {
-    fn default() -> Self {
-        TimeFormat::TwelveHour
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for TimeFormat {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
-    }
-}
-
-#[derive(Clone,PartialEq,Eq,Debug,Hash)]
-pub enum MoneySymbol {
-    CNY = 0,
-    EUR = 1,
-    USD = 2,
-}
-
-impl ::protobuf::ProtobufEnum for MoneySymbol {
-    fn value(&self) -> i32 {
-        *self as i32
-    }
-
-    fn from_i32(value: i32) -> ::std::option::Option<MoneySymbol> {
-        match value {
-            0 => ::std::option::Option::Some(MoneySymbol::CNY),
-            1 => ::std::option::Option::Some(MoneySymbol::EUR),
-            2 => ::std::option::Option::Some(MoneySymbol::USD),
-            _ => ::std::option::Option::None
-        }
-    }
-
-    fn values() -> &'static [Self] {
-        static values: &'static [MoneySymbol] = &[
-            MoneySymbol::CNY,
-            MoneySymbol::EUR,
-            MoneySymbol::USD,
-        ];
-        values
-    }
-
-    fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
-        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
-        descriptor.get(|| {
-            ::protobuf::reflect::EnumDescriptor::new_pb_name::<MoneySymbol>("MoneySymbol", file_descriptor_proto())
-        })
-    }
-}
-
-impl ::std::marker::Copy for MoneySymbol {
-}
-
-impl ::std::default::Default for MoneySymbol {
-    fn default() -> Self {
-        MoneySymbol::CNY
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for MoneySymbol {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
-    }
-}
-
-static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x12type_options.proto\"-\n\x13RichTextDescription\x12\x16\n\x06format\
-    \x18\x01\x20\x01(\tR\x06format\"6\n\x13CheckboxDescription\x12\x1f\n\x0b\
-    is_selected\x18\x01\x20\x01(\x08R\nisSelected\"m\n\x0fDateDescription\
-    \x12,\n\x0bdate_format\x18\x01\x20\x01(\x0e2\x0b.DateFormatR\ndateFormat\
-    \x12,\n\x0btime_format\x18\x02\x20\x01(\x0e2\x0b.TimeFormatR\ntimeFormat\
-    \"g\n\x17SingleSelectDescription\x12'\n\x07options\x18\x01\x20\x03(\x0b2\
-    \r.SelectOptionR\x07options\x12#\n\rdisable_color\x18\x02\x20\x01(\x08R\
-    \x0cdisableColor\"f\n\x16MultiSelectDescription\x12'\n\x07options\x18\
-    \x01\x20\x03(\x0b2\r.SelectOptionR\x07options\x12#\n\rdisable_color\x18\
-    \x02\x20\x01(\x08R\x0cdisableColor\"H\n\x0cSelectOption\x12\x0e\n\x02id\
-    \x18\x01\x20\x01(\tR\x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\
-    \x12\x14\n\x05color\x18\x03\x20\x01(\tR\x05color\"\x9e\x01\n\x11NumberDe\
-    scription\x12\"\n\x05money\x18\x01\x20\x01(\x0e2\x0c.MoneySymbolR\x05mon\
-    ey\x12\x14\n\x05scale\x18\x02\x20\x01(\rR\x05scale\x12\x16\n\x06symbol\
-    \x18\x03\x20\x01(\tR\x06symbol\x12#\n\rsign_positive\x18\x04\x20\x01(\
-    \x08R\x0csignPositive\x12\x12\n\x04name\x18\x05\x20\x01(\tR\x04name*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\x01*(\n\x0bMoneySymbo\
-    l\x12\x07\n\x03CNY\x10\0\x12\x07\n\x03EUR\x10\x01\x12\x07\n\x03USD\x10\
-    \x02b\x06proto3\
-";
-
-static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
-
-fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
-    ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
-}
-
-pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
-    file_descriptor_proto_lazy.get(|| {
-        parse_descriptor_proto()
-    })
-}

+ 5 - 0
frontend/rust-lib/flowy-grid/src/protobuf/proto/checkbox_description.proto

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

+ 16 - 0
frontend/rust-lib/flowy-grid/src/protobuf/proto/date_description.proto

@@ -0,0 +1,16 @@
+syntax = "proto3";
+
+message DateDescription {
+    DateFormat date_format = 1;
+    TimeFormat time_format = 2;
+}
+enum DateFormat {
+    Local = 0;
+    US = 1;
+    ISO = 2;
+    Friendly = 3;
+}
+enum TimeFormat {
+    TwelveHour = 0;
+    TwentyFourHour = 1;
+}

+ 14 - 0
frontend/rust-lib/flowy-grid/src/protobuf/proto/number_description.proto

@@ -0,0 +1,14 @@
+syntax = "proto3";
+
+message NumberDescription {
+    MoneySymbol money = 1;
+    uint32 scale = 2;
+    string symbol = 3;
+    bool sign_positive = 4;
+    string name = 5;
+}
+enum MoneySymbol {
+    CNY = 0;
+    EUR = 1;
+    USD = 2;
+}

+ 15 - 0
frontend/rust-lib/flowy-grid/src/protobuf/proto/selection_description.proto

@@ -0,0 +1,15 @@
+syntax = "proto3";
+
+message SingleSelectDescription {
+    repeated SelectOption options = 1;
+    bool disable_color = 2;
+}
+message MultiSelectDescription {
+    repeated SelectOption options = 1;
+    bool disable_color = 2;
+}
+message SelectOption {
+    string id = 1;
+    string name = 2;
+    string color = 3;
+}

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

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

+ 0 - 47
frontend/rust-lib/flowy-grid/src/protobuf/proto/type_options.proto

@@ -1,47 +0,0 @@
-syntax = "proto3";
-
-message RichTextDescription {
-    string format = 1;
-}
-message CheckboxDescription {
-    bool is_selected = 1;
-}
-message DateDescription {
-    DateFormat date_format = 1;
-    TimeFormat time_format = 2;
-}
-message SingleSelectDescription {
-    repeated SelectOption options = 1;
-    bool disable_color = 2;
-}
-message MultiSelectDescription {
-    repeated SelectOption options = 1;
-    bool disable_color = 2;
-}
-message SelectOption {
-    string id = 1;
-    string name = 2;
-    string color = 3;
-}
-message NumberDescription {
-    MoneySymbol money = 1;
-    uint32 scale = 2;
-    string symbol = 3;
-    bool sign_positive = 4;
-    string name = 5;
-}
-enum DateFormat {
-    Local = 0;
-    US = 1;
-    ISO = 2;
-    Friendly = 3;
-}
-enum TimeFormat {
-    TwelveHour = 0;
-    TwentyFourHour = 1;
-}
-enum MoneySymbol {
-    CNY = 0;
-    EUR = 1;
-    USD = 2;
-}

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

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

+ 40 - 0
frontend/rust-lib/flowy-grid/src/services/cell/description/checkbox_description.rs

@@ -0,0 +1,40 @@
+use crate::impl_from_and_to_type_option;
+use crate::services::row::StringifyCellData;
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::FlowyError;
+use flowy_grid_data_model::entities::{Field, FieldType};
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
+pub struct CheckboxDescription {
+    #[pb(index = 1)]
+    pub is_selected: bool,
+}
+impl_from_and_to_type_option!(CheckboxDescription, FieldType::Checkbox);
+
+impl StringifyCellData for CheckboxDescription {
+    fn str_from_cell_data(&self, data: String) -> String {
+        data
+    }
+
+    fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
+        let s = match string_to_bool(s) {
+            true => "1",
+            false => "0",
+        };
+        Ok(s.to_owned())
+    }
+}
+
+fn string_to_bool(bool_str: &str) -> bool {
+    let lower_case_str: &str = &bool_str.to_lowercase();
+    match lower_case_str {
+        "1" => true,
+        "true" => true,
+        "yes" => true,
+        "0" => false,
+        "false" => false,
+        "no" => false,
+        _ => false,
+    }
+}

+ 145 - 0
frontend/rust-lib/flowy-grid/src/services/cell/description/date_description.rs

@@ -0,0 +1,145 @@
+use crate::impl_from_and_to_type_option;
+use crate::services::row::StringifyCellData;
+use crate::services::util::*;
+use chrono::format::strftime::StrftimeItems;
+use chrono::NaiveDateTime;
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::FlowyError;
+use flowy_grid_data_model::entities::{Field, FieldType};
+use serde::{Deserialize, Serialize};
+
+// Date
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
+pub struct DateDescription {
+    #[pb(index = 1)]
+    pub date_format: DateFormat,
+
+    #[pb(index = 2)]
+    pub time_format: TimeFormat,
+}
+impl_from_and_to_type_option!(DateDescription, FieldType::DateTime);
+
+impl DateDescription {
+    fn date_time_format_str(&self) -> String {
+        format!("{} {}", self.date_format.format_str(), self.time_format.format_str())
+    }
+
+    #[allow(dead_code)]
+    fn today_from_timestamp(&self, timestamp: i64) -> String {
+        let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0);
+        self.today_from_native(native)
+    }
+
+    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 fmt_str = self.date_time_format_str();
+        let output = format!("{}", local.format_with_items(StrftimeItems::new(&fmt_str)));
+        output
+    }
+}
+
+impl StringifyCellData for DateDescription {
+    fn str_from_cell_data(&self, data: String) -> String {
+        match data.parse::<i64>() {
+            Ok(timestamp) => {
+                let native = NaiveDateTime::from_timestamp(timestamp, 0);
+                self.today_from_native(native)
+            }
+            Err(e) => {
+                tracing::debug!("DateDescription format {} fail. error: {:?}", data, e);
+                String::new()
+            }
+        }
+    }
+
+    fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
+        let timestamp = s
+            .parse::<i64>()
+            .map_err(|e| FlowyError::internal().context(format!("Parse {} to i64 failed: {}", s, e)))?;
+        Ok(format!("{}", timestamp))
+    }
+}
+
+#[derive(Clone, Debug, Copy, Serialize, Deserialize, ProtoBuf_Enum)]
+pub enum DateFormat {
+    Local = 0,
+    US = 1,
+    ISO = 2,
+    Friendly = 3,
+}
+impl std::default::Default for DateFormat {
+    fn default() -> Self {
+        DateFormat::Friendly
+    }
+}
+
+impl std::convert::From<i32> for DateFormat {
+    fn from(value: i32) -> Self {
+        match value {
+            0 => DateFormat::Local,
+            1 => DateFormat::US,
+            2 => DateFormat::ISO,
+            3 => DateFormat::Friendly,
+            _ => {
+                tracing::error!("Unsupported date format, fallback to friendly");
+                DateFormat::Friendly
+            }
+        }
+    }
+}
+
+impl DateFormat {
+    pub fn value(&self) -> i32 {
+        *self as i32
+    }
+    // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
+    pub fn format_str(&self) -> &'static str {
+        match self {
+            DateFormat::Local => "%Y/%m/%d",
+            DateFormat::US => "%Y/%m/%d",
+            DateFormat::ISO => "%Y-%m-%d",
+            DateFormat::Friendly => "%b %d,%Y",
+        }
+    }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize, ProtoBuf_Enum)]
+pub enum TimeFormat {
+    TwelveHour = 0,
+    TwentyFourHour = 1,
+}
+
+impl std::convert::From<i32> for TimeFormat {
+    fn from(value: i32) -> Self {
+        match value {
+            0 => TimeFormat::TwelveHour,
+            1 => TimeFormat::TwentyFourHour,
+            _ => {
+                tracing::error!("Unsupported time format, fallback to TwentyFourHour");
+                TimeFormat::TwentyFourHour
+            }
+        }
+    }
+}
+
+impl TimeFormat {
+    pub fn value(&self) -> i32 {
+        *self as i32
+    }
+
+    // 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::TwentyFourHour => "%R",
+        }
+    }
+}
+
+impl std::default::Default for TimeFormat {
+    fn default() -> Self {
+        TimeFormat::TwentyFourHour
+    }
+}

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

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

+ 150 - 0
frontend/rust-lib/flowy-grid/src/services/cell/description/number_description.rs

@@ -0,0 +1,150 @@
+use crate::impl_from_and_to_type_option;
+use crate::services::row::StringifyCellData;
+use crate::services::util::*;
+use chrono::format::strftime::StrftimeItems;
+use chrono::NaiveDateTime;
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::FlowyError;
+use flowy_grid_data_model::entities::{Field, FieldType};
+use rust_decimal::Decimal;
+use rusty_money::{
+    iso::{Currency, CNY, EUR, USD},
+    Money,
+};
+use serde::{Deserialize, Serialize};
+use std::str::FromStr;
+use strum_macros::EnumIter;
+
+// Number
+#[derive(Clone, Debug, Serialize, Deserialize, ProtoBuf)]
+pub struct NumberDescription {
+    #[pb(index = 1)]
+    pub money: MoneySymbol,
+
+    #[pb(index = 2)]
+    pub scale: u32,
+
+    #[pb(index = 3)]
+    pub symbol: String,
+
+    #[pb(index = 4)]
+    pub sign_positive: bool,
+
+    #[pb(index = 5)]
+    pub name: String,
+}
+impl_from_and_to_type_option!(NumberDescription, FieldType::Number);
+
+impl std::default::Default for NumberDescription {
+    fn default() -> Self {
+        let money = MoneySymbol::default();
+        let symbol = money.symbol_str();
+        NumberDescription {
+            money,
+            scale: 0,
+            symbol,
+            sign_positive: true,
+            name: "Number".to_string(),
+        }
+    }
+}
+
+impl NumberDescription {
+    pub fn set_money_symbol(&mut self, money_symbol: MoneySymbol) {
+        self.money = money_symbol;
+        self.symbol = money_symbol.symbol_str();
+    }
+
+    fn money_from_str(&self, s: &str) -> Option<String> {
+        match Decimal::from_str(s) {
+            Ok(mut decimal) => {
+                match decimal.set_scale(self.scale) {
+                    Ok(_) => {}
+                    Err(e) => {
+                        tracing::error!("Set decimal scale failed: {:?}", e);
+                    }
+                }
+                decimal.set_sign_positive(self.sign_positive);
+                Some(self.money.with_decimal(decimal).to_string())
+            }
+            Err(e) => {
+                tracing::error!("Parser money from {} failed: {:?}", s, e);
+                None
+            }
+        }
+    }
+}
+
+impl StringifyCellData for NumberDescription {
+    fn str_from_cell_data(&self, data: String) -> String {
+        match self.money_from_str(&data) {
+            Some(money_str) => money_str,
+            None => String::default(),
+        }
+    }
+
+    fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
+        let strip_symbol_money = strip_money_symbol(s);
+        let decimal = Decimal::from_str(&strip_symbol_money).map_err(|err| FlowyError::internal().context(err))?;
+        Ok(decimal.to_string())
+    }
+}
+
+#[derive(Clone, Copy, Debug, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
+pub enum MoneySymbol {
+    CNY = 0,
+    EUR = 1,
+    USD = 2,
+}
+
+impl std::default::Default for MoneySymbol {
+    fn default() -> Self {
+        MoneySymbol::USD
+    }
+}
+
+impl MoneySymbol {
+    // Currency list https://docs.rs/rusty-money/0.4.0/rusty_money/iso/index.html
+    pub fn from_symbol_str(s: &str) -> MoneySymbol {
+        match s {
+            "CNY" => MoneySymbol::CNY,
+            "EUR" => MoneySymbol::EUR,
+            "USD" => MoneySymbol::USD,
+            _ => MoneySymbol::CNY,
+        }
+    }
+
+    pub fn from_money(money: &rusty_money::Money<Currency>) -> MoneySymbol {
+        MoneySymbol::from_symbol_str(&money.currency().symbol.to_string())
+    }
+
+    pub fn currency(&self) -> &'static Currency {
+        match self {
+            MoneySymbol::CNY => CNY,
+            MoneySymbol::EUR => EUR,
+            MoneySymbol::USD => USD,
+        }
+    }
+
+    // string_to_money("¥18,443").unwrap();
+    // string_to_money("$18,443").unwrap();
+    // string_to_money("€18,443").unwrap();
+    pub fn code(&self) -> String {
+        self.currency().iso_alpha_code.to_string()
+    }
+
+    pub fn symbol_str(&self) -> String {
+        self.currency().symbol.to_string()
+    }
+
+    pub fn zero(&self) -> Money<Currency> {
+        let mut decimal = Decimal::new(0, 0);
+        decimal.set_sign_positive(true);
+        self.with_decimal(decimal)
+    }
+
+    pub fn with_decimal(&self, decimal: Decimal) -> Money<Currency> {
+        let money = rusty_money::Money::from_decimal(decimal, self.currency());
+        money
+    }
+}

+ 70 - 0
frontend/rust-lib/flowy-grid/src/services/cell/description/selection_description.rs

@@ -0,0 +1,70 @@
+use crate::impl_from_and_to_type_option;
+use crate::services::row::StringifyCellData;
+use crate::services::util::*;
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::FlowyError;
+use flowy_grid_data_model::entities::{Field, FieldType};
+use serde::{Deserialize, Serialize};
+
+// Single select
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
+pub struct SingleSelectDescription {
+    #[pb(index = 1)]
+    pub options: Vec<SelectOption>,
+
+    #[pb(index = 2)]
+    pub disable_color: bool,
+}
+impl_from_and_to_type_option!(SingleSelectDescription, FieldType::SingleSelect);
+
+impl StringifyCellData for SingleSelectDescription {
+    fn str_from_cell_data(&self, data: String) -> String {
+        data
+    }
+
+    fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
+        Ok(s.to_owned())
+    }
+}
+
+// Multiple select
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
+pub struct MultiSelectDescription {
+    #[pb(index = 1)]
+    pub options: Vec<SelectOption>,
+
+    #[pb(index = 2)]
+    pub disable_color: bool,
+}
+impl_from_and_to_type_option!(MultiSelectDescription, FieldType::MultiSelect);
+impl StringifyCellData for MultiSelectDescription {
+    fn str_from_cell_data(&self, data: String) -> String {
+        data
+    }
+
+    fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
+        Ok(s.to_owned())
+    }
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
+pub struct SelectOption {
+    #[pb(index = 1)]
+    pub id: String,
+
+    #[pb(index = 2)]
+    pub name: String,
+
+    #[pb(index = 3)]
+    pub color: String,
+}
+
+impl SelectOption {
+    pub fn new(name: &str) -> Self {
+        SelectOption {
+            id: uuid(),
+            name: name.to_owned(),
+            color: "".to_string(),
+        }
+    }
+}

+ 24 - 0
frontend/rust-lib/flowy-grid/src/services/cell/description/text_description.rs

@@ -0,0 +1,24 @@
+use crate::impl_from_and_to_type_option;
+use crate::services::row::StringifyCellData;
+use crate::services::util::*;
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::FlowyError;
+use flowy_grid_data_model::entities::{Field, FieldType};
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
+pub struct RichTextDescription {
+    #[pb(index = 1)]
+    pub format: String,
+}
+impl_from_and_to_type_option!(RichTextDescription, FieldType::RichText);
+
+impl StringifyCellData for RichTextDescription {
+    fn str_from_cell_data(&self, data: String) -> String {
+        data
+    }
+
+    fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
+        Ok(s.to_owned())
+    }
+}

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

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

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

@@ -1,7 +1,3 @@
-use crate::services::field::{
-    CheckboxDescription, DateDescription, DateFormat, MoneySymbol, MultiSelectDescription, NumberDescription,
-    RichTextDescription, SelectOption, SingleSelectDescription, TimeFormat,
-};
 use flowy_grid_data_model::entities::{Field, FieldType};
 
 pub struct FieldBuilder {
@@ -61,157 +57,3 @@ pub trait TypeOptionsBuilder {
     fn field_type(&self) -> FieldType;
     fn build(&self) -> String;
 }
-
-// Text
-pub struct RichTextTypeOptionsBuilder(RichTextDescription);
-
-impl RichTextTypeOptionsBuilder {
-    pub fn new() -> Self {
-        Self(RichTextDescription::default())
-    }
-}
-
-impl TypeOptionsBuilder for RichTextTypeOptionsBuilder {
-    fn field_type(&self) -> FieldType {
-        self.0.field_type()
-    }
-
-    fn build(&self) -> String {
-        self.0.clone().into()
-    }
-}
-
-// Number
-pub struct NumberTypeOptionsBuilder(NumberDescription);
-
-impl NumberTypeOptionsBuilder {
-    pub fn new() -> Self {
-        Self(NumberDescription::default())
-    }
-
-    pub fn name(mut self, name: &str) -> Self {
-        self.0.name = name.to_string();
-        self
-    }
-
-    pub fn set_money_symbol(mut self, money_symbol: MoneySymbol) -> Self {
-        self.0.set_money_symbol(money_symbol);
-        self
-    }
-
-    pub fn scale(mut self, scale: u32) -> Self {
-        self.0.scale = scale;
-        self
-    }
-
-    pub fn positive(mut self, positive: bool) -> Self {
-        self.0.sign_positive = positive;
-        self
-    }
-}
-
-impl TypeOptionsBuilder for NumberTypeOptionsBuilder {
-    fn field_type(&self) -> FieldType {
-        self.0.field_type()
-    }
-
-    fn build(&self) -> String {
-        self.0.clone().into()
-    }
-}
-
-// Date
-pub struct DateTypeOptionsBuilder(DateDescription);
-impl DateTypeOptionsBuilder {
-    pub fn new() -> Self {
-        Self(DateDescription::default())
-    }
-
-    pub fn date_format(mut self, date_format: DateFormat) -> Self {
-        self.0.date_format = date_format;
-        self
-    }
-
-    pub fn time_format(mut self, time_format: TimeFormat) -> Self {
-        self.0.time_format = time_format;
-        self
-    }
-}
-impl TypeOptionsBuilder for DateTypeOptionsBuilder {
-    fn field_type(&self) -> FieldType {
-        self.0.field_type()
-    }
-
-    fn build(&self) -> String {
-        self.0.clone().into()
-    }
-}
-
-// Single Select
-pub struct SingleSelectTypeOptionsBuilder(SingleSelectDescription);
-
-impl SingleSelectTypeOptionsBuilder {
-    pub fn new() -> Self {
-        Self(SingleSelectDescription::default())
-    }
-
-    pub fn option(mut self, opt: SelectOption) -> Self {
-        self.0.options.push(opt);
-        self
-    }
-}
-impl TypeOptionsBuilder for SingleSelectTypeOptionsBuilder {
-    fn field_type(&self) -> FieldType {
-        self.0.field_type()
-    }
-
-    fn build(&self) -> String {
-        self.0.clone().into()
-    }
-}
-
-// Multi Select
-pub struct MultiSelectTypeOptionsBuilder(MultiSelectDescription);
-
-impl MultiSelectTypeOptionsBuilder {
-    pub fn new() -> Self {
-        Self(MultiSelectDescription::default())
-    }
-
-    pub fn option(mut self, opt: SelectOption) -> Self {
-        self.0.options.push(opt);
-        self
-    }
-}
-
-impl TypeOptionsBuilder for MultiSelectTypeOptionsBuilder {
-    fn field_type(&self) -> FieldType {
-        self.0.field_type()
-    }
-
-    fn build(&self) -> String {
-        self.0.clone().into()
-    }
-}
-
-// Checkbox
-pub struct CheckboxTypeOptionsBuilder(CheckboxDescription);
-impl CheckboxTypeOptionsBuilder {
-    pub fn new() -> Self {
-        Self(CheckboxDescription::default())
-    }
-
-    pub fn set_selected(mut self, is_selected: bool) -> Self {
-        self.0.is_selected = is_selected;
-        self
-    }
-}
-impl TypeOptionsBuilder for CheckboxTypeOptionsBuilder {
-    fn field_type(&self) -> FieldType {
-        self.0.field_type()
-    }
-
-    fn build(&self) -> String {
-        self.0.clone().into()
-    }
-}

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

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

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

@@ -1,390 +0,0 @@
-#![allow(clippy::upper_case_acronyms)]
-use crate::impl_from_and_to_type_option;
-use crate::services::row::StringifyCellData;
-use crate::services::util::*;
-
-use chrono::format::strftime::StrftimeItems;
-use chrono::NaiveDateTime;
-use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-use flowy_error::FlowyError;
-use flowy_grid_data_model::entities::{Field, FieldType};
-use rust_decimal::Decimal;
-use rusty_money::{
-    iso::{Currency, CNY, EUR, USD},
-    Money,
-};
-use serde::{Deserialize, Serialize};
-use std::str::FromStr;
-use strum_macros::EnumIter;
-
-#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct RichTextDescription {
-    #[pb(index = 1)]
-    pub format: String,
-}
-impl_from_and_to_type_option!(RichTextDescription, FieldType::RichText);
-
-impl StringifyCellData for RichTextDescription {
-    fn str_from_cell_data(&self, data: String) -> String {
-        data
-    }
-
-    fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
-        Ok(s.to_owned())
-    }
-}
-
-// Checkbox
-#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
-pub struct CheckboxDescription {
-    #[pb(index = 1)]
-    pub is_selected: bool,
-}
-impl_from_and_to_type_option!(CheckboxDescription, FieldType::Checkbox);
-
-impl StringifyCellData for CheckboxDescription {
-    fn str_from_cell_data(&self, data: String) -> String {
-        data
-    }
-
-    fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
-        let s = match string_to_bool(s) {
-            true => "1",
-            false => "0",
-        };
-        Ok(s.to_owned())
-    }
-}
-
-// Date
-#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct DateDescription {
-    #[pb(index = 1)]
-    pub date_format: DateFormat,
-
-    #[pb(index = 2)]
-    pub time_format: TimeFormat,
-}
-impl_from_and_to_type_option!(DateDescription, FieldType::DateTime);
-
-impl DateDescription {
-    fn date_time_format_str(&self) -> String {
-        format!("{} {}", self.date_format.format_str(), self.time_format.format_str())
-    }
-
-    #[allow(dead_code)]
-    fn today_from_timestamp(&self, timestamp: i64) -> String {
-        let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0);
-        self.today_from_native(native)
-    }
-
-    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 fmt_str = self.date_time_format_str();
-        let output = format!("{}", local.format_with_items(StrftimeItems::new(&fmt_str)));
-        output
-    }
-}
-
-impl StringifyCellData for DateDescription {
-    fn str_from_cell_data(&self, data: String) -> String {
-        match data.parse::<i64>() {
-            Ok(timestamp) => {
-                let native = NaiveDateTime::from_timestamp(timestamp, 0);
-                self.today_from_native(native)
-            }
-            Err(e) => {
-                tracing::debug!("DateDescription format {} fail. error: {:?}", data, e);
-                String::new()
-            }
-        }
-    }
-
-    fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
-        let timestamp = s
-            .parse::<i64>()
-            .map_err(|e| FlowyError::internal().context(format!("Parse {} to i64 failed: {}", s, e)))?;
-        Ok(format!("{}", timestamp))
-    }
-}
-
-#[derive(Clone, Debug, Copy, Serialize, Deserialize, ProtoBuf_Enum)]
-pub enum DateFormat {
-    Local = 0,
-    US = 1,
-    ISO = 2,
-    Friendly = 3,
-}
-impl std::default::Default for DateFormat {
-    fn default() -> Self {
-        DateFormat::Friendly
-    }
-}
-
-impl std::convert::From<i32> for DateFormat {
-    fn from(value: i32) -> Self {
-        match value {
-            0 => DateFormat::Local,
-            1 => DateFormat::US,
-            2 => DateFormat::ISO,
-            3 => DateFormat::Friendly,
-            _ => {
-                tracing::error!("Unsupported date format, fallback to friendly");
-                DateFormat::Friendly
-            }
-        }
-    }
-}
-
-impl DateFormat {
-    pub fn value(&self) -> i32 {
-        *self as i32
-    }
-    // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
-    pub fn format_str(&self) -> &'static str {
-        match self {
-            DateFormat::Local => "%Y/%m/%d",
-            DateFormat::US => "%Y/%m/%d",
-            DateFormat::ISO => "%Y-%m-%d",
-            DateFormat::Friendly => "%b %d,%Y",
-        }
-    }
-}
-
-#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize, ProtoBuf_Enum)]
-pub enum TimeFormat {
-    TwelveHour = 0,
-    TwentyFourHour = 1,
-}
-
-impl std::convert::From<i32> for TimeFormat {
-    fn from(value: i32) -> Self {
-        match value {
-            0 => TimeFormat::TwelveHour,
-            1 => TimeFormat::TwentyFourHour,
-            _ => {
-                tracing::error!("Unsupported time format, fallback to TwentyFourHour");
-                TimeFormat::TwentyFourHour
-            }
-        }
-    }
-}
-
-impl TimeFormat {
-    pub fn value(&self) -> i32 {
-        *self as i32
-    }
-
-    // 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::TwentyFourHour => "%R",
-        }
-    }
-}
-
-impl std::default::Default for TimeFormat {
-    fn default() -> Self {
-        TimeFormat::TwentyFourHour
-    }
-}
-
-// Single select
-#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct SingleSelectDescription {
-    #[pb(index = 1)]
-    pub options: Vec<SelectOption>,
-
-    #[pb(index = 2)]
-    pub disable_color: bool,
-}
-impl_from_and_to_type_option!(SingleSelectDescription, FieldType::SingleSelect);
-
-impl StringifyCellData for SingleSelectDescription {
-    fn str_from_cell_data(&self, data: String) -> String {
-        data
-    }
-
-    fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
-        Ok(s.to_owned())
-    }
-}
-
-// Multiple select
-#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct MultiSelectDescription {
-    #[pb(index = 1)]
-    pub options: Vec<SelectOption>,
-
-    #[pb(index = 2)]
-    pub disable_color: bool,
-}
-impl_from_and_to_type_option!(MultiSelectDescription, FieldType::MultiSelect);
-impl StringifyCellData for MultiSelectDescription {
-    fn str_from_cell_data(&self, data: String) -> String {
-        data
-    }
-
-    fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
-        Ok(s.to_owned())
-    }
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct SelectOption {
-    #[pb(index = 1)]
-    pub id: String,
-
-    #[pb(index = 2)]
-    pub name: String,
-
-    #[pb(index = 3)]
-    pub color: String,
-}
-
-impl SelectOption {
-    pub fn new(name: &str) -> Self {
-        SelectOption {
-            id: uuid(),
-            name: name.to_owned(),
-            color: "".to_string(),
-        }
-    }
-}
-
-// Number
-#[derive(Clone, Debug, Serialize, Deserialize, ProtoBuf)]
-pub struct NumberDescription {
-    #[pb(index = 1)]
-    pub money: MoneySymbol,
-
-    #[pb(index = 2)]
-    pub scale: u32,
-
-    #[pb(index = 3)]
-    pub symbol: String,
-
-    #[pb(index = 4)]
-    pub sign_positive: bool,
-
-    #[pb(index = 5)]
-    pub name: String,
-}
-impl_from_and_to_type_option!(NumberDescription, FieldType::Number);
-
-impl std::default::Default for NumberDescription {
-    fn default() -> Self {
-        let money = MoneySymbol::default();
-        let symbol = money.symbol_str();
-        NumberDescription {
-            money,
-            scale: 0,
-            symbol,
-            sign_positive: true,
-            name: "Number".to_string(),
-        }
-    }
-}
-
-impl NumberDescription {
-    pub fn set_money_symbol(&mut self, money_symbol: MoneySymbol) {
-        self.money = money_symbol;
-        self.symbol = money_symbol.symbol_str();
-    }
-
-    fn money_from_str(&self, s: &str) -> Option<String> {
-        match Decimal::from_str(s) {
-            Ok(mut decimal) => {
-                match decimal.set_scale(self.scale) {
-                    Ok(_) => {}
-                    Err(e) => {
-                        tracing::error!("Set decimal scale failed: {:?}", e);
-                    }
-                }
-                decimal.set_sign_positive(self.sign_positive);
-                Some(self.money.with_decimal(decimal).to_string())
-            }
-            Err(e) => {
-                tracing::error!("Parser money from {} failed: {:?}", s, e);
-                None
-            }
-        }
-    }
-}
-
-impl StringifyCellData for NumberDescription {
-    fn str_from_cell_data(&self, data: String) -> String {
-        match self.money_from_str(&data) {
-            Some(money_str) => money_str,
-            None => String::default(),
-        }
-    }
-
-    fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
-        let strip_symbol_money = strip_money_symbol(s);
-        let decimal = Decimal::from_str(&strip_symbol_money).map_err(|err| FlowyError::internal().context(err))?;
-        Ok(decimal.to_string())
-    }
-}
-
-#[derive(Clone, Copy, Debug, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
-pub enum MoneySymbol {
-    CNY = 0,
-    EUR = 1,
-    USD = 2,
-}
-
-impl std::default::Default for MoneySymbol {
-    fn default() -> Self {
-        MoneySymbol::USD
-    }
-}
-
-impl MoneySymbol {
-    // Currency list https://docs.rs/rusty-money/0.4.0/rusty_money/iso/index.html
-    pub fn from_symbol_str(s: &str) -> MoneySymbol {
-        match s {
-            "CNY" => MoneySymbol::CNY,
-            "EUR" => MoneySymbol::EUR,
-            "USD" => MoneySymbol::USD,
-            _ => MoneySymbol::CNY,
-        }
-    }
-
-    pub fn from_money(money: &rusty_money::Money<Currency>) -> MoneySymbol {
-        MoneySymbol::from_symbol_str(&money.currency().symbol.to_string())
-    }
-
-    pub fn currency(&self) -> &'static Currency {
-        match self {
-            MoneySymbol::CNY => CNY,
-            MoneySymbol::EUR => EUR,
-            MoneySymbol::USD => USD,
-        }
-    }
-
-    // string_to_money("¥18,443").unwrap();
-    // string_to_money("$18,443").unwrap();
-    // string_to_money("€18,443").unwrap();
-    pub fn code(&self) -> String {
-        self.currency().iso_alpha_code.to_string()
-    }
-
-    pub fn symbol_str(&self) -> String {
-        self.currency().symbol.to_string()
-    }
-
-    pub fn zero(&self) -> Money<Currency> {
-        let mut decimal = Decimal::new(0, 0);
-        decimal.set_sign_positive(true);
-        self.with_decimal(decimal)
-    }
-
-    pub fn with_decimal(&self, decimal: Decimal) -> Money<Currency> {
-        let money = rusty_money::Money::from_decimal(decimal, self.currency());
-        money
-    }
-}

+ 75 - 44
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -1,17 +1,20 @@
 use crate::manager::GridUser;
-use crate::services::kv_persistence::{GridKVPersistence, KVTransaction};
-
 use crate::services::grid_meta_editor::GridBlockMetaEditorManager;
+use crate::services::kv_persistence::{GridKVPersistence, KVTransaction};
 use bytes::Bytes;
-use dashmap::DashMap;
 use flowy_collaboration::client_grid::{GridChange, GridMetaPad};
 use flowy_collaboration::entities::revision::Revision;
 use flowy_collaboration::util::make_delta_from_revisions;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
-    Field, FieldChangeset, Grid, GridBlock, GridBlockChangeset, RepeatedFieldOrder, RepeatedRowOrder, Row,
+    CellMetaChangeset, Field, FieldChangeset, Grid, GridBlock, GridBlockChangeset, RepeatedFieldOrder,
+    RepeatedRowOrder, Row, RowMeta, RowMetaChangeset,
 };
+use std::collections::HashMap;
 
+use crate::services::row::{
+    make_row_by_row_id, make_rows, row_meta_from_context, CreateRowContext, CreateRowContextBuilder,
+};
 use flowy_sync::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
 use lib_infra::future::FutureResult;
 use lib_ot::core::PlainTextAttributes;
@@ -73,44 +76,83 @@ impl ClientGridEditor {
         Ok(())
     }
 
-    pub async fn update_block(&self, change: GridBlockChangeset) -> FlowyResult<()> {
-        let _ = self.modify(|grid| Ok(grid.update_block(change)?)).await?;
+    pub async fn update_block(&self, changeset: GridBlockChangeset) -> FlowyResult<()> {
+        let _ = self.modify(|grid| Ok(grid.update_block(changeset)?)).await?;
         Ok(())
     }
 
     pub async fn create_row(&self) -> FlowyResult<()> {
         let fields = self.grid_meta_pad.read().await.get_fields(None)?;
-        let grid_block = match self.grid_meta_pad.read().await.get_blocks().last() {
-            None => Err(FlowyError::internal().context("There is no grid block in this grid")),
-            Some(grid_block) => Ok(grid_block.clone()),
-        }?;
+        let block_id = self.last_block_id().await?;
+        let row = row_meta_from_context(&block_id, CreateRowContextBuilder::new(&fields).build());
+        let row_count = self.block_meta_manager.create_row(row).await?;
+        let changeset = GridBlockChangeset::from_row_count(&block_id, row_count);
+        let _ = self.update_block(changeset).await?;
+        Ok(())
+    }
 
-        let row_count = self.block_meta_manager.create_row(fields, &grid_block).await?;
-        let change = GridBlockChangeset::from_row_count(&grid_block.id, row_count);
-        let _ = self.update_block(change).await?;
+    pub async fn insert_rows(&self, contexts: Vec<CreateRowContext>) -> FlowyResult<()> {
+        let block_id = self.last_block_id().await?;
+        let mut rows_by_block_id: HashMap<String, Vec<RowMeta>> = HashMap::new();
+
+        for ctx in contexts {
+            let row_meta = row_meta_from_context(&block_id, ctx);
+            rows_by_block_id
+                .entry(block_id.clone())
+                .or_insert(Vec::new())
+                .push(row_meta);
+        }
+        let changesets = self.block_meta_manager.insert_row(rows_by_block_id).await?;
+        for changeset in changesets {
+            let _ = self.update_block(changeset).await?;
+        }
         Ok(())
     }
 
-    pub async fn get_rows(&self, row_orders: RepeatedRowOrder) -> FlowyResult<Vec<Row>> {
-        let fields = self.grid_meta_pad.read().await.get_fields(None)?;
-        let rows = self.block_meta_manager.get_rows(fields, row_orders).await?;
-        Ok(rows)
+    pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
+        self.block_meta_manager.update_row(changeset).await
+    }
+
+    pub async fn update_cell(&self, changeset: CellMetaChangeset) -> FlowyResult<()> {
+        let row_changeset: RowMetaChangeset = changeset.into();
+        self.update_row(row_changeset).await
     }
 
-    pub async fn get_all_rows(&self) -> FlowyResult<Vec<Row>> {
+    pub async fn get_rows(&self, row_orders: Option<RepeatedRowOrder>) -> FlowyResult<Vec<Row>> {
+        let row_metas = self.get_row_metas(&row_orders).await?;
         let fields = self.grid_meta_pad.read().await.get_fields(None)?;
-        let grid_blocks = self.grid_meta_pad.read().await.get_blocks();
-        self.block_meta_manager.get_all_rows(grid_blocks, fields).await
+        match row_orders {
+            None => Ok(make_rows(&fields, row_metas)),
+            Some(row_orders) => {
+                let mut row_map: HashMap<String, Row> = make_row_by_row_id(&fields, row_metas);
+                let rows = row_orders
+                    .iter()
+                    .flat_map(|row_order| row_map.remove(&row_order.row_id))
+                    .collect::<Vec<_>>();
+                Ok(rows)
+            }
+        }
     }
 
-    pub async fn delete_rows(&self, row_orders: Option<RepeatedRowOrder>) -> FlowyResult<()> {
-        let row_counts = self.block_meta_manager.delete_rows(row_orders).await?;
-        for (block_id, row_count) in row_counts {
-            let _ = self
-                .update_block(GridBlockChangeset::from_row_count(&block_id, row_count))
-                .await?;
+    pub async fn get_row_metas(&self, row_orders: &Option<RepeatedRowOrder>) -> FlowyResult<Vec<RowMeta>> {
+        match row_orders {
+            None => {
+                let grid_blocks = self.grid_meta_pad.read().await.get_blocks();
+                let row_metas = self.block_meta_manager.get_all_rows(grid_blocks).await?;
+                Ok(row_metas)
+            }
+            Some(row_orders) => {
+                let row_metas = self.block_meta_manager.get_rows(row_orders).await?;
+                Ok(row_metas)
+            }
         }
+    }
 
+    pub async fn delete_rows(&self, row_ids: Vec<String>) -> FlowyResult<()> {
+        let changesets = self.block_meta_manager.delete_rows(row_ids).await?;
+        for changeset in changesets {
+            let _ = self.update_block(changeset).await?;
+        }
         Ok(())
     }
 
@@ -165,6 +207,13 @@ impl ClientGridEditor {
             .await?;
         Ok(())
     }
+
+    async fn last_block_id(&self) -> FlowyResult<String> {
+        match self.grid_meta_pad.read().await.get_blocks().last() {
+            None => Err(FlowyError::internal().context("There is no grid block in this grid")),
+            Some(grid_block) => Ok(grid_block.id.clone()),
+        }
+    }
 }
 
 #[cfg(feature = "flowy_unit_test")]
@@ -174,24 +223,6 @@ impl ClientGridEditor {
     }
 }
 
-async fn load_all_fields(
-    grid_pad: &GridMetaPad,
-    kv_persistence: &Arc<GridKVPersistence>,
-) -> FlowyResult<DashMap<String, Field>> {
-    let field_ids = grid_pad
-        .fields()
-        .iter()
-        .map(|field| field.id.clone())
-        .collect::<Vec<_>>();
-
-    let fields = kv_persistence.batch_get::<Field>(field_ids)?;
-    let map = DashMap::new();
-    for field in fields {
-        map.insert(field.id.clone(), field);
-    }
-    Ok(map)
-}
-
 pub struct GridPadBuilder();
 impl RevisionObjectBuilder for GridPadBuilder {
     type Output = GridMetaPad;

+ 105 - 27
frontend/rust-lib/flowy-grid/src/services/grid_meta_editor.rs

@@ -1,5 +1,5 @@
 use crate::manager::GridUser;
-use crate::services::row::{make_row_by_row_id, make_row_ids_per_block, make_rows, RowBuilder};
+use crate::services::row::make_row_ids_per_block;
 use bytes::Bytes;
 
 use dashmap::DashMap;
@@ -7,7 +7,9 @@ use flowy_collaboration::client_grid::{GridBlockMetaChange, GridBlockMetaPad};
 use flowy_collaboration::entities::revision::Revision;
 use flowy_collaboration::util::make_delta_from_revisions;
 use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::entities::{Field, GridBlock, RepeatedRowOrder, Row, RowMeta, RowMetaChangeset};
+use flowy_grid_data_model::entities::{
+    GridBlock, GridBlockChangeset, RepeatedRowOrder, RowMeta, RowMetaChangeset, RowOrder,
+};
 use flowy_sync::disk::SQLiteGridBlockMetaRevisionPersistence;
 use flowy_sync::{
     RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder, RevisionPersistence,
@@ -16,19 +18,30 @@ use lib_infra::future::FutureResult;
 use lib_ot::core::PlainTextAttributes;
 
 use std::collections::HashMap;
+
+use dashmap::mapref::one::Ref;
 use std::sync::Arc;
 use tokio::sync::RwLock;
 
+type RowId = String;
+type BlockId = String;
+
 pub(crate) struct GridBlockMetaEditorManager {
     user: Arc<dyn GridUser>,
     editor_map: DashMap<String, Arc<ClientGridBlockMetaEditor>>,
+    block_id_by_row_id: DashMap<BlockId, RowId>,
 }
 
 impl GridBlockMetaEditorManager {
     pub(crate) async fn new(user: &Arc<dyn GridUser>, blocks: Vec<GridBlock>) -> FlowyResult<Self> {
         let editor_map = make_block_meta_editor_map(user, blocks).await?;
         let user = user.clone();
-        let manager = Self { user, editor_map };
+        let block_id_by_row_id = DashMap::new();
+        let manager = Self {
+            user,
+            editor_map,
+            block_id_by_row_id,
+        };
         Ok(manager)
     }
 
@@ -44,40 +57,98 @@ impl GridBlockMetaEditorManager {
         }
     }
 
-    pub(crate) async fn create_row(&self, fields: Vec<Field>, grid_block: &GridBlock) -> FlowyResult<i32> {
-        let row = RowBuilder::new(&fields, &grid_block.id).build();
-        let editor = self.get_editor(&grid_block.id).await?;
+    pub(crate) async fn create_row(&self, row: RowMeta) -> FlowyResult<i32> {
+        self.block_id_by_row_id.insert(row.id.clone(), row.block_id.clone());
+        let editor = self.get_editor(&row.block_id).await?;
         editor.create_row(row).await
     }
 
-    pub(crate) async fn delete_rows(&self, _row_orders: Option<RepeatedRowOrder>) -> FlowyResult<Vec<(String, i32)>> {
-        Ok(vec![("".to_owned(), 2)])
+    pub(crate) async fn insert_row(
+        &self,
+        rows_by_block_id: HashMap<String, Vec<RowMeta>>,
+    ) -> FlowyResult<Vec<GridBlockChangeset>> {
+        let mut changesets = vec![];
+        for (block_id, rows) in rows_by_block_id {
+            let editor = self.get_editor(&block_id).await?;
+            let mut row_count = 0;
+            for row in rows {
+                self.block_id_by_row_id.insert(row.id.clone(), row.block_id.clone());
+                row_count = editor.create_row(row).await?;
+            }
+            changesets.push(GridBlockChangeset::from_row_count(&block_id, row_count));
+        }
+
+        Ok(changesets)
     }
 
-    pub(crate) async fn get_all_rows(&self, grid_blocks: Vec<GridBlock>, fields: Vec<Field>) -> FlowyResult<Vec<Row>> {
-        let mut rows = vec![];
+    pub(crate) async fn delete_rows(&self, row_ids: Vec<String>) -> FlowyResult<Vec<GridBlockChangeset>> {
+        let row_orders = row_ids
+            .into_iter()
+            .flat_map(|row_id| match self.block_id_by_row_id.get(&row_id) {
+                None => None,
+                Some(block_id) => Some(RowOrder {
+                    row_id,
+                    block_id: block_id.clone(),
+                }),
+            })
+            .collect::<Vec<RowOrder>>();
+        let mut changesets = vec![];
+        let row_ids_per_blocks = make_row_ids_per_block(&row_orders);
+        for row_ids_per_block in row_ids_per_blocks {
+            let editor = self.get_editor(&row_ids_per_block.block_id).await?;
+            let row_count = editor.delete_rows(row_ids_per_block.row_ids).await?;
+
+            let changeset = GridBlockChangeset::from_row_count(&row_ids_per_block.block_id, row_count);
+            changesets.push(changeset);
+        }
+
+        Ok(changesets)
+    }
+
+    pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
+        match self.block_id_by_row_id.get(&changeset.row_id) {
+            None => {
+                let msg = format!(
+                    "Update Row failed. Can't find the corresponding block with row_id: {}",
+                    changeset.row_id
+                );
+                Err(FlowyError::internal().context(msg))
+            }
+            Some(block_id) => {
+                let editor = self.get_editor(&block_id).await?;
+                editor.update_row(changeset).await
+            }
+        }
+    }
+
+    pub(crate) async fn get_all_rows(&self, grid_blocks: Vec<GridBlock>) -> FlowyResult<Vec<RowMeta>> {
+        let mut row_metas = vec![];
         for grid_block in grid_blocks {
             let editor = self.get_editor(&grid_block.id).await?;
-            let row_metas = editor.get_rows(None).await?;
-            rows.extend(make_rows(&fields, row_metas));
+            let new_row_metas = editor.get_rows(None).await?;
+            new_row_metas.iter().for_each(|row_meta| {
+                self.block_id_by_row_id
+                    .insert(row_meta.id.clone(), row_meta.block_id.clone());
+            });
+
+            row_metas.extend(new_row_metas);
         }
-        Ok(rows)
+        Ok(row_metas)
     }
 
-    pub(crate) async fn get_rows(&self, fields: Vec<Field>, row_orders: RepeatedRowOrder) -> FlowyResult<Vec<Row>> {
-        let row_ids_per_blocks = make_row_ids_per_block(&row_orders);
-        let mut row_map: HashMap<String, Row> = HashMap::new();
+    pub(crate) async fn get_rows(&self, row_orders: &RepeatedRowOrder) -> FlowyResult<Vec<RowMeta>> {
+        let row_ids_per_blocks = make_row_ids_per_block(row_orders);
+        let mut row_metas = vec![];
         for row_ids_per_block in row_ids_per_blocks {
             let editor = self.get_editor(&row_ids_per_block.block_id).await?;
-            let row_metas = editor.get_rows(Some(row_ids_per_block.row_ids)).await?;
-            row_map.extend(make_row_by_row_id(&fields, row_metas));
+            let new_row_metas = editor.get_rows(Some(row_ids_per_block.row_ids)).await?;
+            new_row_metas.iter().for_each(|row_meta| {
+                self.block_id_by_row_id
+                    .insert(row_meta.id.clone(), row_meta.block_id.clone());
+            });
+            row_metas.extend(new_row_metas);
         }
-
-        let rows = row_orders
-            .iter()
-            .flat_map(|row_order| row_map.remove(&row_order.row_id))
-            .collect::<Vec<_>>();
-        Ok(rows)
+        Ok(row_metas)
     }
 }
 
@@ -148,9 +219,16 @@ impl ClientGridBlockMetaEditor {
         Ok(row_count)
     }
 
-    pub async fn delete_rows(&self, ids: Vec<String>) -> FlowyResult<()> {
-        let _ = self.modify(|pad| Ok(pad.delete_rows(&ids)?)).await?;
-        Ok(())
+    pub async fn delete_rows(&self, ids: Vec<String>) -> FlowyResult<i32> {
+        let mut row_count = 0;
+        let _ = self
+            .modify(|pad| {
+                let changeset = pad.delete_rows(&ids)?;
+                row_count = pad.number_of_rows();
+                Ok(changeset)
+            })
+            .await?;
+        Ok(row_count)
     }
 
     pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {

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

@@ -1,5 +1,6 @@
 mod util;
 
+pub mod cell;
 pub mod field;
 pub mod grid_editor;
 pub mod grid_meta_editor;

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

@@ -1,5 +1,4 @@
-use crate::services::field::*;
-
+use crate::services::cell::*;
 use flowy_error::FlowyError;
 use flowy_grid_data_model::entities::{Field, FieldType};
 

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

@@ -1,24 +1,60 @@
-use flowy_grid_data_model::entities::{CellMeta, Field, RowMeta};
+use flowy_grid_data_model::entities::{CellMeta, Field, RowMeta, DEFAULT_ROW_HEIGHT};
+use std::collections::HashMap;
 
-pub struct RowBuilder<'a> {
-    fields: &'a Vec<Field>,
-    row: RowMeta,
+pub struct CreateRowContextBuilder<'a> {
+    #[allow(dead_code)]
+    fields: &'a [Field],
+    ctx: CreateRowContext,
 }
 
-impl<'a> RowBuilder<'a> {
-    pub fn new(fields: &'a Vec<Field>, block_id: &'a String) -> Self {
-        let row = RowMeta::new(block_id);
-        Self { fields, row }
+impl<'a> CreateRowContextBuilder<'a> {
+    pub fn new(fields: &'a [Field]) -> Self {
+        let ctx = CreateRowContext {
+            row_id: uuid::Uuid::new_v4().to_string(),
+            cell_by_field_id: Default::default(),
+            height: DEFAULT_ROW_HEIGHT,
+            visibility: true,
+        };
+        Self { fields, ctx }
     }
 
     #[allow(dead_code)]
     pub fn add_cell(mut self, field_id: &str, data: String) -> Self {
         let cell = CellMeta::new(field_id, data);
-        self.row.cell_by_field_id.insert(field_id.to_owned(), cell);
+        self.ctx.cell_by_field_id.insert(field_id.to_owned(), cell);
+        self
+    }
+
+    #[allow(dead_code)]
+    pub fn height(mut self, height: i32) -> Self {
+        self.ctx.height = height;
+        self
+    }
+
+    #[allow(dead_code)]
+    pub fn visibility(mut self, visibility: bool) -> Self {
+        self.ctx.visibility = visibility;
         self
     }
 
-    pub fn build(self) -> RowMeta {
-        self.row
+    pub fn build(self) -> CreateRowContext {
+        self.ctx
     }
 }
+
+pub fn row_meta_from_context(block_id: &str, ctx: CreateRowContext) -> RowMeta {
+    RowMeta {
+        id: ctx.row_id,
+        block_id: block_id.to_owned(),
+        cell_by_field_id: ctx.cell_by_field_id,
+        height: ctx.height,
+        visibility: ctx.visibility,
+    }
+}
+
+pub struct CreateRowContext {
+    pub row_id: String,
+    pub cell_by_field_id: HashMap<String, CellMeta>,
+    pub height: i32,
+    pub visibility: bool,
+}

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

@@ -1,5 +1,5 @@
 use crate::services::row::stringify_deserialize;
-use flowy_grid_data_model::entities::{Cell, CellMeta, Field, RepeatedRowOrder, Row, RowMeta};
+use flowy_grid_data_model::entities::{Cell, CellMeta, Field, RepeatedRowOrder, Row, RowMeta, RowOrder};
 use rayon::iter::{IntoParallelIterator, ParallelIterator};
 use std::collections::HashMap;
 
@@ -8,7 +8,7 @@ pub(crate) struct RowIdsPerBlock {
     pub(crate) row_ids: Vec<String>,
 }
 
-pub(crate) fn make_row_ids_per_block(row_orders: &RepeatedRowOrder) -> Vec<RowIdsPerBlock> {
+pub(crate) fn make_row_ids_per_block(row_orders: &[RowOrder]) -> Vec<RowIdsPerBlock> {
     let mut map: HashMap<String, RowIdsPerBlock> = HashMap::new();
     row_orders.iter().for_each(|row_order| {
         let block_id = row_order.block_id.clone();
@@ -21,7 +21,7 @@ pub(crate) fn make_row_ids_per_block(row_orders: &RepeatedRowOrder) -> Vec<RowId
     map.into_values().collect::<Vec<_>>()
 }
 
-pub(crate) fn make_rows(fields: &Vec<Field>, row_metas: Vec<RowMeta>) -> Vec<Row> {
+pub(crate) fn make_rows(fields: &[Field], row_metas: Vec<RowMeta>) -> Vec<Row> {
     let field_map = fields
         .iter()
         .map(|field| (&field.id, field))
@@ -59,7 +59,7 @@ fn make_cell(field_map: &HashMap<&String, &Field>, field_id: String, raw_cell: C
     }
 }
 
-pub(crate) fn make_row_by_row_id(fields: &Vec<Field>, row_metas: Vec<RowMeta>) -> HashMap<String, Row> {
+pub(crate) fn make_row_by_row_id(fields: &[Field], row_metas: Vec<RowMeta>) -> HashMap<String, Row> {
     let field_map = fields
         .iter()
         .map(|field| (&field.id, field))

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

@@ -1,4 +1,4 @@
-use crate::services::field::MoneySymbol;
+use crate::services::cell::MoneySymbol;
 use flowy_error::FlowyError;
 use flowy_grid_data_model::entities::{AnyData, Field, FieldType};
 use lazy_static::lazy_static;
@@ -99,31 +99,6 @@ fn crop_letters(s: &mut String, pos: usize) {
     }
 }
 
-pub fn string_to_bool(bool_str: &str) -> bool {
-    let lower_case_str: &str = &bool_str.to_lowercase();
-    match lower_case_str {
-        "1" => true,
-        "true" => true,
-        "yes" => true,
-        "0" => false,
-        "false" => false,
-        "no" => false,
-        _ => false,
-    }
-}
-
 pub fn uuid() -> String {
     uuid::Uuid::new_v4().to_string()
 }
-
-pub fn check_type_id(data: &AnyData, field: &Field) -> Result<(), FlowyError> {
-    let field_type = FieldType::from_type_id(&data.type_id).map_err(|e| FlowyError::internal().context(e))?;
-    if field_type != field.field_type {
-        tracing::error!(
-            "expected field type: {:?} but receive {:?} ",
-            field_type,
-            field.field_type
-        );
-    }
-    Ok(())
-}

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

@@ -1,15 +1,16 @@
+use crate::services::cell::*;
 use crate::services::field::*;
 use flowy_collaboration::client_grid::{BuildGridInfo, GridBuilder};
 use flowy_grid_data_model::entities::FieldType;
 
 pub fn make_default_grid(grid_id: &str) -> BuildGridInfo {
-    let text_field = FieldBuilder::new(RichTextTypeOptionsBuilder::new())
+    let text_field = FieldBuilder::new(RichTextTypeOptionsBuilder::default())
         .name("Name")
         .visibility(true)
         .field_type(FieldType::RichText)
         .build();
 
-    let single_select = SingleSelectTypeOptionsBuilder::new()
+    let single_select = SingleSelectTypeOptionsBuilder::default()
         .option(SelectOption::new("Done"))
         .option(SelectOption::new("Progress"));
 

+ 101 - 8
frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs

@@ -1,7 +1,10 @@
 use crate::grid::script::EditorScript::*;
 use crate::grid::script::*;
 use flowy_grid::services::field::{SelectOption, SingleSelectDescription};
-use flowy_grid_data_model::entities::{FieldChangeset, GridBlock, GridBlockChangeset};
+use flowy_grid::services::row::CreateRowContextBuilder;
+use flowy_grid_data_model::entities::{
+    FieldChangeset, FieldType, GridBlock, GridBlockChangeset, Row, RowMetaChangeset,
+};
 
 #[tokio::test]
 async fn default_grid_test() {
@@ -55,7 +58,7 @@ async fn grid_create_duplicate_field() {
 #[tokio::test]
 async fn grid_update_field_with_empty_change() {
     let single_select_field = create_single_select_field();
-    let change = FieldChangeset {
+    let changeset = FieldChangeset {
         field_id: single_select_field.id.clone(),
         name: None,
         desc: None,
@@ -70,7 +73,7 @@ async fn grid_update_field_with_empty_change() {
         CreateField {
             field: single_select_field.clone(),
         },
-        UpdateField { change },
+        UpdateField { changeset },
         AssertFieldEqual {
             field_index: 2,
             field: single_select_field,
@@ -87,7 +90,7 @@ async fn grid_update_field() {
     let mut single_select_type_options = SingleSelectDescription::from(&single_select_field);
     single_select_type_options.options.push(SelectOption::new("Unknown"));
 
-    let change = FieldChangeset {
+    let changeset = FieldChangeset {
         field_id: single_select_field.id.clone(),
         name: None,
         desc: None,
@@ -106,7 +109,7 @@ async fn grid_update_field() {
         CreateField {
             field: single_select_field.clone(),
         },
-        UpdateField { change },
+        UpdateField { changeset },
         AssertFieldEqual {
             field_index: 2,
             field: cloned_field,
@@ -145,7 +148,7 @@ async fn grid_create_block() {
 async fn grid_update_block() {
     let grid_block = GridBlock::new();
     let mut cloned_grid_block = grid_block.clone();
-    let change = GridBlockChangeset {
+    let changeset = GridBlockChangeset {
         block_id: grid_block.id.clone(),
         start_row_index: Some(2),
         row_count: Some(10),
@@ -157,7 +160,7 @@ async fn grid_update_block() {
     let scripts = vec![
         AssertBlockCount(1),
         CreateBlock { block: grid_block },
-        UpdateBlock { change },
+        UpdateBlock { changeset },
         AssertBlockCount(2),
         AssertBlockEqual {
             block_index: 1,
@@ -169,6 +172,96 @@ async fn grid_update_block() {
 
 #[tokio::test]
 async fn grid_create_row() {
-    let scripts = vec![AssertRowCount(3), CreateRow, CreateRow, AssertRowCount(5)];
+    let scripts = vec![AssertRowCount(3), CreateEmptyRow, CreateEmptyRow, AssertRowCount(5)];
     GridEditorTest::new().await.run_scripts(scripts).await;
 }
+
+#[tokio::test]
+async fn grid_create_row2() {
+    let mut test = GridEditorTest::new().await;
+    let create_row_context = CreateRowContextBuilder::new(&test.fields).build();
+    let scripts = vec![
+        AssertRowCount(3),
+        CreateRow {
+            context: create_row_context,
+        },
+        AssertRowCount(4),
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn grid_update_row() {
+    let mut test = GridEditorTest::new().await;
+    let context = CreateRowContextBuilder::new(&test.fields).build();
+    let changeset = RowMetaChangeset {
+        row_id: context.row_id.clone(),
+        height: None,
+        visibility: None,
+        cell_by_field_id: Default::default(),
+    };
+
+    let scripts = vec![
+        AssertRowCount(3),
+        CreateRow { context },
+        UpdateRow {
+            changeset: changeset.clone(),
+        },
+        AssertRow { changeset },
+        AssertRowCount(4),
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn grid_delete_row() {
+    let mut test = GridEditorTest::new().await;
+    let context_1 = CreateRowContextBuilder::new(&test.fields).build();
+    let context_2 = CreateRowContextBuilder::new(&test.fields).build();
+    let row_ids = vec![context_1.row_id.clone(), context_2.row_id.clone()];
+    let scripts = vec![
+        AssertRowCount(3),
+        CreateRow { context: context_1 },
+        CreateRow { context: context_2 },
+        AssertBlockCount(1),
+        AssertBlock {
+            block_index: 0,
+            row_count: 5,
+            start_row_index: 0,
+        },
+        DeleteRow { row_ids },
+        AssertBlock {
+            block_index: 0,
+            row_count: 3,
+            start_row_index: 0,
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn grid_update_cell() {
+    let mut test = GridEditorTest::new().await;
+    let mut builder = CreateRowContextBuilder::new(&test.fields);
+    for field in &test.fields {
+        match field.field_type {
+            FieldType::RichText => {
+                builder = builder.add_cell(&field.id, "hello world".to_owned());
+            }
+            FieldType::Number => {
+                builder = builder.add_cell(&field.id, "123".to_owned());
+            }
+            FieldType::DateTime => {
+                builder = builder.add_cell(&field.id, "March 8, 2022".to_owned());
+            }
+            FieldType::SingleSelect => {}
+            FieldType::MultiSelect => {}
+            FieldType::Checkbox => {
+                builder = builder.add_cell(&field.id, "1".to_owned());
+            }
+        }
+    }
+    let context = builder.build();
+    let scripts = vec![AssertRowCount(3), CreateRow { context }];
+    test.run_scripts(scripts).await;
+}

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

@@ -1,8 +1,11 @@
+use flowy_grid::services::cell::*;
 use flowy_grid::services::field::*;
 use flowy_grid::services::grid_editor::{ClientGridEditor, GridPadBuilder};
-use flowy_grid_data_model::entities::{Field, FieldChangeset, FieldType, GridBlock, GridBlockChangeset};
+use flowy_grid::services::row::CreateRowContext;
+use flowy_grid_data_model::entities::{
+    CellMetaChangeset, Field, FieldChangeset, FieldType, GridBlock, GridBlockChangeset, Row, RowMeta, RowMetaChangeset,
+};
 use flowy_sync::REVISION_WRITE_INTERVAL_IN_MILLIS;
-
 use flowy_test::helper::ViewTest;
 use flowy_test::FlowySDKTest;
 use std::sync::Arc;
@@ -10,16 +13,52 @@ use std::time::Duration;
 use tokio::time::sleep;
 
 pub enum EditorScript {
-    CreateField { field: Field },
-    UpdateField { change: FieldChangeset },
-    DeleteField { field: Field },
+    CreateField {
+        field: Field,
+    },
+    UpdateField {
+        changeset: FieldChangeset,
+    },
+    DeleteField {
+        field: Field,
+    },
     AssertFieldCount(usize),
-    AssertFieldEqual { field_index: usize, field: Field },
-    CreateBlock { block: GridBlock },
-    UpdateBlock { change: GridBlockChangeset },
+    AssertFieldEqual {
+        field_index: usize,
+        field: Field,
+    },
+    CreateBlock {
+        block: GridBlock,
+    },
+    UpdateBlock {
+        changeset: GridBlockChangeset,
+    },
     AssertBlockCount(usize),
-    AssertBlockEqual { block_index: usize, block: GridBlock },
-    CreateRow,
+    AssertBlock {
+        block_index: usize,
+        row_count: i32,
+        start_row_index: i32,
+    },
+    AssertBlockEqual {
+        block_index: usize,
+        block: GridBlock,
+    },
+    CreateEmptyRow,
+    CreateRow {
+        context: CreateRowContext,
+    },
+    UpdateRow {
+        changeset: RowMetaChangeset,
+    },
+    AssertRow {
+        changeset: RowMetaChangeset,
+    },
+    DeleteRow {
+        row_ids: Vec<String>,
+    },
+    UpdateCell {
+        changeset: CellMetaChangeset,
+    },
     AssertRowCount(usize),
     // AssertRowEqual{ row_index: usize, row: RowMeta},
     AssertGridMetaPad,
@@ -29,6 +68,9 @@ pub struct GridEditorTest {
     pub sdk: FlowySDKTest,
     pub grid_id: String,
     pub editor: Arc<ClientGridEditor>,
+    pub fields: Vec<Field>,
+    pub grid_blocks: Vec<GridBlock>,
+    pub row_metas: Vec<RowMeta>,
 }
 
 impl GridEditorTest {
@@ -37,8 +79,19 @@ impl GridEditorTest {
         let _ = sdk.init_user().await;
         let test = ViewTest::new_grid_view(&sdk).await;
         let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap();
+        let fields = editor.get_fields(None).await.unwrap();
+        let grid_blocks = editor.get_blocks().await.unwrap();
+        let row_metas = editor.get_row_metas(&None).await.unwrap();
+
         let grid_id = test.view.id;
-        Self { sdk, grid_id, editor }
+        Self {
+            sdk,
+            grid_id,
+            editor,
+            fields,
+            grid_blocks,
+            row_metas,
+        }
     }
 
     pub async fn run_scripts(&mut self, scripts: Vec<EditorScript>) {
@@ -56,16 +109,20 @@ impl GridEditorTest {
         match script {
             EditorScript::CreateField { field } => {
                 self.editor.create_field(field).await.unwrap();
+                self.fields = self.editor.get_fields(None).await.unwrap();
             }
-            EditorScript::UpdateField { change } => {
+            EditorScript::UpdateField { changeset: change } => {
                 self.editor.update_field(change).await.unwrap();
+                self.fields = self.editor.get_fields(None).await.unwrap();
             }
             EditorScript::DeleteField { field } => {
                 self.editor.delete_field(&field.id).await.unwrap();
+                self.fields = self.editor.get_fields(None).await.unwrap();
             }
             EditorScript::AssertFieldCount(count) => {
                 assert_eq!(self.editor.get_fields(None).await.unwrap().len(), count);
             }
+
             EditorScript::AssertFieldEqual { field_index, field } => {
                 let repeated_fields = self.editor.get_fields(None).await.unwrap();
                 let compared_field = repeated_fields[field_index].clone();
@@ -73,23 +130,60 @@ impl GridEditorTest {
             }
             EditorScript::CreateBlock { block } => {
                 self.editor.create_block(block).await.unwrap();
+                self.grid_blocks = self.editor.get_blocks().await.unwrap();
             }
-            EditorScript::UpdateBlock { change } => {
+            EditorScript::UpdateBlock { changeset: change } => {
                 self.editor.update_block(change).await.unwrap();
             }
             EditorScript::AssertBlockCount(count) => {
                 assert_eq!(self.editor.get_blocks().await.unwrap().len(), count);
             }
+            EditorScript::AssertBlock {
+                block_index,
+                row_count,
+                start_row_index,
+            } => {
+                assert_eq!(self.grid_blocks[block_index].row_count, row_count);
+                assert_eq!(self.grid_blocks[block_index].start_row_index, start_row_index);
+            }
             EditorScript::AssertBlockEqual { block_index, block } => {
                 let blocks = self.editor.get_blocks().await.unwrap();
                 let compared_block = blocks[block_index].clone();
                 assert_eq!(compared_block, block);
             }
-            EditorScript::CreateRow => {
+            EditorScript::CreateEmptyRow => {
                 self.editor.create_row().await.unwrap();
+                self.row_metas = self.editor.get_row_metas(&None).await.unwrap();
+                self.grid_blocks = self.editor.get_blocks().await.unwrap();
+            }
+            EditorScript::CreateRow { context } => {
+                self.editor.insert_rows(vec![context]).await.unwrap();
+                self.row_metas = self.editor.get_row_metas(&None).await.unwrap();
+                self.grid_blocks = self.editor.get_blocks().await.unwrap();
+            }
+            EditorScript::UpdateRow { changeset: change } => self.editor.update_row(change).await.unwrap(),
+            EditorScript::DeleteRow { row_ids } => {
+                self.editor.delete_rows(row_ids).await.unwrap();
+                self.row_metas = self.editor.get_row_metas(&None).await.unwrap();
+                self.grid_blocks = self.editor.get_blocks().await.unwrap();
+            }
+            EditorScript::AssertRow { changeset } => {
+                let row = self.row_metas.iter().find(|row| row.id == changeset.row_id).unwrap();
+
+                if let Some(visibility) = changeset.visibility {
+                    assert_eq!(row.visibility, visibility);
+                }
+
+                if let Some(height) = changeset.height {
+                    assert_eq!(row.height, height);
+                }
+            }
+            EditorScript::UpdateCell { changeset } => {
+                self.editor.update_cell(changeset).await.unwrap();
+                self.row_metas = self.editor.get_row_metas(&None).await.unwrap();
             }
             EditorScript::AssertRowCount(count) => {
-                assert_eq!(self.editor.get_all_rows().await.unwrap().len(), count);
+                assert_eq!(self.editor.get_rows(None).await.unwrap().len(), count);
             }
             EditorScript::AssertGridMetaPad => {
                 sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await;
@@ -102,7 +196,7 @@ impl GridEditorTest {
 }
 
 pub fn create_text_field() -> Field {
-    FieldBuilder::new(RichTextTypeOptionsBuilder::new())
+    FieldBuilder::new(RichTextTypeOptionsBuilder::default())
         .name("Name")
         .visibility(true)
         .field_type(FieldType::RichText)
@@ -110,7 +204,7 @@ pub fn create_text_field() -> Field {
 }
 
 pub fn create_single_select_field() -> Field {
-    let single_select = SingleSelectTypeOptionsBuilder::new()
+    let single_select = SingleSelectTypeOptionsBuilder::default()
         .option(SelectOption::new("Done"))
         .option(SelectOption::new("Progress"));
 

+ 1 - 0
shared-lib/flowy-collaboration/src/client_grid/grid_builder.rs

@@ -33,6 +33,7 @@ impl GridBuilder {
     pub fn add_empty_row(mut self) -> Self {
         let row = RowMeta::new(&self.grid_block.id);
         self.grid_block_meta.rows.push(row);
+        self.grid_block.row_count += 1;
         self
     }
 

+ 13 - 13
shared-lib/flowy-collaboration/src/client_grid/grid_pad.rs

@@ -82,41 +82,41 @@ impl GridMetaPad {
         }
     }
 
-    pub fn update_field(&mut self, change: FieldChangeset) -> CollaborateResult<Option<GridChange>> {
-        let field_id = change.field_id.clone();
+    pub fn update_field(&mut self, changeset: FieldChangeset) -> CollaborateResult<Option<GridChange>> {
+        let field_id = changeset.field_id.clone();
         self.modify_field(&field_id, |field| {
             let mut is_changed = None;
-            if let Some(name) = change.name {
+            if let Some(name) = changeset.name {
                 field.name = name;
                 is_changed = Some(())
             }
 
-            if let Some(desc) = change.desc {
+            if let Some(desc) = changeset.desc {
                 field.desc = desc;
                 is_changed = Some(())
             }
 
-            if let Some(field_type) = change.field_type {
+            if let Some(field_type) = changeset.field_type {
                 field.field_type = field_type;
                 is_changed = Some(())
             }
 
-            if let Some(frozen) = change.frozen {
+            if let Some(frozen) = changeset.frozen {
                 field.frozen = frozen;
                 is_changed = Some(())
             }
 
-            if let Some(visibility) = change.visibility {
+            if let Some(visibility) = changeset.visibility {
                 field.visibility = visibility;
                 is_changed = Some(())
             }
 
-            if let Some(width) = change.width {
+            if let Some(width) = changeset.width {
                 field.width = width;
                 is_changed = Some(())
             }
 
-            if let Some(type_options) = change.type_options {
+            if let Some(type_options) = changeset.type_options {
                 field.type_options = type_options;
                 is_changed = Some(())
             }
@@ -141,17 +141,17 @@ impl GridMetaPad {
         self.grid_meta.blocks.clone()
     }
 
-    pub fn update_block(&mut self, change: GridBlockChangeset) -> CollaborateResult<Option<GridChange>> {
-        let block_id = change.block_id.clone();
+    pub fn update_block(&mut self, changeset: GridBlockChangeset) -> CollaborateResult<Option<GridChange>> {
+        let block_id = changeset.block_id.clone();
         self.modify_block(&block_id, |block| {
             let mut is_changed = None;
 
-            if let Some(row_count) = change.row_count {
+            if let Some(row_count) = changeset.row_count {
                 block.row_count = row_count;
                 is_changed = Some(());
             }
 
-            if let Some(start_row_index) = change.start_row_index {
+            if let Some(start_row_index) = changeset.start_row_index {
                 block.start_row_index = start_row_index;
                 is_changed = Some(());
             }

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

@@ -297,3 +297,36 @@ impl CellMeta {
         }
     }
 }
+
+#[derive(Debug, Clone, Default, ProtoBuf)]
+pub struct CellMetaChangeset {
+    #[pb(index = 1)]
+    pub row_id: String,
+
+    #[pb(index = 2)]
+    pub field_id: String,
+
+    #[pb(index = 3, one_of)]
+    pub data: Option<String>,
+}
+
+impl std::convert::From<CellMetaChangeset> for RowMetaChangeset {
+    fn from(changeset: CellMetaChangeset) -> Self {
+        let mut cell_by_field_id = HashMap::with_capacity(1);
+        if let Some(data) = changeset.data {
+            let field_id = changeset.field_id;
+            let cell_meta = CellMeta {
+                field_id: field_id.clone(),
+                data,
+            };
+            cell_by_field_id.insert(field_id, cell_meta);
+        }
+
+        RowMetaChangeset {
+            row_id: changeset.row_id,
+            height: None,
+            visibility: None,
+            cell_by_field_id,
+        }
+    }
+}

+ 290 - 4
shared-lib/flowy-grid-data-model/src/protobuf/model/meta.rs

@@ -2956,6 +2956,289 @@ impl ::protobuf::reflect::ProtobufValue for CellMeta {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct CellMetaChangeset {
+    // message fields
+    pub row_id: ::std::string::String,
+    pub field_id: ::std::string::String,
+    // message oneof groups
+    pub one_of_data: ::std::option::Option<CellMetaChangeset_oneof_one_of_data>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a CellMetaChangeset {
+    fn default() -> &'a CellMetaChangeset {
+        <CellMetaChangeset as ::protobuf::Message>::default_instance()
+    }
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum CellMetaChangeset_oneof_one_of_data {
+    data(::std::string::String),
+}
+
+impl CellMetaChangeset {
+    pub fn new() -> CellMetaChangeset {
+        ::std::default::Default::default()
+    }
+
+    // string row_id = 1;
+
+
+    pub fn get_row_id(&self) -> &str {
+        &self.row_id
+    }
+    pub fn clear_row_id(&mut self) {
+        self.row_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_row_id(&mut self, v: ::std::string::String) {
+        self.row_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_row_id(&mut self) -> &mut ::std::string::String {
+        &mut self.row_id
+    }
+
+    // Take field
+    pub fn take_row_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.row_id, ::std::string::String::new())
+    }
+
+    // string 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())
+    }
+
+    // string data = 3;
+
+
+    pub fn get_data(&self) -> &str {
+        match self.one_of_data {
+            ::std::option::Option::Some(CellMetaChangeset_oneof_one_of_data::data(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_data(&mut self) {
+        self.one_of_data = ::std::option::Option::None;
+    }
+
+    pub fn has_data(&self) -> bool {
+        match self.one_of_data {
+            ::std::option::Option::Some(CellMetaChangeset_oneof_one_of_data::data(..)) => 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(CellMetaChangeset_oneof_one_of_data::data(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_data(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(CellMetaChangeset_oneof_one_of_data::data(_)) = self.one_of_data {
+        } else {
+            self.one_of_data = ::std::option::Option::Some(CellMetaChangeset_oneof_one_of_data::data(::std::string::String::new()));
+        }
+        match self.one_of_data {
+            ::std::option::Option::Some(CellMetaChangeset_oneof_one_of_data::data(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(CellMetaChangeset_oneof_one_of_data::data(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+}
+
+impl ::protobuf::Message for CellMetaChangeset {
+    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.row_id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?;
+                },
+                3 => {
+                    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(CellMetaChangeset_oneof_one_of_data::data(is.read_string()?));
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.row_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.row_id);
+        }
+        if !self.field_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.field_id);
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_data {
+            match v {
+                &CellMetaChangeset_oneof_one_of_data::data(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 !self.row_id.is_empty() {
+            os.write_string(1, &self.row_id)?;
+        }
+        if !self.field_id.is_empty() {
+            os.write_string(2, &self.field_id)?;
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_data {
+            match v {
+                &CellMetaChangeset_oneof_one_of_data::data(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() -> CellMetaChangeset {
+        CellMetaChangeset::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>(
+                "row_id",
+                |m: &CellMetaChangeset| { &m.row_id },
+                |m: &mut CellMetaChangeset| { &mut m.row_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "field_id",
+                |m: &CellMetaChangeset| { &m.field_id },
+                |m: &mut CellMetaChangeset| { &mut m.field_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "data",
+                CellMetaChangeset::has_data,
+                CellMetaChangeset::get_data,
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CellMetaChangeset>(
+                "CellMetaChangeset",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static CellMetaChangeset {
+        static instance: ::protobuf::rt::LazyV2<CellMetaChangeset> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(CellMetaChangeset::new)
+    }
+}
+
+impl ::protobuf::Clear for CellMetaChangeset {
+    fn clear(&mut self) {
+        self.row_id.clear();
+        self.field_id.clear();
+        self.one_of_data = ::std::option::Option::None;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for CellMetaChangeset {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for CellMetaChangeset {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(Clone,PartialEq,Eq,Debug,Hash)]
 pub enum FieldType {
     RichText = 0,
@@ -3059,10 +3342,13 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x01(\tR\x03key\x12\x1f\n\x05value\x18\x02\x20\x01(\x0b2\t.CellMetaR\x05\
     value:\x028\x01B\x0f\n\rone_of_heightB\x13\n\x11one_of_visibility\"9\n\
     \x08CellMeta\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\
-    \x12\n\x04data\x18\x02\x20\x01(\tR\x04data*d\n\tFieldType\x12\x0c\n\x08R\
-    ichText\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\
+    \x12\n\x04data\x18\x02\x20\x01(\tR\x04data\"j\n\x11CellMetaChangeset\x12\
+    \x15\n\x06row_id\x18\x01\x20\x01(\tR\x05rowId\x12\x19\n\x08field_id\x18\
+    \x02\x20\x01(\tR\x07fieldId\x12\x14\n\x04data\x18\x03\x20\x01(\tH\0R\x04\
+    dataB\r\n\x0bone_of_data*d\n\tFieldType\x12\x0c\n\x08RichText\x10\0\x12\
+    \n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\x0cSingle\
+    Select\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;

+ 5 - 0
shared-lib/flowy-grid-data-model/src/protobuf/proto/meta.proto

@@ -58,6 +58,11 @@ message CellMeta {
     string field_id = 1;
     string data = 2;
 }
+message CellMetaChangeset {
+    string row_id = 1;
+    string field_id = 2;
+    oneof one_of_data { string data = 3; };
+}
 enum FieldType {
     RichText = 0;
     Number = 1;