소스 검색

chore: save type option data as map

appflowy 3 년 전
부모
커밋
e0b969595c
19개의 변경된 파일520개의 추가작업 그리고 231개의 파일을 삭제
  1. 51 8
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pb.dart
  2. 23 2
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pbjson.dart
  3. 5 3
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  4. 41 35
      frontend/rust-lib/flowy-grid/src/macros.rs
  5. 32 22
      frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs
  6. 12 13
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs
  7. 7 12
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs
  8. 1 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs
  9. 6 10
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs
  10. 10 18
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs
  11. 6 10
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs
  12. 9 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option_data.rs
  13. 6 14
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  14. 16 16
      frontend/rust-lib/flowy-grid/src/services/row/cell_data_serde.rs
  15. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/script.rs
  16. 54 3
      shared-lib/flowy-grid-data-model/src/entities/meta.rs
  17. 234 61
      shared-lib/flowy-grid-data-model/src/protobuf/model/meta.rs
  18. 4 1
      shared-lib/flowy-grid-data-model/src/protobuf/proto/meta.proto
  19. 2 2
      shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs

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

@@ -215,7 +215,7 @@ class FieldMeta extends $pb.GeneratedMessage {
     ..aOB(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'frozen')
     ..aOB(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'visibility')
     ..a<$core.int>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'width', $pb.PbFieldType.O3)
-    ..aOS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptionJson')
+    ..aOM<TypeOptionDataByFieldTypeId>(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptionByFieldTypeId', subBuilder: TypeOptionDataByFieldTypeId.create)
     ..hasRequiredFields = false
   ;
 
@@ -228,7 +228,7 @@ class FieldMeta extends $pb.GeneratedMessage {
     $core.bool? frozen,
     $core.bool? visibility,
     $core.int? width,
-    $core.String? typeOptionJson,
+    TypeOptionDataByFieldTypeId? typeOptionByFieldTypeId,
   }) {
     final _result = create();
     if (id != null) {
@@ -252,8 +252,8 @@ class FieldMeta extends $pb.GeneratedMessage {
     if (width != null) {
       _result.width = width;
     }
-    if (typeOptionJson != null) {
-      _result.typeOptionJson = typeOptionJson;
+    if (typeOptionByFieldTypeId != null) {
+      _result.typeOptionByFieldTypeId = typeOptionByFieldTypeId;
     }
     return _result;
   }
@@ -342,13 +342,56 @@ class FieldMeta extends $pb.GeneratedMessage {
   void clearWidth() => clearField(7);
 
   @$pb.TagNumber(8)
-  $core.String get typeOptionJson => $_getSZ(7);
+  TypeOptionDataByFieldTypeId get typeOptionByFieldTypeId => $_getN(7);
   @$pb.TagNumber(8)
-  set typeOptionJson($core.String v) { $_setString(7, v); }
+  set typeOptionByFieldTypeId(TypeOptionDataByFieldTypeId v) { setField(8, v); }
   @$pb.TagNumber(8)
-  $core.bool hasTypeOptionJson() => $_has(7);
+  $core.bool hasTypeOptionByFieldTypeId() => $_has(7);
   @$pb.TagNumber(8)
-  void clearTypeOptionJson() => clearField(8);
+  void clearTypeOptionByFieldTypeId() => clearField(8);
+  @$pb.TagNumber(8)
+  TypeOptionDataByFieldTypeId ensureTypeOptionByFieldTypeId() => $_ensure(7);
+}
+
+class TypeOptionDataByFieldTypeId extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'TypeOptionDataByFieldTypeId', createEmptyInstance: create)
+    ..m<$core.String, $core.String>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'map', entryClassName: 'TypeOptionDataByFieldTypeId.MapEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.OS)
+    ..hasRequiredFields = false
+  ;
+
+  TypeOptionDataByFieldTypeId._() : super();
+  factory TypeOptionDataByFieldTypeId({
+    $core.Map<$core.String, $core.String>? map,
+  }) {
+    final _result = create();
+    if (map != null) {
+      _result.map.addAll(map);
+    }
+    return _result;
+  }
+  factory TypeOptionDataByFieldTypeId.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory TypeOptionDataByFieldTypeId.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')
+  TypeOptionDataByFieldTypeId clone() => TypeOptionDataByFieldTypeId()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  TypeOptionDataByFieldTypeId copyWith(void Function(TypeOptionDataByFieldTypeId) updates) => super.copyWith((message) => updates(message as TypeOptionDataByFieldTypeId)) as TypeOptionDataByFieldTypeId; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static TypeOptionDataByFieldTypeId create() => TypeOptionDataByFieldTypeId._();
+  TypeOptionDataByFieldTypeId createEmptyInstance() => create();
+  static $pb.PbList<TypeOptionDataByFieldTypeId> createRepeated() => $pb.PbList<TypeOptionDataByFieldTypeId>();
+  @$core.pragma('dart2js:noInline')
+  static TypeOptionDataByFieldTypeId getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<TypeOptionDataByFieldTypeId>(create);
+  static TypeOptionDataByFieldTypeId? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.Map<$core.String, $core.String> get map => $_getMap(0);
 }
 
 enum FieldChangesetPayload_OneOfName {

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

@@ -69,12 +69,33 @@ const FieldMeta$json = const {
     const {'1': 'frozen', '3': 5, '4': 1, '5': 8, '10': 'frozen'},
     const {'1': 'visibility', '3': 6, '4': 1, '5': 8, '10': 'visibility'},
     const {'1': 'width', '3': 7, '4': 1, '5': 5, '10': 'width'},
-    const {'1': 'type_option_json', '3': 8, '4': 1, '5': 9, '10': 'typeOptionJson'},
+    const {'1': 'type_option_by_field_type_id', '3': 8, '4': 1, '5': 11, '6': '.TypeOptionDataByFieldTypeId', '10': 'typeOptionByFieldTypeId'},
   ],
 };
 
 /// Descriptor for `FieldMeta`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List fieldMetaDescriptor = $convert.base64Decode('CglGaWVsZE1ldGESDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEZGVzYxgDIAEoCVIEZGVzYxIpCgpmaWVsZF90eXBlGAQgASgOMgouRmllbGRUeXBlUglmaWVsZFR5cGUSFgoGZnJvemVuGAUgASgIUgZmcm96ZW4SHgoKdmlzaWJpbGl0eRgGIAEoCFIKdmlzaWJpbGl0eRIUCgV3aWR0aBgHIAEoBVIFd2lkdGgSKAoQdHlwZV9vcHRpb25fanNvbhgIIAEoCVIOdHlwZU9wdGlvbkpzb24=');
+final $typed_data.Uint8List fieldMetaDescriptor = $convert.base64Decode('CglGaWVsZE1ldGESDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEZGVzYxgDIAEoCVIEZGVzYxIpCgpmaWVsZF90eXBlGAQgASgOMgouRmllbGRUeXBlUglmaWVsZFR5cGUSFgoGZnJvemVuGAUgASgIUgZmcm96ZW4SHgoKdmlzaWJpbGl0eRgGIAEoCFIKdmlzaWJpbGl0eRIUCgV3aWR0aBgHIAEoBVIFd2lkdGgSWwocdHlwZV9vcHRpb25fYnlfZmllbGRfdHlwZV9pZBgIIAEoCzIcLlR5cGVPcHRpb25EYXRhQnlGaWVsZFR5cGVJZFIXdHlwZU9wdGlvbkJ5RmllbGRUeXBlSWQ=');
+@$core.Deprecated('Use typeOptionDataByFieldTypeIdDescriptor instead')
+const TypeOptionDataByFieldTypeId$json = const {
+  '1': 'TypeOptionDataByFieldTypeId',
+  '2': const [
+    const {'1': 'map', '3': 1, '4': 3, '5': 11, '6': '.TypeOptionDataByFieldTypeId.MapEntry', '10': 'map'},
+  ],
+  '3': const [TypeOptionDataByFieldTypeId_MapEntry$json],
+};
+
+@$core.Deprecated('Use typeOptionDataByFieldTypeIdDescriptor instead')
+const TypeOptionDataByFieldTypeId_MapEntry$json = const {
+  '1': 'MapEntry',
+  '2': const [
+    const {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'},
+    const {'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'},
+  ],
+  '7': const {'7': true},
+};
+
+/// Descriptor for `TypeOptionDataByFieldTypeId`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List typeOptionDataByFieldTypeIdDescriptor = $convert.base64Decode('ChtUeXBlT3B0aW9uRGF0YUJ5RmllbGRUeXBlSWQSNwoDbWFwGAEgAygLMiUuVHlwZU9wdGlvbkRhdGFCeUZpZWxkVHlwZUlkLk1hcEVudHJ5UgNtYXAaNgoITWFwRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSFAoFdmFsdWUYAiABKAlSBXZhbHVlOgI4AQ==');
 @$core.Deprecated('Use fieldChangesetPayloadDescriptor instead')
 const FieldChangesetPayload$json = const {
   '1': 'FieldChangesetPayload',

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

@@ -1,5 +1,5 @@
 use crate::manager::GridManager;
-use crate::services::field::{type_option_data_from_str, SelectOption};
+use crate::services::field::{type_option_builder_from_json_str, SelectOption};
 use crate::services::grid_editor::ClientGridEditor;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::*;
@@ -115,9 +115,11 @@ pub(crate) async fn get_field_context_handler(
 ) -> DataResult<EditFieldContext, FlowyError> {
     let params = data.into_inner();
     let editor = manager.get_grid_editor(&params.grid_id)?;
-
     let mut field_meta = get_or_create_field_meta(&params, editor).await?;
-    let type_option_data = type_option_data_from_str(&field_meta.type_option_json, &field_meta.field_type);
+    let s = field_meta.get_type_option_str().unwrap();
+    let builder = type_option_builder_from_json_str(&s, &field_meta.field_type);
+    let type_option_data = builder.entry().protobuf_bytes().to_vec();
+
     let field: Field = field_meta.into();
     let edit_context = EditFieldContext {
         grid_id: params.grid_id,

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

@@ -9,15 +9,16 @@ macro_rules! impl_into_box_type_option_builder {
     };
 }
 
-macro_rules! impl_from_json_str_and_from_bytes {
+macro_rules! impl_builder_from_json_str_and_from_bytes {
     ($target: ident,$type_option: ident) => {
         impl $target {
-            pub fn from_json_str(s: &str) -> $target {
-                $target($type_option::from(s))
+            pub fn from_protobuf_bytes(bytes: Bytes) -> $target {
+                let type_option = $type_option::from_protobuf_bytes(bytes);
+                $target(type_option)
             }
 
-            pub fn from_bytes(bytes: Bytes) -> $target {
-                let type_option = $type_option::try_from(bytes).unwrap_or($type_option::default());
+            pub fn from_json_str(s: &str) -> $target {
+                let type_option = $type_option::from_json_str(s);
                 $target(type_option)
             }
         }
@@ -25,48 +26,33 @@ macro_rules! impl_from_json_str_and_from_bytes {
 }
 
 #[macro_export]
-macro_rules! impl_from_and_to_type_option {
+macro_rules! impl_type_option {
     ($target: ident, $field_type:expr) => {
-        impl_from_field_type_option!($target);
-        impl_to_field_type_option!($target, $field_type);
-    };
-}
-
-#[macro_export]
-macro_rules! impl_from_field_type_option {
-    ($target: ident) => {
         impl std::convert::From<&FieldMeta> for $target {
             fn from(field_meta: &FieldMeta) -> $target {
-                $target::from(field_meta.type_option_json.as_str())
+                match field_meta
+                    .type_option_by_field_type_id
+                    .get_entry::<$target>(&$field_type)
+                {
+                    None => $target::default(),
+                    Some(target) => target,
+                }
             }
         }
 
-        impl std::convert::From<&str> for $target {
-            fn from(type_option_str: &str) -> $target {
-                match serde_json::from_str(type_option_str) {
-                    Ok(obj) => obj,
-                    Err(err) => {
-                        tracing::error!("{} convert from any data failed, {:?}", stringify!($target), err);
-                        $target::default()
-                    }
-                }
+        impl std::convert::From<$target> for String {
+            fn from(type_option: $target) -> String {
+                type_option.json_str()
             }
         }
-    };
-}
 
-#[macro_export]
-macro_rules! impl_to_field_type_option {
-    ($target: ident, $field_type:expr) => {
-        impl $target {
-            pub fn field_type(&self) -> FieldType {
+        impl TypeOptionDataEntry for $target {
+            fn field_type(&self) -> FieldType {
                 $field_type
             }
-        }
 
-        impl std::convert::From<$target> for String {
-            fn from(field_description: $target) -> Self {
-                match serde_json::to_string(&field_description) {
+            fn json_str(&self) -> String {
+                match serde_json::to_string(&self) {
                     Ok(s) => s,
                     Err(e) => {
                         tracing::error!("Field type data convert to AnyData fail, error: {:?}", e);
@@ -74,6 +60,26 @@ macro_rules! impl_to_field_type_option {
                     }
                 }
             }
+
+            fn protobuf_bytes(&self) -> Bytes {
+                self.clone().try_into().unwrap()
+            }
+        }
+
+        impl TypeOptionDataFrom for $target {
+            fn from_json_str(s: &str) -> $target {
+                match serde_json::from_str(s) {
+                    Ok(obj) => obj,
+                    Err(err) => {
+                        tracing::error!("{} convert from any data failed, {:?}", stringify!($target), err);
+                        $target::default()
+                    }
+                }
+            }
+
+            fn from_protobuf_bytes(bytes: Bytes) -> $target {
+                $target::try_from(bytes).unwrap_or($target::default())
+            }
         }
     };
 }

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

@@ -1,6 +1,8 @@
 use crate::services::field::type_options::*;
 use bytes::Bytes;
-use flowy_grid_data_model::entities::{FieldMeta, FieldType};
+use flowy_grid_data_model::entities::{
+    Field, FieldMeta, FieldType, TypeOptionDataByFieldTypeId, TypeOptionDataEntry, TypeOptionDataFrom,
+};
 
 pub struct FieldBuilder {
     field_meta: FieldMeta,
@@ -24,6 +26,23 @@ impl FieldBuilder {
         Self::new(type_option_builder)
     }
 
+    pub fn from_field(field: Field, type_option_builder: Box<dyn TypeOptionBuilder>) -> Self {
+        let field_meta = FieldMeta {
+            id: field.id,
+            name: field.name,
+            desc: field.desc,
+            field_type: field.field_type,
+            frozen: field.frozen,
+            visibility: field.visibility,
+            width: field.width,
+            type_option_by_field_type_id: TypeOptionDataByFieldTypeId::default(),
+        };
+        Self {
+            field_meta,
+            type_option_builder,
+        }
+    }
+
     pub fn name(mut self, name: &str) -> Self {
         self.field_meta.name = name.to_owned();
         self
@@ -52,16 +71,16 @@ impl FieldBuilder {
     pub fn build(self) -> FieldMeta {
         debug_assert_eq!(self.field_meta.field_type, self.type_option_builder.field_type());
         let mut field_meta = self.field_meta;
-        let type_option_json = self.type_option_builder.build_type_option_str();
-        field_meta.type_option_json = type_option_json;
+        field_meta
+            .type_option_by_field_type_id
+            .insert_entry(self.type_option_builder.entry());
         field_meta
     }
 }
 
 pub trait TypeOptionBuilder {
     fn field_type(&self) -> FieldType;
-    fn build_type_option_str(&self) -> String;
-    fn build_type_option_data(&self) -> Bytes;
+    fn entry(&self) -> &dyn TypeOptionDataEntry;
 }
 
 pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box<dyn TypeOptionBuilder> {
@@ -88,23 +107,14 @@ pub fn type_option_builder_from_json_str(s: &str, field_type: &FieldType) -> Box
     }
 }
 
-pub fn type_option_builder_from_bytes(bytes: Bytes, field_type: &FieldType) -> Box<dyn TypeOptionBuilder> {
+pub fn type_option_builder_from_bytes<T: Into<Bytes>>(bytes: T, field_type: &FieldType) -> Box<dyn TypeOptionBuilder> {
+    let bytes = bytes.into();
     match field_type {
-        FieldType::RichText => Box::new(RichTextTypeOptionBuilder::from_bytes(bytes)),
-        FieldType::Number => Box::new(NumberTypeOptionBuilder::from_bytes(bytes)),
-        FieldType::DateTime => Box::new(DateTypeOptionBuilder::from_bytes(bytes)),
-        FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_bytes(bytes)),
-        FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_bytes(bytes)),
-        FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_bytes(bytes)),
+        FieldType::RichText => Box::new(RichTextTypeOptionBuilder::from_protobuf_bytes(bytes)),
+        FieldType::Number => Box::new(NumberTypeOptionBuilder::from_protobuf_bytes(bytes)),
+        FieldType::DateTime => Box::new(DateTypeOptionBuilder::from_protobuf_bytes(bytes)),
+        FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_protobuf_bytes(bytes)),
+        FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_protobuf_bytes(bytes)),
+        FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_protobuf_bytes(bytes)),
     }
 }
-
-pub fn type_option_data_from_str(s: &str, field_type: &FieldType) -> Vec<u8> {
-    let builder = type_option_builder_from_json_str(s, field_type);
-    builder.build_type_option_data().to_vec()
-}
-
-pub fn type_option_json_str_from_bytes(bytes: Vec<u8>, field_type: &FieldType) -> String {
-    let builder = type_option_builder_from_bytes(Bytes::from(bytes), field_type);
-    builder.build_type_option_str()
-}

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

@@ -1,16 +1,19 @@
-use crate::impl_from_and_to_type_option;
-use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
+use crate::impl_type_option;
+use crate::services::field::{
+    BoxTypeOptionBuilder, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RichTextTypeOption,
+    SingleSelectTypeOption, TypeOptionBuilder,
+};
 use crate::services::row::CellDataSerde;
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::FlowyError;
-use flowy_grid_data_model::entities::{FieldMeta, FieldType};
+use flowy_grid_data_model::entities::{FieldMeta, FieldType, TypeOptionDataEntry, TypeOptionDataFrom};
 use serde::{Deserialize, Serialize};
 
 #[derive(Default)]
 pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption);
 impl_into_box_type_option_builder!(CheckboxTypeOptionBuilder);
-impl_from_json_str_and_from_bytes!(CheckboxTypeOptionBuilder, CheckboxTypeOption);
+impl_builder_from_json_str_and_from_bytes!(CheckboxTypeOptionBuilder, CheckboxTypeOption);
 
 impl CheckboxTypeOptionBuilder {
     pub fn set_selected(mut self, is_selected: bool) -> Self {
@@ -24,12 +27,8 @@ impl TypeOptionBuilder for CheckboxTypeOptionBuilder {
         self.0.field_type()
     }
 
-    fn build_type_option_str(&self) -> String {
-        self.0.clone().into()
-    }
-
-    fn build_type_option_data(&self) -> Bytes {
-        self.0.clone().try_into().unwrap()
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
     }
 }
 
@@ -38,7 +37,7 @@ pub struct CheckboxTypeOption {
     #[pb(index = 1)]
     pub is_selected: bool,
 }
-impl_from_and_to_type_option!(CheckboxTypeOption, FieldType::Checkbox);
+impl_type_option!(CheckboxTypeOption, FieldType::Checkbox);
 
 impl CellDataSerde for CheckboxTypeOption {
     fn deserialize_cell_data(&self, data: String) -> String {
@@ -47,8 +46,8 @@ impl CellDataSerde for CheckboxTypeOption {
 
     fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> {
         let s = match string_to_bool(data) {
-            true => "1",
-            false => "0",
+            true => "No",
+            false => "Yes",
         };
         Ok(s.to_owned())
     }

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

@@ -1,15 +1,14 @@
-use crate::impl_from_and_to_type_option;
+use crate::impl_type_option;
 use crate::services::row::CellDataSerde;
 use bytes::Bytes;
-
 use chrono::format::strftime::StrftimeItems;
 use chrono::NaiveDateTime;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::FlowyError;
-use flowy_grid_data_model::entities::{FieldMeta, FieldType};
+use flowy_grid_data_model::entities::{FieldMeta, FieldType, TypeOptionDataEntry, TypeOptionDataFrom};
 use serde::{Deserialize, Serialize};
 
-use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
+use crate::services::field::{BoxTypeOptionBuilder, RichTextTypeOption, TypeOptionBuilder};
 use strum_macros::EnumIter;
 
 // Date
@@ -21,7 +20,7 @@ pub struct DateTypeOption {
     #[pb(index = 2)]
     pub time_format: TimeFormat,
 }
-impl_from_and_to_type_option!(DateTypeOption, FieldType::DateTime);
+impl_type_option!(DateTypeOption, FieldType::DateTime);
 
 impl DateTypeOption {
     #[allow(dead_code)]
@@ -66,7 +65,7 @@ impl CellDataSerde for DateTypeOption {
 #[derive(Default)]
 pub struct DateTypeOptionBuilder(DateTypeOption);
 impl_into_box_type_option_builder!(DateTypeOptionBuilder);
-impl_from_json_str_and_from_bytes!(DateTypeOptionBuilder, DateTypeOption);
+impl_builder_from_json_str_and_from_bytes!(DateTypeOptionBuilder, DateTypeOption);
 
 impl DateTypeOptionBuilder {
     pub fn date_format(mut self, date_format: DateFormat) -> Self {
@@ -84,12 +83,8 @@ impl TypeOptionBuilder for DateTypeOptionBuilder {
         self.0.field_type()
     }
 
-    fn build_type_option_str(&self) -> String {
-        self.0.clone().into()
-    }
-
-    fn build_type_option_data(&self) -> Bytes {
-        self.0.clone().try_into().unwrap()
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
     }
 }
 

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

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

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

@@ -1,8 +1,8 @@
-use crate::impl_from_and_to_type_option;
+use crate::impl_type_option;
 use crate::services::row::CellDataSerde;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::FlowyError;
-use flowy_grid_data_model::entities::{FieldMeta, FieldType};
+use flowy_grid_data_model::entities::{FieldMeta, FieldType, TypeOptionDataEntry, TypeOptionDataFrom};
 use lazy_static::lazy_static;
 use rust_decimal::prelude::Zero;
 use rust_decimal::Decimal;
@@ -22,7 +22,7 @@ lazy_static! {
 #[derive(Default)]
 pub struct NumberTypeOptionBuilder(NumberTypeOption);
 impl_into_box_type_option_builder!(NumberTypeOptionBuilder);
-impl_from_json_str_and_from_bytes!(NumberTypeOptionBuilder, NumberTypeOption);
+impl_builder_from_json_str_and_from_bytes!(NumberTypeOptionBuilder, NumberTypeOption);
 
 impl NumberTypeOptionBuilder {
     pub fn name(mut self, name: &str) -> Self {
@@ -51,12 +51,8 @@ impl TypeOptionBuilder for NumberTypeOptionBuilder {
         self.0.field_type()
     }
 
-    fn build_type_option_str(&self) -> String {
-        self.0.clone().into()
-    }
-
-    fn build_type_option_data(&self) -> Bytes {
-        self.0.clone().try_into().unwrap()
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
     }
 }
 
@@ -78,7 +74,7 @@ pub struct NumberTypeOption {
     #[pb(index = 5)]
     pub name: String,
 }
-impl_from_and_to_type_option!(NumberTypeOption, FieldType::Number);
+impl_type_option!(NumberTypeOption, FieldType::Number);
 
 impl std::default::Default for NumberTypeOption {
     fn default() -> Self {

+ 10 - 18
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs

@@ -1,11 +1,11 @@
-use crate::impl_from_and_to_type_option;
+use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
 use crate::services::row::CellDataSerde;
 use crate::services::util::*;
 use bytes::Bytes;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::entities::{FieldMeta, FieldType};
+use flowy_grid_data_model::entities::{FieldMeta, FieldType, TypeOptionDataEntry, TypeOptionDataFrom};
 use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
 use serde::{Deserialize, Serialize};
 use uuid::Uuid;
@@ -21,7 +21,7 @@ pub struct SingleSelectTypeOption {
     #[pb(index = 2)]
     pub disable_color: bool,
 }
-impl_from_and_to_type_option!(SingleSelectTypeOption, FieldType::SingleSelect);
+impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect);
 
 impl CellDataSerde for SingleSelectTypeOption {
     fn deserialize_cell_data(&self, data: String) -> String {
@@ -36,7 +36,7 @@ impl CellDataSerde for SingleSelectTypeOption {
 #[derive(Default)]
 pub struct SingleSelectTypeOptionBuilder(SingleSelectTypeOption);
 impl_into_box_type_option_builder!(SingleSelectTypeOptionBuilder);
-impl_from_json_str_and_from_bytes!(SingleSelectTypeOptionBuilder, SingleSelectTypeOption);
+impl_builder_from_json_str_and_from_bytes!(SingleSelectTypeOptionBuilder, SingleSelectTypeOption);
 
 impl SingleSelectTypeOptionBuilder {
     pub fn option(mut self, opt: SelectOption) -> Self {
@@ -50,12 +50,8 @@ impl TypeOptionBuilder for SingleSelectTypeOptionBuilder {
         self.0.field_type()
     }
 
-    fn build_type_option_str(&self) -> String {
-        self.0.clone().into()
-    }
-
-    fn build_type_option_data(&self) -> Bytes {
-        self.0.clone().try_into().unwrap()
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
     }
 }
 
@@ -68,7 +64,7 @@ pub struct MultiSelectTypeOption {
     #[pb(index = 2)]
     pub disable_color: bool,
 }
-impl_from_and_to_type_option!(MultiSelectTypeOption, FieldType::MultiSelect);
+impl_type_option!(MultiSelectTypeOption, FieldType::MultiSelect);
 
 impl CellDataSerde for MultiSelectTypeOption {
     fn deserialize_cell_data(&self, data: String) -> String {
@@ -83,7 +79,7 @@ impl CellDataSerde for MultiSelectTypeOption {
 #[derive(Default)]
 pub struct MultiSelectTypeOptionBuilder(MultiSelectTypeOption);
 impl_into_box_type_option_builder!(MultiSelectTypeOptionBuilder);
-impl_from_json_str_and_from_bytes!(MultiSelectTypeOptionBuilder, MultiSelectTypeOption);
+impl_builder_from_json_str_and_from_bytes!(MultiSelectTypeOptionBuilder, MultiSelectTypeOption);
 impl MultiSelectTypeOptionBuilder {
     pub fn option(mut self, opt: SelectOption) -> Self {
         self.0.options.push(opt);
@@ -96,12 +92,8 @@ impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
         self.0.field_type()
     }
 
-    fn build_type_option_str(&self) -> String {
-        self.0.clone().into()
-    }
-
-    fn build_type_option_data(&self) -> Bytes {
-        self.0.clone().try_into().unwrap()
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
     }
 }
 

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

@@ -1,28 +1,24 @@
-use crate::impl_from_and_to_type_option;
+use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
 use crate::services::row::CellDataSerde;
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::FlowyError;
-use flowy_grid_data_model::entities::{FieldMeta, FieldType};
+use flowy_grid_data_model::entities::{FieldMeta, FieldType, TypeOptionDataEntry, TypeOptionDataFrom};
 use serde::{Deserialize, Serialize};
 
 #[derive(Default)]
 pub struct RichTextTypeOptionBuilder(RichTextTypeOption);
 impl_into_box_type_option_builder!(RichTextTypeOptionBuilder);
-impl_from_json_str_and_from_bytes!(RichTextTypeOptionBuilder, RichTextTypeOption);
+impl_builder_from_json_str_and_from_bytes!(RichTextTypeOptionBuilder, RichTextTypeOption);
 
 impl TypeOptionBuilder for RichTextTypeOptionBuilder {
     fn field_type(&self) -> FieldType {
         self.0.field_type()
     }
 
-    fn build_type_option_str(&self) -> String {
-        self.0.clone().into()
-    }
-
-    fn build_type_option_data(&self) -> Bytes {
-        self.0.clone().try_into().unwrap()
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
     }
 }
 
@@ -31,7 +27,7 @@ pub struct RichTextTypeOption {
     #[pb(index = 1)]
     pub format: String,
 }
-impl_from_and_to_type_option!(RichTextTypeOption, FieldType::RichText);
+impl_type_option!(RichTextTypeOption, FieldType::RichText);
 
 impl CellDataSerde for RichTextTypeOption {
     fn deserialize_cell_data(&self, data: String) -> String {

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

@@ -0,0 +1,9 @@
+use flowy_grid_data_model::entities::FieldType;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+
+// #[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
+// pub struct TypeOptionData {
+//     #[pb(index = 1)]
+//     pub map: HashMap<FieldType, String>,
+// }

+ 6 - 14
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -1,7 +1,7 @@
 use crate::dart_notification::{send_dart_notification, GridNotification};
 use crate::manager::GridUser;
 use crate::services::block_meta_editor::GridBlockMetaEditorManager;
-use crate::services::field::{type_option_json_str_from_bytes, FieldBuilder};
+use crate::services::field::{type_option_builder_from_bytes, FieldBuilder};
 use crate::services::row::*;
 use bytes::Bytes;
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
@@ -74,17 +74,9 @@ impl ClientGridEditor {
                     };
                     Ok(grid.update_field(changeset, deserializer)?)
                 } else {
-                    let type_option_json = type_option_json_str_from_bytes(type_option_data, &field.field_type);
-                    let field_meta = FieldMeta {
-                        id: field.id,
-                        name: field.name,
-                        desc: field.desc,
-                        field_type: field.field_type,
-                        frozen: field.frozen,
-                        visibility: field.visibility,
-                        width: field.width,
-                        type_option_json,
-                    };
+                    // let type_option_json = type_option_json_str_from_bytes(type_option_data, &field.field_type);
+                    let builder = type_option_builder_from_bytes(type_option_data, &field.field_type);
+                    let field_meta = FieldBuilder::from_field(field, builder).build();
                     Ok(grid.create_field(field_meta, start_field_id)?)
                 }
             })
@@ -409,7 +401,7 @@ impl TypeOptionDataDeserializer for TypeOptionChangesetDeserializer {
         // The type_option_data is serialized by protobuf. But the type_option_data should be
         // serialized by utf-8. So we must transform the data here.
 
-        let type_option_json = type_option_json_str_from_bytes(type_option_data, &self.0);
-        Ok(type_option_json)
+        let builder = type_option_builder_from_bytes(type_option_data, &self.0);
+        Ok(builder.entry().json_str())
     }
 }

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

@@ -8,25 +8,25 @@ pub trait CellDataSerde {
 }
 
 #[allow(dead_code)]
-pub fn serialize_cell_data(data: &str, field: &FieldMeta) -> Result<String, FlowyError> {
-    match field.field_type {
-        FieldType::RichText => RichTextTypeOption::from(field).serialize_cell_data(data),
-        FieldType::Number => NumberTypeOption::from(field).serialize_cell_data(data),
-        FieldType::DateTime => DateTypeOption::from(field).serialize_cell_data(data),
-        FieldType::SingleSelect => SingleSelectTypeOption::from(field).serialize_cell_data(data),
-        FieldType::MultiSelect => MultiSelectTypeOption::from(field).serialize_cell_data(data),
-        FieldType::Checkbox => CheckboxTypeOption::from(field).serialize_cell_data(data),
+pub fn serialize_cell_data(data: &str, field_meta: &FieldMeta) -> Result<String, FlowyError> {
+    match field_meta.field_type {
+        FieldType::RichText => RichTextTypeOption::from(field_meta).serialize_cell_data(data),
+        FieldType::Number => NumberTypeOption::from(field_meta).serialize_cell_data(data),
+        FieldType::DateTime => DateTypeOption::from(field_meta).serialize_cell_data(data),
+        FieldType::SingleSelect => SingleSelectTypeOption::from(field_meta).serialize_cell_data(data),
+        FieldType::MultiSelect => MultiSelectTypeOption::from(field_meta).serialize_cell_data(data),
+        FieldType::Checkbox => CheckboxTypeOption::from(field_meta).serialize_cell_data(data),
     }
 }
 
-pub fn deserialize_cell_data(data: String, field: &FieldMeta) -> Result<String, FlowyError> {
-    let s = match field.field_type {
-        FieldType::RichText => RichTextTypeOption::from(field).deserialize_cell_data(data),
-        FieldType::Number => NumberTypeOption::from(field).deserialize_cell_data(data),
-        FieldType::DateTime => DateTypeOption::from(field).deserialize_cell_data(data),
-        FieldType::SingleSelect => SingleSelectTypeOption::from(field).deserialize_cell_data(data),
-        FieldType::MultiSelect => MultiSelectTypeOption::from(field).deserialize_cell_data(data),
-        FieldType::Checkbox => CheckboxTypeOption::from(field).deserialize_cell_data(data),
+pub fn deserialize_cell_data(data: String, field_meta: &FieldMeta) -> Result<String, FlowyError> {
+    let s = match field_meta.field_type {
+        FieldType::RichText => RichTextTypeOption::from(field_meta).deserialize_cell_data(data),
+        FieldType::Number => NumberTypeOption::from(field_meta).deserialize_cell_data(data),
+        FieldType::DateTime => DateTypeOption::from(field_meta).deserialize_cell_data(data),
+        FieldType::SingleSelect => SingleSelectTypeOption::from(field_meta).deserialize_cell_data(data),
+        FieldType::MultiSelect => MultiSelectTypeOption::from(field_meta).deserialize_cell_data(data),
+        FieldType::Checkbox => CheckboxTypeOption::from(field_meta).deserialize_cell_data(data),
     };
     Ok(s)
 }

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

@@ -3,7 +3,7 @@ use flowy_grid::services::field::*;
 use flowy_grid::services::grid_editor::{ClientGridEditor, GridPadBuilder};
 use flowy_grid::services::row::CreateRowMetaPayload;
 use flowy_grid_data_model::entities::{
-    BuildGridContext, CellMetaChangeset, Field, FieldChangeset, FieldMeta, FieldType, GridBlockMeta,
+    BuildGridContext, CellMetaChangeset, CreateFieldParams, Field, FieldChangeset, FieldMeta, FieldType, GridBlockMeta,
     GridBlockMetaChangeset, RowMeta, RowMetaChangeset, RowOrder,
 };
 use flowy_grid_data_model::parser::CreateFieldParams;

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

@@ -1,8 +1,11 @@
 use crate::parser::NotEmptyUuid;
+use bytes::Bytes;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error_code::ErrorCode;
 use serde::{Deserialize, Serialize};
+use std::any::Any;
 use std::collections::HashMap;
+use std::ops::Deref;
 use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter, EnumString};
 
 pub const DEFAULT_ROW_HEIGHT: i32 = 36;
@@ -76,7 +79,7 @@ pub struct GridBlockMetaSerde {
     pub row_metas: Vec<RowMeta>,
 }
 
-#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf, PartialEq, Eq)]
+#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
 pub struct FieldMeta {
     #[pb(index = 1)]
     pub id: String,
@@ -100,7 +103,7 @@ pub struct FieldMeta {
     pub width: i32,
 
     #[pb(index = 8)]
-    pub type_option_json: String,
+    pub type_option_by_field_type_id: TypeOptionDataByFieldTypeId,
 }
 
 impl FieldMeta {
@@ -113,7 +116,55 @@ impl FieldMeta {
             frozen: false,
             visibility: true,
             width: DEFAULT_FIELD_WIDTH,
-            type_option_json: Default::default(),
+            type_option_by_field_type_id: Default::default(),
+        }
+    }
+
+    pub fn get_type_option_str(&self) -> Option<String> {
+        match self.type_option_by_field_type_id.get(&self.field_type) {
+            None => None,
+            Some(s) => Some(s.to_owned()),
+        }
+    }
+}
+
+pub trait TypeOptionDataEntry {
+    fn field_type(&self) -> FieldType;
+    fn json_str(&self) -> String;
+    fn protobuf_bytes(&self) -> Bytes;
+}
+
+pub trait TypeOptionDataFrom {
+    fn from_json_str(s: &str) -> Self;
+    fn from_protobuf_bytes(bytes: Bytes) -> Self;
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
+pub struct TypeOptionDataByFieldTypeId {
+    #[pb(index = 1)]
+    pub map: HashMap<String, String>,
+}
+
+impl TypeOptionDataByFieldTypeId {
+    pub fn insert_entry<T: TypeOptionDataEntry + ?Sized>(&mut self, entry: &T) {
+        self.map.insert(entry.field_type().type_id(), entry.json_str());
+    }
+
+    pub fn insert(&mut self, field_type: &FieldType, json_str: String) {
+        self.map.insert(field_type.type_id(), json_str);
+    }
+
+    pub fn get_entry<T: TypeOptionDataFrom>(&self, field_type: &FieldType) -> Option<T> {
+        match self.map.get(&field_type.type_id()) {
+            None => None,
+            Some(s) => Some(T::from_json_str(s)),
+        }
+    }
+
+    pub fn get(&self, field_type: &FieldType) -> Option<String> {
+        match self.map.get(&field_type.type_id()) {
+            None => None,
+            Some(s) => Some(s.to_owned()),
         }
     }
 }

+ 234 - 61
shared-lib/flowy-grid-data-model/src/protobuf/model/meta.rs

@@ -727,7 +727,7 @@ pub struct FieldMeta {
     pub frozen: bool,
     pub visibility: bool,
     pub width: i32,
-    pub type_option_json: ::std::string::String,
+    pub type_option_by_field_type_id: ::protobuf::SingularPtrField<TypeOptionDataByFieldTypeId>,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -882,35 +882,47 @@ impl FieldMeta {
         self.width = v;
     }
 
-    // string type_option_json = 8;
+    // .TypeOptionDataByFieldTypeId type_option_by_field_type_id = 8;
 
 
-    pub fn get_type_option_json(&self) -> &str {
-        &self.type_option_json
+    pub fn get_type_option_by_field_type_id(&self) -> &TypeOptionDataByFieldTypeId {
+        self.type_option_by_field_type_id.as_ref().unwrap_or_else(|| <TypeOptionDataByFieldTypeId as ::protobuf::Message>::default_instance())
     }
-    pub fn clear_type_option_json(&mut self) {
-        self.type_option_json.clear();
+    pub fn clear_type_option_by_field_type_id(&mut self) {
+        self.type_option_by_field_type_id.clear();
+    }
+
+    pub fn has_type_option_by_field_type_id(&self) -> bool {
+        self.type_option_by_field_type_id.is_some()
     }
 
     // Param is passed by value, moved
-    pub fn set_type_option_json(&mut self, v: ::std::string::String) {
-        self.type_option_json = v;
+    pub fn set_type_option_by_field_type_id(&mut self, v: TypeOptionDataByFieldTypeId) {
+        self.type_option_by_field_type_id = ::protobuf::SingularPtrField::some(v);
     }
 
     // Mutable pointer to the field.
     // If field is not initialized, it is initialized with default value first.
-    pub fn mut_type_option_json(&mut self) -> &mut ::std::string::String {
-        &mut self.type_option_json
+    pub fn mut_type_option_by_field_type_id(&mut self) -> &mut TypeOptionDataByFieldTypeId {
+        if self.type_option_by_field_type_id.is_none() {
+            self.type_option_by_field_type_id.set_default();
+        }
+        self.type_option_by_field_type_id.as_mut().unwrap()
     }
 
     // Take field
-    pub fn take_type_option_json(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.type_option_json, ::std::string::String::new())
+    pub fn take_type_option_by_field_type_id(&mut self) -> TypeOptionDataByFieldTypeId {
+        self.type_option_by_field_type_id.take().unwrap_or_else(|| TypeOptionDataByFieldTypeId::new())
     }
 }
 
 impl ::protobuf::Message for FieldMeta {
     fn is_initialized(&self) -> bool {
+        for v in &self.type_option_by_field_type_id {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
         true
     }
 
@@ -952,7 +964,7 @@ impl ::protobuf::Message for FieldMeta {
                     self.width = tmp;
                 },
                 8 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.type_option_json)?;
+                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.type_option_by_field_type_id)?;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -987,8 +999,9 @@ impl ::protobuf::Message for FieldMeta {
         if self.width != 0 {
             my_size += ::protobuf::rt::value_size(7, self.width, ::protobuf::wire_format::WireTypeVarint);
         }
-        if !self.type_option_json.is_empty() {
-            my_size += ::protobuf::rt::string_size(8, &self.type_option_json);
+        if let Some(ref v) = self.type_option_by_field_type_id.as_ref() {
+            let len = v.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -1017,8 +1030,10 @@ impl ::protobuf::Message for FieldMeta {
         if self.width != 0 {
             os.write_int32(7, self.width)?;
         }
-        if !self.type_option_json.is_empty() {
-            os.write_string(8, &self.type_option_json)?;
+        if let Some(ref v) = self.type_option_by_field_type_id.as_ref() {
+            os.write_tag(8, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -1093,10 +1108,10 @@ impl ::protobuf::Message for FieldMeta {
                 |m: &FieldMeta| { &m.width },
                 |m: &mut FieldMeta| { &mut m.width },
             ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "type_option_json",
-                |m: &FieldMeta| { &m.type_option_json },
-                |m: &mut FieldMeta| { &mut m.type_option_json },
+            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<TypeOptionDataByFieldTypeId>>(
+                "type_option_by_field_type_id",
+                |m: &FieldMeta| { &m.type_option_by_field_type_id },
+                |m: &mut FieldMeta| { &mut m.type_option_by_field_type_id },
             ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<FieldMeta>(
                 "FieldMeta",
@@ -1121,7 +1136,7 @@ impl ::protobuf::Clear for FieldMeta {
         self.frozen = false;
         self.visibility = false;
         self.width = 0;
-        self.type_option_json.clear();
+        self.type_option_by_field_type_id.clear();
         self.unknown_fields.clear();
     }
 }
@@ -1138,6 +1153,160 @@ impl ::protobuf::reflect::ProtobufValue for FieldMeta {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct TypeOptionDataByFieldTypeId {
+    // message fields
+    pub map: ::std::collections::HashMap<::std::string::String, ::std::string::String>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a TypeOptionDataByFieldTypeId {
+    fn default() -> &'a TypeOptionDataByFieldTypeId {
+        <TypeOptionDataByFieldTypeId as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl TypeOptionDataByFieldTypeId {
+    pub fn new() -> TypeOptionDataByFieldTypeId {
+        ::std::default::Default::default()
+    }
+
+    // repeated .TypeOptionDataByFieldTypeId.MapEntry map = 1;
+
+
+    pub fn get_map(&self) -> &::std::collections::HashMap<::std::string::String, ::std::string::String> {
+        &self.map
+    }
+    pub fn clear_map(&mut self) {
+        self.map.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_map(&mut self, v: ::std::collections::HashMap<::std::string::String, ::std::string::String>) {
+        self.map = v;
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_map(&mut self) -> &mut ::std::collections::HashMap<::std::string::String, ::std::string::String> {
+        &mut self.map
+    }
+
+    // Take field
+    pub fn take_map(&mut self) -> ::std::collections::HashMap<::std::string::String, ::std::string::String> {
+        ::std::mem::replace(&mut self.map, ::std::collections::HashMap::new())
+    }
+}
+
+impl ::protobuf::Message for TypeOptionDataByFieldTypeId {
+    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_map_into::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeString>(wire_type, is, &mut self.map)?;
+                },
+                _ => {
+                    ::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;
+        my_size += ::protobuf::rt::compute_map_size::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeString>(1, &self.map);
+        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<()> {
+        ::protobuf::rt::write_map_with_cached_sizes::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeString>(1, &self.map, os)?;
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> TypeOptionDataByFieldTypeId {
+        TypeOptionDataByFieldTypeId::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_map_accessor::<_, ::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeString>(
+                "map",
+                |m: &TypeOptionDataByFieldTypeId| { &m.map },
+                |m: &mut TypeOptionDataByFieldTypeId| { &mut m.map },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<TypeOptionDataByFieldTypeId>(
+                "TypeOptionDataByFieldTypeId",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static TypeOptionDataByFieldTypeId {
+        static instance: ::protobuf::rt::LazyV2<TypeOptionDataByFieldTypeId> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(TypeOptionDataByFieldTypeId::new)
+    }
+}
+
+impl ::protobuf::Clear for TypeOptionDataByFieldTypeId {
+    fn clear(&mut self) {
+        self.map.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for TypeOptionDataByFieldTypeId {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for TypeOptionDataByFieldTypeId {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(PartialEq,Clone,Default)]
 pub struct FieldChangesetPayload {
     // message fields
@@ -3507,51 +3676,55 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     ockId\x12&\n\x0fstart_row_index\x18\x02\x20\x01(\x05R\rstartRowIndex\x12\
     \x1b\n\trow_count\x18\x03\x20\x01(\x05R\x08rowCount\"V\n\x12GridBlockMet\
     aSerde\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\x12%\n\trow_\
-    metas\x18\x02\x20\x03(\x0b2\x08.RowMetaR\x08rowMetas\"\xe6\x01\n\tFieldM\
+    metas\x18\x02\x20\x03(\x0b2\x08.RowMetaR\x08rowMetas\"\x99\x02\n\tFieldM\
     eta\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x12\n\x04name\x18\x02\
     \x20\x01(\tR\x04name\x12\x12\n\x04desc\x18\x03\x20\x01(\tR\x04desc\x12)\
     \n\nfield_type\x18\x04\x20\x01(\x0e2\n.FieldTypeR\tfieldType\x12\x16\n\
     \x06frozen\x18\x05\x20\x01(\x08R\x06frozen\x12\x1e\n\nvisibility\x18\x06\
     \x20\x01(\x08R\nvisibility\x12\x14\n\x05width\x18\x07\x20\x01(\x05R\x05w\
-    idth\x12(\n\x10type_option_json\x18\x08\x20\x01(\tR\x0etypeOptionJson\"\
-    \xa8\x03\n\x15FieldChangesetPayload\x12\x19\n\x08field_id\x18\x01\x20\
-    \x01(\tR\x07fieldId\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\x06gridId\
-    \x12\x14\n\x04name\x18\x03\x20\x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\
-    \x04\x20\x01(\tH\x01R\x04desc\x12+\n\nfield_type\x18\x05\x20\x01(\x0e2\n\
-    .FieldTypeH\x02R\tfieldType\x12\x18\n\x06frozen\x18\x06\x20\x01(\x08H\
-    \x03R\x06frozen\x12\x20\n\nvisibility\x18\x07\x20\x01(\x08H\x04R\nvisibi\
-    lity\x12\x16\n\x05width\x18\x08\x20\x01(\x05H\x05R\x05width\x12*\n\x10ty\
-    pe_option_data\x18\t\x20\x01(\x0cH\x06R\x0etypeOptionDataB\r\n\x0bone_of\
-    _nameB\r\n\x0bone_of_descB\x13\n\x11one_of_field_typeB\x0f\n\rone_of_fro\
-    zenB\x13\n\x11one_of_visibilityB\x0e\n\x0cone_of_widthB\x19\n\x17one_of_\
-    type_option_data\"8\n\x07AnyData\x12\x17\n\x07type_id\x18\x01\x20\x01(\t\
-    R\x06typeId\x12\x14\n\x05value\x18\x02\x20\x01(\x0cR\x05value\"\xff\x01\
-    \n\x07RowMeta\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x19\n\x08blo\
-    ck_id\x18\x02\x20\x01(\tR\x07blockId\x12D\n\x10cell_by_field_id\x18\x03\
-    \x20\x03(\x0b2\x1b.RowMeta.CellByFieldIdEntryR\rcellByFieldId\x12\x16\n\
-    \x06height\x18\x04\x20\x01(\x05R\x06height\x12\x1e\n\nvisibility\x18\x05\
-    \x20\x01(\x08R\nvisibility\x1aK\n\x12CellByFieldIdEntry\x12\x10\n\x03key\
-    \x18\x01\x20\x01(\tR\x03key\x12\x1f\n\x05value\x18\x02\x20\x01(\x0b2\t.C\
-    ellMetaR\x05value:\x028\x01\"\xa7\x02\n\x10RowMetaChangeset\x12\x15\n\
-    \x06row_id\x18\x01\x20\x01(\tR\x05rowId\x12\x18\n\x06height\x18\x02\x20\
-    \x01(\x05H\0R\x06height\x12\x20\n\nvisibility\x18\x03\x20\x01(\x08H\x01R\
-    \nvisibility\x12M\n\x10cell_by_field_id\x18\x04\x20\x03(\x0b2$.RowMetaCh\
-    angeset.CellByFieldIdEntryR\rcellByFieldId\x1aK\n\x12CellByFieldIdEntry\
+    idth\x12[\n\x1ctype_option_by_field_type_id\x18\x08\x20\x01(\x0b2\x1c.Ty\
+    peOptionDataByFieldTypeIdR\x17typeOptionByFieldTypeId\"\x8e\x01\n\x1bTyp\
+    eOptionDataByFieldTypeId\x127\n\x03map\x18\x01\x20\x03(\x0b2%.TypeOption\
+    DataByFieldTypeId.MapEntryR\x03map\x1a6\n\x08MapEntry\x12\x10\n\x03key\
+    \x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05va\
+    lue:\x028\x01\"\xa8\x03\n\x15FieldChangesetPayload\x12\x19\n\x08field_id\
+    \x18\x01\x20\x01(\tR\x07fieldId\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\
+    \x06gridId\x12\x14\n\x04name\x18\x03\x20\x01(\tH\0R\x04name\x12\x14\n\
+    \x04desc\x18\x04\x20\x01(\tH\x01R\x04desc\x12+\n\nfield_type\x18\x05\x20\
+    \x01(\x0e2\n.FieldTypeH\x02R\tfieldType\x12\x18\n\x06frozen\x18\x06\x20\
+    \x01(\x08H\x03R\x06frozen\x12\x20\n\nvisibility\x18\x07\x20\x01(\x08H\
+    \x04R\nvisibility\x12\x16\n\x05width\x18\x08\x20\x01(\x05H\x05R\x05width\
+    \x12*\n\x10type_option_data\x18\t\x20\x01(\x0cH\x06R\x0etypeOptionDataB\
+    \r\n\x0bone_of_nameB\r\n\x0bone_of_descB\x13\n\x11one_of_field_typeB\x0f\
+    \n\rone_of_frozenB\x13\n\x11one_of_visibilityB\x0e\n\x0cone_of_widthB\
+    \x19\n\x17one_of_type_option_data\"8\n\x07AnyData\x12\x17\n\x07type_id\
+    \x18\x01\x20\x01(\tR\x06typeId\x12\x14\n\x05value\x18\x02\x20\x01(\x0cR\
+    \x05value\"\xff\x01\n\x07RowMeta\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02\
+    id\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07blockId\x12D\n\x10cell_b\
+    y_field_id\x18\x03\x20\x03(\x0b2\x1b.RowMeta.CellByFieldIdEntryR\rcellBy\
+    FieldId\x12\x16\n\x06height\x18\x04\x20\x01(\x05R\x06height\x12\x1e\n\nv\
+    isibility\x18\x05\x20\x01(\x08R\nvisibility\x1aK\n\x12CellByFieldIdEntry\
     \x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x1f\n\x05value\x18\x02\
-    \x20\x01(\x0b2\t.CellMetaR\x05value:\x028\x01B\x0f\n\rone_of_heightB\x13\
-    \n\x11one_of_visibility\"9\n\x08CellMeta\x12\x19\n\x08field_id\x18\x01\
-    \x20\x01(\tR\x07fieldId\x12\x12\n\x04data\x18\x02\x20\x01(\tR\x04data\"\
-    \x83\x01\n\x11CellMetaChangeset\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
-    \x06gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\
-    \x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x12\x14\n\x04data\x18\x04\
-    \x20\x01(\tH\0R\x04dataB\r\n\x0bone_of_data\"\xad\x01\n\x10BuildGridCont\
-    ext\x12+\n\x0bfield_metas\x18\x01\x20\x03(\x0b2\n.FieldMetaR\nfieldMetas\
-    \x12/\n\x0bblock_metas\x18\x02\x20\x01(\x0b2\x0e.GridBlockMetaR\nblockMe\
-    tas\x12;\n\x0fblock_meta_data\x18\x03\x20\x01(\x0b2\x13.GridBlockMetaSer\
-    deR\rblockMetaData*d\n\tFieldType\x12\x0c\n\x08RichText\x10\0\x12\n\n\
-    \x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\x0cSingleSele\
-    ct\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08Checkbox\x10\
-    \x05b\x06proto3\
+    \x20\x01(\x0b2\t.CellMetaR\x05value:\x028\x01\"\xa7\x02\n\x10RowMetaChan\
+    geset\x12\x15\n\x06row_id\x18\x01\x20\x01(\tR\x05rowId\x12\x18\n\x06heig\
+    ht\x18\x02\x20\x01(\x05H\0R\x06height\x12\x20\n\nvisibility\x18\x03\x20\
+    \x01(\x08H\x01R\nvisibility\x12M\n\x10cell_by_field_id\x18\x04\x20\x03(\
+    \x0b2$.RowMetaChangeset.CellByFieldIdEntryR\rcellByFieldId\x1aK\n\x12Cel\
+    lByFieldIdEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x1f\n\
+    \x05value\x18\x02\x20\x01(\x0b2\t.CellMetaR\x05value:\x028\x01B\x0f\n\ro\
+    ne_of_heightB\x13\n\x11one_of_visibility\"9\n\x08CellMeta\x12\x19\n\x08f\
+    ield_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x12\n\x04data\x18\x02\x20\x01\
+    (\tR\x04data\"\x83\x01\n\x11CellMetaChangeset\x12\x17\n\x07grid_id\x18\
+    \x01\x20\x01(\tR\x06gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05ro\
+    wId\x12\x19\n\x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x12\x14\n\x04da\
+    ta\x18\x04\x20\x01(\tH\0R\x04dataB\r\n\x0bone_of_data\"\xad\x01\n\x10Bui\
+    ldGridContext\x12+\n\x0bfield_metas\x18\x01\x20\x03(\x0b2\n.FieldMetaR\n\
+    fieldMetas\x12/\n\x0bblock_metas\x18\x02\x20\x01(\x0b2\x0e.GridBlockMeta\
+    R\nblockMetas\x12;\n\x0fblock_meta_data\x18\x03\x20\x01(\x0b2\x13.GridBl\
+    ockMetaSerdeR\rblockMetaData*d\n\tFieldType\x12\x0c\n\x08RichText\x10\0\
+    \x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\x0cSi\
+    ngleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08Checkbo\
+    x\x10\x05b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -22,7 +22,10 @@ message FieldMeta {
     bool frozen = 5;
     bool visibility = 6;
     int32 width = 7;
-    string type_option_json = 8;
+    TypeOptionDataByFieldTypeId type_option_by_field_type_id = 8;
+}
+message TypeOptionDataByFieldTypeId {
+    map<string, string> map = 1;
 }
 message FieldChangesetPayload {
     string field_id = 1;

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

@@ -129,8 +129,8 @@ impl GridMetaPad {
 
             if let Some(type_option_data) = changeset.type_option_data {
                 match deserializer.deserialize(type_option_data) {
-                    Ok(type_option_json) => {
-                        field.type_option_json = type_option_json;
+                    Ok(json_str) => {
+                        field.type_option_by_field_type_id.insert(&field.field_type, json_str);
                         is_changed = Some(())
                     }
                     Err(err) => {