Parcourir la source

Refactor/update type options (#1265)

* chore: add documentation

* chore: update type option data after switching to a new field type

* chore: insert yes/no option when switch from checkbox to single/multi select

Co-authored-by: nathan <[email protected]>
Nathan.fooo il y a 2 ans
Parent
commit
1adf6530fe
24 fichiers modifiés avec 276 ajouts et 111 suppressions
  1. 13 1
      frontend/rust-lib/flowy-grid/src/entities/field_entities.rs
  2. 4 3
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  3. 1 1
      frontend/rust-lib/flowy-grid/src/event_map.rs
  4. 1 1
      frontend/rust-lib/flowy-grid/src/macros.rs
  5. 1 1
      frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs
  6. 2 2
      frontend/rust-lib/flowy-grid/src/services/field/field_operation.rs
  7. 20 2
      frontend/rust-lib/flowy-grid/src/services/field/type_option_builder.rs
  8. 8 2
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs
  9. 7 2
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs
  10. 7 2
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs
  11. 40 8
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs
  12. 7 4
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs
  13. 41 10
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs
  14. 7 2
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs
  15. 8 2
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs
  16. 66 36
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  17. 3 3
      frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs
  18. 1 1
      frontend/rust-lib/flowy-grid/src/services/group/controller.rs
  19. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/field_test/script.rs
  20. 5 7
      frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs
  21. 2 2
      frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs
  22. 1 1
      shared-lib/flowy-error-code/src/code.rs
  23. 4 4
      shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs
  24. 26 13
      shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs

+ 13 - 1
frontend/rust-lib/flowy-grid/src/entities/field_entities.rs

@@ -320,7 +320,7 @@ impl std::convert::From<String> for RepeatedFieldIdPB {
     }
 }
 
-/// [UpdateFieldTypeOptionPayloadPB] is used to update the type option data.
+/// [UpdateFieldTypeOptionPayloadPB] is used to update the type-option data.
 #[derive(ProtoBuf, Default)]
 pub struct UpdateFieldTypeOptionPayloadPB {
     #[pb(index = 1)]
@@ -465,6 +465,18 @@ pub struct FieldChangesetParams {
 
     pub type_option_data: Option<Vec<u8>>,
 }
+
+impl FieldChangesetParams {
+    pub fn has_changes(&self) -> bool {
+        self.name.is_some()
+            || self.desc.is_some()
+            || self.field_type.is_some()
+            || self.frozen.is_some()
+            || self.type_option_data.is_some()
+            || self.frozen.is_some()
+            || self.width.is_some()
+    }
+}
 /// Certain field types have user-defined options such as color, date format, number format,
 /// or a list of values for a multi-select list. These options are defined within a specialization
 /// of the FieldTypeOption class.

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

@@ -142,6 +142,7 @@ pub(crate) async fn switch_to_field_handler(
         .await
         .unwrap_or(Arc::new(editor.next_field_rev(&params.field_type).await?));
 
+    // Update the type-option data after the field type has been changed
     let type_option_data = get_type_option_data(&field_rev, &params.field_type).await?;
     let _ = editor
         .update_field_type_option(&params.grid_id, &field_rev.id, type_option_data)
@@ -220,12 +221,12 @@ pub(crate) async fn move_field_handler(
 async fn get_type_option_data(field_rev: &FieldRevision, field_type: &FieldType) -> FlowyResult<Vec<u8>> {
     let s = field_rev.get_type_option_str(field_type).unwrap_or_else(|| {
         default_type_option_builder_from_type(field_type)
-            .data_format()
+            .serializer()
             .json_str()
     });
     let field_type: FieldType = field_rev.ty.into();
     let builder = type_option_builder_from_json_str(&s, &field_type);
-    let type_option_data = builder.data_format().protobuf_bytes().to_vec();
+    let type_option_data = builder.serializer().protobuf_bytes().to_vec();
 
     Ok(type_option_data)
 }
@@ -373,7 +374,7 @@ pub(crate) async fn update_select_option_handler(
                 tokio::spawn(async move {
                     match cloned_editor.update_cell(changeset).await {
                         Ok(_) => {}
-                        Err(_) => {}
+                        Err(e) => tracing::error!("{}", e),
                     }
                 });
             }

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

@@ -92,7 +92,7 @@ pub enum GridEvent {
     #[event(input = "FieldChangesetPayloadPB")]
     UpdateField = 11,
 
-    /// [UpdateFieldTypeOption] event is used to update the field's type option data. Certain field
+    /// [UpdateFieldTypeOption] event is used to update the field's type-option data. Certain field
     /// types have user-defined options such as color, date format, number format, or a list of values
     /// for a multi-select list. These options are defined within a specialization of the
     /// FieldTypeOption class.

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

@@ -52,7 +52,7 @@ macro_rules! impl_type_option {
             }
         }
 
-        impl TypeOptionDataFormat for $target {
+        impl TypeOptionDataSerializer for $target {
             fn json_str(&self) -> String {
                 match serde_json::to_string(&self) {
                     Ok(s) => s,

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

@@ -79,7 +79,7 @@ impl FieldBuilder {
 
     pub fn build(self) -> FieldRevision {
         let mut field_rev = self.field_rev;
-        field_rev.insert_type_option(self.type_option_builder.data_format());
+        field_rev.insert_type_option(self.type_option_builder.serializer());
         field_rev
     }
 }

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

@@ -1,7 +1,7 @@
 use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
 use crate::services::grid_editor::GridRevisionEditor;
 use flowy_error::FlowyResult;
-use flowy_grid_data_model::revision::{TypeOptionDataDeserializer, TypeOptionDataFormat};
+use flowy_grid_data_model::revision::{TypeOptionDataDeserializer, TypeOptionDataSerializer};
 use std::sync::Arc;
 
 pub async fn edit_field_type_option<T>(
@@ -10,7 +10,7 @@ pub async fn edit_field_type_option<T>(
     action: impl FnOnce(&mut T),
 ) -> FlowyResult<()>
 where
-    T: TypeOptionDataDeserializer + TypeOptionDataFormat,
+    T: TypeOptionDataDeserializer + TypeOptionDataSerializer,
 {
     let get_type_option = async {
         let field_rev = editor.get_field_rev(field_id).await?;

+ 20 - 2
frontend/rust-lib/flowy-grid/src/services/field/type_option_builder.rs

@@ -1,11 +1,29 @@
 use crate::entities::FieldType;
 use crate::services::field::type_options::*;
 use bytes::Bytes;
-use flowy_grid_data_model::revision::TypeOptionDataFormat;
+use flowy_grid_data_model::revision::TypeOptionDataSerializer;
 
 pub trait TypeOptionBuilder {
     fn field_type(&self) -> FieldType;
-    fn data_format(&self) -> &dyn TypeOptionDataFormat;
+    fn serializer(&self) -> &dyn TypeOptionDataSerializer;
+
+    /// Transform the data from passed-in type-option to current type-option
+    ///
+    /// The current type-option data may be changed if it supports transform
+    /// the data from the other kind of type-option data.
+    ///
+    /// For example, when switching from `checkbox` type-option to `single-select`
+    /// type-option, adding the `Yes` option if the `single-select` type-option doesn't contain it.
+    /// But the cell content is a string, `Yes`, it's need to do the cell content transform.
+    /// The `Yes` string will be transformed to the `Yes` option id.
+    ///
+    ///
+    /// # Arguments
+    ///
+    /// * `field_type`: represents as the field type of the passed-in type-option data
+    /// * `type_option_data`: passed-in type-option data
+    //
+    fn transform(&mut self, field_type: &FieldType, type_option_data: String);
 }
 
 pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box<dyn TypeOptionBuilder> {

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

@@ -5,7 +5,9 @@ use crate::services::field::{BoxTypeOptionBuilder, CheckboxCellData, TypeOptionB
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
+use flowy_grid_data_model::revision::{
+    CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer,
+};
 use serde::{Deserialize, Serialize};
 use std::str::FromStr;
 
@@ -26,9 +28,13 @@ impl TypeOptionBuilder for CheckboxTypeOptionBuilder {
         FieldType::Checkbox
     }
 
-    fn data_format(&self) -> &dyn TypeOptionDataFormat {
+    fn serializer(&self) -> &dyn TypeOptionDataSerializer {
         &self.0
     }
+
+    fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
+        // Do nothing
+    }
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]

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

@@ -9,7 +9,9 @@ use chrono::format::strftime::StrftimeItems;
 use chrono::{NaiveDateTime, Timelike};
 use flowy_derive::ProtoBuf;
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
+use flowy_grid_data_model::revision::{
+    CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer,
+};
 use serde::{Deserialize, Serialize};
 
 // Date
@@ -200,7 +202,10 @@ impl TypeOptionBuilder for DateTypeOptionBuilder {
         FieldType::DateTime
     }
 
-    fn data_format(&self) -> &dyn TypeOptionDataFormat {
+    fn serializer(&self) -> &dyn TypeOptionDataSerializer {
         &self.0
     }
+    fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
+        // Do nothing
+    }
 }

+ 7 - 2
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs

@@ -6,7 +6,9 @@ use crate::services::field::{BoxTypeOptionBuilder, NumberCellData, TypeOptionBui
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
+use flowy_grid_data_model::revision::{
+    CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer,
+};
 
 use rust_decimal::Decimal;
 
@@ -45,9 +47,12 @@ impl TypeOptionBuilder for NumberTypeOptionBuilder {
         FieldType::Number
     }
 
-    fn data_format(&self) -> &dyn TypeOptionDataFormat {
+    fn serializer(&self) -> &dyn TypeOptionDataSerializer {
         &self.0
     }
+    fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
+        // Do nothing
+    }
 }
 
 // Number

+ 40 - 8
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs

@@ -4,12 +4,14 @@ use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOper
 use crate::services::field::type_options::util::get_cell_data;
 use crate::services::field::{
     make_selected_select_options, BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB,
-    SelectOptionIds, SelectOptionOperation, SelectOptionPB, TypeOptionBuilder,
+    SelectOptionColorPB, SelectOptionIds, SelectOptionOperation, SelectOptionPB, TypeOptionBuilder, CHECK, UNCHECK,
 };
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
+use flowy_grid_data_model::revision::{
+    CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer,
+};
 use serde::{Deserialize, Serialize};
 
 // Multiple select
@@ -48,10 +50,6 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for MultiSele
         decoded_field_type: &FieldType,
         field_rev: &FieldRevision,
     ) -> FlowyResult<CellBytes> {
-        if !decoded_field_type.is_select_option() {
-            return Ok(CellBytes::default());
-        }
-
         self.display_data(cell_data, decoded_field_type, field_rev)
     }
 
@@ -111,17 +109,51 @@ impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
         FieldType::MultiSelect
     }
 
-    fn data_format(&self) -> &dyn TypeOptionDataFormat {
+    fn serializer(&self) -> &dyn TypeOptionDataSerializer {
         &self.0
     }
+
+    fn transform(&mut self, field_type: &FieldType, _type_option_data: String) {
+        match field_type {
+            FieldType::Checkbox => {
+                //Add Yes and No options if it's not exist.
+                if self.0.options.iter().find(|option| option.name == CHECK).is_none() {
+                    let check_option = SelectOptionPB::with_color(CHECK, SelectOptionColorPB::Green);
+                    self.0.options.push(check_option);
+                }
+
+                if self.0.options.iter().find(|option| option.name == UNCHECK).is_none() {
+                    let uncheck_option = SelectOptionPB::with_color(UNCHECK, SelectOptionColorPB::Yellow);
+                    self.0.options.push(uncheck_option);
+                }
+            }
+            FieldType::SingleSelect => {}
+            _ => {}
+        }
+    }
 }
 #[cfg(test)]
 mod tests {
+    use crate::entities::FieldType;
     use crate::services::cell::CellDataOperation;
     use crate::services::field::type_options::selection_type_option::*;
-    use crate::services::field::FieldBuilder;
+    use crate::services::field::{CheckboxTypeOptionBuilder, FieldBuilder, TypeOptionBuilder};
     use crate::services::field::{MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB};
 
+    #[test]
+    fn multi_select_transform_with_checkbox_type_option_test() {
+        let checkbox_type_option_builder = CheckboxTypeOptionBuilder::default();
+        let checkbox_type_option_data = checkbox_type_option_builder.serializer().json_str();
+
+        let mut multi_select = MultiSelectTypeOptionBuilder::default();
+        multi_select.transform(&FieldType::Checkbox, checkbox_type_option_data.clone());
+        debug_assert_eq!(multi_select.0.options.len(), 2);
+
+        // Already contain the yes/no option. It doesn't need to insert new options
+        multi_select.transform(&FieldType::Checkbox, checkbox_type_option_data);
+        debug_assert_eq!(multi_select.0.options.len(), 2);
+    }
+
     #[test]
     fn multi_select_insert_multi_option_test() {
         let google = SelectOptionPB::new("Google");

+ 7 - 4
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs

@@ -5,7 +5,7 @@ use bytes::Bytes;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::{internal_error, ErrorCode, FlowyResult};
 use flowy_grid_data_model::parser::NotEmptyStr;
-use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataFormat};
+use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataSerializer};
 use nanoid::nanoid;
 use serde::{Deserialize, Serialize};
 
@@ -75,9 +75,8 @@ pub fn make_selected_select_options(
     }
 }
 
-pub trait SelectOptionOperation: TypeOptionDataFormat + Send + Sync {
+pub trait SelectOptionOperation: TypeOptionDataSerializer + Send + Sync {
     /// Insert the `SelectOptionPB` into corresponding type option.
-    /// Replace the old value if the option already exists in the option list.
     fn insert_option(&mut self, new_option: SelectOptionPB) {
         let options = self.mut_options();
         if let Some(index) = options
@@ -117,9 +116,13 @@ where
     fn display_data(
         &self,
         cell_data: CellData<SelectOptionIds>,
-        _decoded_field_type: &FieldType,
+        decoded_field_type: &FieldType,
         _field_rev: &FieldRevision,
     ) -> FlowyResult<CellBytes> {
+        if !decoded_field_type.is_select_option() {
+            return Ok(CellBytes::default());
+        }
+
         CellBytes::from(self.selected_select_option(cell_data))
     }
 

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

@@ -2,14 +2,16 @@ use crate::entities::FieldType;
 use crate::impl_type_option;
 use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
 use crate::services::field::{
-    make_selected_select_options, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds,
-    SelectOptionOperation, SelectOptionPB,
+    make_selected_select_options, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionColorPB,
+    SelectOptionIds, SelectOptionOperation, SelectOptionPB, CHECK, UNCHECK,
 };
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
+use flowy_grid_data_model::revision::{
+    CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer,
+};
 use serde::{Deserialize, Serialize};
 
 // Single select
@@ -50,10 +52,6 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for SingleSel
         decoded_field_type: &FieldType,
         field_rev: &FieldRevision,
     ) -> FlowyResult<CellBytes> {
-        if !decoded_field_type.is_select_option() {
-            return Ok(CellBytes::default());
-        }
-
         self.display_data(cell_data, decoded_field_type, field_rev)
     }
 
@@ -103,17 +101,50 @@ impl TypeOptionBuilder for SingleSelectTypeOptionBuilder {
         FieldType::SingleSelect
     }
 
-    fn data_format(&self) -> &dyn TypeOptionDataFormat {
+    fn serializer(&self) -> &dyn TypeOptionDataSerializer {
         &self.0
     }
+
+    fn transform(&mut self, field_type: &FieldType, _type_option_data: String) {
+        match field_type {
+            FieldType::Checkbox => {
+                //add Yes and No options if it's not exist.
+                if self.0.options.iter().find(|option| option.name == CHECK).is_none() {
+                    let check_option = SelectOptionPB::with_color(CHECK, SelectOptionColorPB::Green);
+                    self.0.options.push(check_option);
+                }
+
+                if self.0.options.iter().find(|option| option.name == UNCHECK).is_none() {
+                    let uncheck_option = SelectOptionPB::with_color(UNCHECK, SelectOptionColorPB::Yellow);
+                    self.0.options.push(uncheck_option);
+                }
+            }
+            FieldType::MultiSelect => {}
+            _ => {}
+        }
+    }
 }
 
 #[cfg(test)]
 mod tests {
+    use crate::entities::FieldType;
     use crate::services::cell::CellDataOperation;
-
     use crate::services::field::type_options::*;
-    use crate::services::field::FieldBuilder;
+    use crate::services::field::{FieldBuilder, TypeOptionBuilder};
+
+    #[test]
+    fn single_select_transform_with_checkbox_type_option_test() {
+        let checkbox_type_option_builder = CheckboxTypeOptionBuilder::default();
+        let checkbox_type_option_data = checkbox_type_option_builder.serializer().json_str();
+
+        let mut single_select = SingleSelectTypeOptionBuilder::default();
+        single_select.transform(&FieldType::Checkbox, checkbox_type_option_data.clone());
+        debug_assert_eq!(single_select.0.options.len(), 2);
+
+        // Already contain the yes/no option. It doesn't need to insert new options
+        single_select.transform(&FieldType::Checkbox, checkbox_type_option_data);
+        debug_assert_eq!(single_select.0.options.len(), 2);
+    }
 
     #[test]
     fn single_select_insert_multi_option_test() {

+ 7 - 2
frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs

@@ -8,7 +8,9 @@ use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
+use flowy_grid_data_model::revision::{
+    CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer,
+};
 use serde::{Deserialize, Serialize};
 
 #[derive(Default)]
@@ -21,9 +23,12 @@ impl TypeOptionBuilder for RichTextTypeOptionBuilder {
         FieldType::RichText
     }
 
-    fn data_format(&self) -> &dyn TypeOptionDataFormat {
+    fn serializer(&self) -> &dyn TypeOptionDataSerializer {
         &self.0
     }
+    fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
+        // Do nothing
+    }
 }
 
 #[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]

+ 8 - 2
frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs

@@ -6,7 +6,9 @@ use bytes::Bytes;
 use fancy_regex::Regex;
 use flowy_derive::ProtoBuf;
 use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
+use flowy_grid_data_model::revision::{
+    CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer,
+};
 use lazy_static::lazy_static;
 use serde::{Deserialize, Serialize};
 
@@ -20,9 +22,13 @@ impl TypeOptionBuilder for URLTypeOptionBuilder {
         FieldType::URL
     }
 
-    fn data_format(&self) -> &dyn TypeOptionDataFormat {
+    fn serializer(&self) -> &dyn TypeOptionDataSerializer {
         &self.0
     }
+
+    fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
+        // Do nothing
+    }
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]

+ 66 - 36
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -5,7 +5,10 @@ use crate::manager::{GridTaskSchedulerRwLock, GridUser};
 use crate::services::block_manager::GridBlockManager;
 
 use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data, CellBytes};
-use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder};
+use crate::services::field::{
+    default_type_option_builder_from_type, type_option_builder_from_bytes, type_option_builder_from_json_str,
+    FieldBuilder,
+};
 use crate::services::filter::GridFilterService;
 use crate::services::grid_view_manager::GridViewManager;
 use crate::services::persistence::block_index::BlockIndexCache;
@@ -87,12 +90,27 @@ impl GridRevisionEditor {
         Ok(editor)
     }
 
+    /// Save the type-option data to disk and send a `GridNotification::DidUpdateField` notification
+    /// to dart side.
+    ///
+    /// It will do nothing if the passed-in type_option_data is empty
+    /// # Arguments
+    ///
+    /// * `grid_id`: the id of the grid
+    /// * `field_id`: the id of the field
+    /// * `type_option_data`: the updated type-option data.
+    ///
     pub async fn update_field_type_option(
         &self,
         grid_id: &str,
         field_id: &str,
         type_option_data: Vec<u8>,
     ) -> FlowyResult<()> {
+        debug_assert!(!type_option_data.is_empty());
+        if type_option_data.is_empty() {
+            return Ok(());
+        }
+
         let result = self.get_field_rev(field_id).await;
         if result.is_none() {
             tracing::warn!("Can't find the field with id: {}", field_id);
@@ -124,7 +142,7 @@ impl GridRevisionEditor {
         let mut field_rev = self.next_field_rev(field_type).await?;
         if let Some(type_option_data) = type_option_data {
             let type_option_builder = type_option_builder_from_bytes(type_option_data, field_type);
-            field_rev.insert_type_option(type_option_builder.data_format());
+            field_rev.insert_type_option(type_option_builder.serializer());
         }
         let _ = self
             .modify(|grid| Ok(grid.create_field_rev(field_rev.clone(), None)?))
@@ -165,7 +183,7 @@ impl GridRevisionEditor {
         let _ = self
             .modify(|grid| {
                 let changeset = grid.modify_field(field_id, |field_rev| {
-                    Ok(f(field_rev).map_err(|e| CollaborateError::internal().context(e))?)
+                    f(field_rev).map_err(|e| CollaborateError::internal().context(e))
                 })?;
                 is_changed = changeset.is_some();
                 Ok(changeset)
@@ -197,26 +215,42 @@ impl GridRevisionEditor {
 
     /// Switch the field with id to a new field type.  
     ///
-    /// If the field type is not exist before, the default type option data will be created.
-    /// Each field type has its corresponding data, aka, the type option data. Check out [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid#fieldtype)
+    /// If the field type is not exist before, the default type-option data will be created.
+    /// Each field type has its corresponding data, aka, the type-option data. Check out [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid#fieldtype)
     /// for more information
     ///
     /// # Arguments
     ///
     /// * `field_id`: the id of the field
-    /// * `field_type`: the new field type of the field
+    /// * `new_field_type`: the new field type of the field
     ///
-    pub async fn switch_to_field_type(&self, field_id: &str, field_type: &FieldType) -> FlowyResult<()> {
-        let type_option_builder = |field_type: &FieldTypeRevision| -> String {
-            let field_type: FieldType = field_type.into();
-
-            return default_type_option_builder_from_type(&field_type)
-                .data_format()
+    pub async fn switch_to_field_type(&self, field_id: &str, new_field_type: &FieldType) -> FlowyResult<()> {
+        //
+        let make_default_type_option = || -> String {
+            return default_type_option_builder_from_type(new_field_type)
+                .serializer()
                 .json_str();
         };
 
+        let type_option_transform =
+            |prev_field_type: FieldTypeRevision, prev_type_option: Option<String>, current_type_option: String| {
+                let prev_field_type: FieldType = prev_field_type.into();
+                let mut type_option_builder = type_option_builder_from_json_str(&current_type_option, new_field_type);
+                if let Some(prev_type_option) = prev_type_option {
+                    type_option_builder.transform(&prev_field_type, prev_type_option)
+                }
+                type_option_builder.serializer().json_str()
+            };
+
         let _ = self
-            .modify(|grid| Ok(grid.switch_to_field(field_id, field_type.clone(), type_option_builder)?))
+            .modify(|grid| {
+                Ok(grid.switch_to_field(
+                    field_id,
+                    new_field_type.clone(),
+                    make_default_type_option,
+                    type_option_transform,
+                )?)
+            })
             .await?;
 
         let _ = self.notify_did_update_grid_field(field_id).await?;
@@ -258,73 +292,69 @@ impl GridRevisionEditor {
         Ok(field_revs)
     }
 
+    /// Apply the changeset to field. Including the `name`,`field_type`,`width`,`visibility`,and `type_option_data`.
+    /// Do nothing if the passed-in params doesn't carry any changes.
+    ///
+    /// # Arguments
+    ///
+    /// * `params`: contains the changesets that is going to applied to the field.
+    /// Ignore the change if one of the properties is None.
+    ///
+    /// * `field_type`: is used by `TypeOptionJsonDeserializer` to deserialize the type_option_data
+    ///
     #[tracing::instrument(level = "debug", skip_all, err)]
     async fn update_field_rev(&self, params: FieldChangesetParams, field_type: FieldType) -> FlowyResult<()> {
         let mut is_type_option_changed = false;
+        if !params.has_changes() {
+            return Ok(());
+        }
+
         let _ = self
             .modify(|grid| {
-                let deserializer = TypeOptionJsonDeserializer(field_type);
                 let changeset = grid.modify_field(&params.field_id, |field| {
-                    let mut is_changed = None;
                     if let Some(name) = params.name {
                         field.name = name;
-                        is_changed = Some(())
                     }
-
                     if let Some(desc) = params.desc {
                         field.desc = desc;
-                        is_changed = Some(())
                     }
-
                     if let Some(field_type) = params.field_type {
                         field.ty = field_type;
-                        is_changed = Some(())
                     }
-
                     if let Some(frozen) = params.frozen {
                         field.frozen = frozen;
-                        is_changed = Some(())
                     }
-
                     if let Some(visibility) = params.visibility {
                         field.visibility = visibility;
-                        is_changed = Some(())
                     }
-
                     if let Some(width) = params.width {
                         field.width = width;
-                        is_changed = Some(())
                     }
-
                     if let Some(type_option_data) = params.type_option_data {
+                        let deserializer = TypeOptionJsonDeserializer(field_type);
                         match deserializer.deserialize(type_option_data) {
                             Ok(json_str) => {
                                 let field_type = field.ty;
                                 field.insert_type_option_str(&field_type, json_str);
                                 is_type_option_changed = true;
-                                is_changed = Some(())
                             }
                             Err(err) => {
                                 tracing::error!("Deserialize data to type option json failed: {}", err);
                             }
                         }
                     }
-
-                    Ok(is_changed)
+                    Ok(Some(()))
                 })?;
                 Ok(changeset)
             })
             .await?;
-
+        let _ = self.view_manager.did_update_view_field(&params.field_id).await?;
         if is_type_option_changed {
             let _ = self
                 .view_manager
                 .did_update_view_field_type_option(&params.field_id)
                 .await?;
-        } else {
-            let _ = self.view_manager.did_update_view_field(&params.field_id).await?;
         }
-
         Ok(())
     }
 
@@ -835,8 +865,8 @@ impl JsonDeserializer for TypeOptionJsonDeserializer {
     fn deserialize(&self, type_option_data: Vec<u8>) -> CollaborateResult<String> {
         // The type_option_data sent from Dart is serialized by protobuf.
         let builder = type_option_builder_from_bytes(type_option_data, &self.0);
-        let json = builder.data_format().json_str();
-        tracing::trace!("Deserialize type option data to: {}", json);
+        let json = builder.serializer().json_str();
+        tracing::trace!("Deserialize type-option data to: {}", json);
         Ok(json)
     }
 }

+ 3 - 3
frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs

@@ -187,9 +187,9 @@ impl GridViewManager {
         Ok(())
     }
 
-    /// Notifies the view's field type option data is changed
-    /// For the moment, only the groups will be generated after the type option data changed. A
-    /// [FieldRevision] has a property named type_options contains a list of type option data.
+    /// Notifies the view's field type-option data is changed
+    /// For the moment, only the groups will be generated after the type-option data changed. A
+    /// [FieldRevision] has a property named type_options contains a list of type-option data.
     /// # Arguments
     ///
     /// * `field_id`: the id of the field in current view

+ 1 - 1
frontend/rust-lib/flowy-grid/src/services/group/controller.rs

@@ -87,7 +87,7 @@ pub trait GroupControllerActions: Send + Sync {
 }
 
 /// C: represents the group configuration that impl [GroupConfigurationSerde]
-/// T: the type option data deserializer that impl [TypeOptionDataDeserializer]
+/// T: the type-option data deserializer that impl [TypeOptionDataDeserializer]
 /// G: the group generator, [GroupGenerator]
 /// P: the parser that impl [CellBytesParser] for the CellBytes
 pub struct GenericGroupController<C, T, G, P> {

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

@@ -76,7 +76,7 @@ impl GridFieldTest {
             } => {
                 let field_revs = self.editor.get_field_revs(None).await.unwrap();
                 let field_rev = field_revs[field_index].as_ref();
-                let type_option_data = field_rev.get_type_option_str(field_rev.ty.clone()).unwrap();
+                let type_option_data = field_rev.get_type_option_str(field_rev.ty).unwrap();
                 assert_eq!(type_option_data, expected_type_option_data);
             }
         }

+ 5 - 7
frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs

@@ -4,7 +4,7 @@ use crate::grid::field_test::util::*;
 use flowy_grid::entities::FieldChangesetParams;
 use flowy_grid::services::field::selection_type_option::SelectOptionPB;
 use flowy_grid::services::field::SingleSelectTypeOptionPB;
-use flowy_grid_data_model::revision::TypeOptionDataFormat;
+use flowy_grid_data_model::revision::TypeOptionDataSerializer;
 
 #[tokio::test]
 async fn grid_create_field() {
@@ -15,7 +15,7 @@ async fn grid_create_field() {
         CreateField { params },
         AssertFieldTypeOptionEqual {
             field_index: test.field_count(),
-            expected_type_option_data: field_rev.get_type_option_str(field_rev.ty.clone()).unwrap(),
+            expected_type_option_data: field_rev.get_type_option_str(field_rev.ty).unwrap(),
         },
     ];
     test.run_scripts(scripts).await;
@@ -25,7 +25,7 @@ async fn grid_create_field() {
         CreateField { params },
         AssertFieldTypeOptionEqual {
             field_index: test.field_count(),
-            expected_type_option_data: field_rev.get_type_option_str(field_rev.ty.clone()).unwrap(),
+            expected_type_option_data: field_rev.get_type_option_str(field_rev.ty).unwrap(),
         },
     ];
     test.run_scripts(scripts).await;
@@ -63,7 +63,7 @@ async fn grid_update_field_with_empty_change() {
         UpdateField { changeset },
         AssertFieldTypeOptionEqual {
             field_index: create_field_index,
-            expected_type_option_data: field_rev.get_type_option_str(field_rev.ty.clone()).unwrap(),
+            expected_type_option_data: field_rev.get_type_option_str(field_rev.ty).unwrap(),
         },
     ];
     test.run_scripts(scripts).await;
@@ -100,9 +100,7 @@ async fn grid_update_field() {
         UpdateField { changeset },
         AssertFieldTypeOptionEqual {
             field_index: create_field_index,
-            expected_type_option_data: expected_field_rev
-                .get_type_option_str(expected_field_rev.ty.clone())
-                .unwrap(),
+            expected_type_option_data: expected_field_rev.get_type_option_str(expected_field_rev.ty).unwrap(),
         },
     ];
     test.run_scripts(scripts).await;

+ 2 - 2
frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs

@@ -18,7 +18,7 @@ pub fn create_text_field(grid_id: &str) -> (CreateFieldParams, FieldRevision) {
         .to_vec();
 
     let type_option_builder = type_option_builder_from_bytes(type_option_data.clone(), &field_rev.ty.into());
-    field_rev.insert_type_option(type_option_builder.data_format());
+    field_rev.insert_type_option(type_option_builder.serializer());
 
     let params = CreateFieldParams {
         grid_id: grid_id.to_owned(),
@@ -42,7 +42,7 @@ pub fn create_single_select_field(grid_id: &str) -> (CreateFieldParams, FieldRev
         .to_vec();
 
     let type_option_builder = type_option_builder_from_bytes(type_option_data.clone(), &field_rev.ty.into());
-    field_rev.insert_type_option(type_option_builder.data_format());
+    field_rev.insert_type_option(type_option_builder.serializer());
 
     let params = CreateFieldParams {
         grid_id: grid_id.to_owned(),

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

@@ -111,7 +111,7 @@ pub enum ErrorCode {
     #[display(fmt = "The operation in this field is invalid")]
     FieldInvalidOperation = 444,
 
-    #[display(fmt = "Field's type option data should not be empty")]
+    #[display(fmt = "Field's type-option data should not be empty")]
     TypeOptionDataIsEmpty = 450,
 
     #[display(fmt = "Group id is empty")]

+ 4 - 4
shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs

@@ -108,7 +108,7 @@ pub struct FieldRevision {
 
     /// type_options contains key/value pairs
     /// key: id of the FieldType
-    /// value: type option data that can be parsed into specified TypeOptionStruct.
+    /// value: type-option data that can be parsed into specified TypeOptionStruct.
     ///
     /// For example, CheckboxTypeOption, MultiSelectTypeOption etc.
     #[serde(with = "indexmap::serde_seq")]
@@ -149,7 +149,7 @@ impl FieldRevision {
 
     pub fn insert_type_option<T>(&mut self, type_option: &T)
     where
-        T: TypeOptionDataFormat + ?Sized,
+        T: TypeOptionDataSerializer + ?Sized,
     {
         let id = self.ty.to_string();
         self.type_options.insert(id, type_option.json_str());
@@ -172,9 +172,9 @@ impl FieldRevision {
     }
 }
 
-/// The macro [impl_type_option] will implement the [TypeOptionDataEntry] for the type that
+/// The macro [impl_type_option] will implement the [TypeOptionDataSerializer] for the type that
 /// supports the serde trait and the TryInto<Bytes> trait.
-pub trait TypeOptionDataFormat {
+pub trait TypeOptionDataSerializer {
     fn json_str(&self) -> String;
     fn protobuf_bytes(&self) -> Bytes;
 }

+ 26 - 13
shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs

@@ -10,7 +10,6 @@ use lib_infra::util::move_vec_element;
 use lib_ot::core::{DeltaBuilder, DeltaOperations, EmptyAttributes, OperationTransform};
 use std::collections::HashMap;
 use std::sync::Arc;
-
 pub type GridOperations = DeltaOperations<EmptyAttributes>;
 pub type GridOperationsBuilder = DeltaBuilder;
 
@@ -139,22 +138,24 @@ impl GridRevisionPad {
     ///
     /// * `field_id`: the id of the field
     /// * `field_type`: the new field type of the field
-    /// * `type_option_builder`: builder for creating the field type's type option data
+    /// * `make_default_type_option`: create the field type's type-option data
+    /// * `type_option_transform`: create the field type's type-option data
     ///
     ///
-    pub fn switch_to_field<B, T>(
+    pub fn switch_to_field<DT, TT, T>(
         &mut self,
         field_id: &str,
-        field_type: T,
-        type_option_builder: B,
+        new_field_type: T,
+        make_default_type_option: DT,
+        type_option_transform: TT,
     ) -> CollaborateResult<Option<GridRevisionChangeset>>
     where
-        B: FnOnce(&FieldTypeRevision) -> String,
+        DT: FnOnce() -> String,
+        TT: FnOnce(FieldTypeRevision, Option<String>, String) -> String,
         T: Into<FieldTypeRevision>,
     {
-        let field_type = field_type.into();
+        let new_field_type = new_field_type.into();
         self.modify_grid(|grid_meta| {
-            //
             match grid_meta.fields.iter_mut().find(|field_rev| field_rev.id == field_id) {
                 None => {
                     tracing::warn!("Can not find the field with id: {}", field_id);
@@ -162,13 +163,25 @@ impl GridRevisionPad {
                 }
                 Some(field_rev) => {
                     let mut_field_rev = Arc::make_mut(field_rev);
-                    // If the type option data isn't exist before, creating the default type option data.
-                    if mut_field_rev.get_type_option_str(field_type).is_none() {
-                        let type_option_json = type_option_builder(&field_type);
-                        mut_field_rev.insert_type_option_str(&field_type, type_option_json);
+                    let old_field_type_rev = mut_field_rev.ty.clone();
+                    let old_field_type_option = mut_field_rev.get_type_option_str(mut_field_rev.ty);
+                    match mut_field_rev.get_type_option_str(new_field_type) {
+                        Some(new_field_type_option) => {
+                            //
+                            let transformed_type_option =
+                                type_option_transform(old_field_type_rev, old_field_type_option, new_field_type_option);
+                            mut_field_rev.insert_type_option_str(&new_field_type, transformed_type_option);
+                        }
+                        None => {
+                            // If the type-option data isn't exist before, creating the default type-option data.
+                            let new_field_type_option = make_default_type_option();
+                            let transformed_type_option =
+                                type_option_transform(old_field_type_rev, old_field_type_option, new_field_type_option);
+                            mut_field_rev.insert_type_option_str(&new_field_type, transformed_type_option);
+                        }
                     }
 
-                    mut_field_rev.ty = field_type;
+                    mut_field_rev.ty = new_field_type;
                     Ok(Some(()))
                 }
             }