Forráskód Böngészése

refactor: type options directory

appflowy 2 éve
szülő
commit
f10e324b73
40 módosított fájl, 1504 hozzáadás és 1452 törlés
  1. 0 1
      frontend/rust-lib/flowy-grid/Flowy.toml
  2. 1 1
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs
  3. 4 2
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  4. 2 2
      frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs
  5. 0 1
      frontend/rust-lib/flowy-grid/src/services/field/mod.rs
  6. 0 145
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs
  7. 75 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_option.rs
  8. 61 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_option_entities.rs
  9. 6 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/mod.rs
  10. 30 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/tests.rs
  11. 0 672
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs
  12. 199 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_option.rs
  13. 206 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_option_entities.rs
  14. 6 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/mod.rs
  15. 272 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/tests.rs
  16. 7 10
      frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs
  17. 5 2
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs
  18. 149 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_option.rs
  19. 93 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_option_entities.rs
  20. 0 376
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs
  21. 139 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/tests.rs
  22. 7 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs
  23. 6 11
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs
  24. 4 4
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs
  25. 3 4
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs
  26. 2 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/mod.rs
  27. 5 6
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_option.rs
  28. 0 206
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs
  29. 6 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs
  30. 67 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/tests.rs
  31. 101 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_option.rs
  32. 40 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_option_entities.rs
  33. 0 1
      frontend/rust-lib/flowy-grid/src/services/field/type_options/util/mod.rs
  34. 2 2
      frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs
  35. 1 1
      frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs
  36. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs
  37. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/cell_test/test.rs
  38. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs
  39. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs
  40. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs

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

@@ -2,7 +2,6 @@
 proto_input = [
     "src/event_map.rs",
     "src/services/field/type_options",
-    "src/services/field/select_option.rs",
     "src/entities",
     "src/dart_notification.rs"
 ]

+ 1 - 1
frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs

@@ -1,4 +1,4 @@
-use crate::services::field::select_option::SelectOptionIds;
+use crate::services::field::SelectOptionIds;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::revision::GridFilterRevision;

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

@@ -1,9 +1,11 @@
 use crate::entities::*;
 use crate::manager::GridManager;
 use crate::services::cell::AnyCellData;
-use crate::services::field::select_option::*;
 use crate::services::field::{
-    default_type_option_builder_from_type, type_option_builder_from_json_str, DateChangesetParams, DateChangesetPayload,
+    default_type_option_builder_from_type, select_option_operation, type_option_builder_from_json_str,
+    DateChangesetParams, DateChangesetPayload, SelectOption, SelectOptionCellChangeset,
+    SelectOptionCellChangesetParams, SelectOptionCellChangesetPayload, SelectOptionCellData, SelectOptionChangeset,
+    SelectOptionChangesetPayload,
 };
 use crate::services::row::make_row_from_row_rev;
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};

+ 2 - 2
frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs

@@ -12,13 +12,13 @@ pub trait CellFilterOperation<T> {
 }
 
 /// Return object that describes the cell.
-pub trait CellDisplayable<CD, DC> {
+pub trait CellDisplayable<CD> {
     fn display_data(
         &self,
         cell_data: CellData<CD>,
         decoded_field_type: &FieldType,
         field_rev: &FieldRevision,
-    ) -> FlowyResult<DC>;
+    ) -> FlowyResult<CellBytes>;
 }
 
 // CD: Short for CellData. This type is the type return by apply_changeset function.

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

@@ -1,5 +1,4 @@
 mod field_builder;
-pub mod select_option;
 pub(crate) mod type_options;
 
 pub use field_builder::*;

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

@@ -1,145 +0,0 @@
-use crate::entities::FieldType;
-use crate::impl_type_option;
-use crate::services::cell::{AnyCellData, CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
-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, TypeOptionDataEntry};
-use serde::{Deserialize, Serialize};
-
-#[derive(Default)]
-pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption);
-impl_into_box_type_option_builder!(CheckboxTypeOptionBuilder);
-impl_builder_from_json_str_and_from_bytes!(CheckboxTypeOptionBuilder, CheckboxTypeOption);
-
-impl CheckboxTypeOptionBuilder {
-    pub fn set_selected(mut self, is_selected: bool) -> Self {
-        self.0.is_selected = is_selected;
-        self
-    }
-}
-
-impl TypeOptionBuilder for CheckboxTypeOptionBuilder {
-    fn field_type(&self) -> FieldType {
-        FieldType::Checkbox
-    }
-
-    fn entry(&self) -> &dyn TypeOptionDataEntry {
-        &self.0
-    }
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
-pub struct CheckboxTypeOption {
-    #[pb(index = 1)]
-    pub is_selected: bool,
-}
-impl_type_option!(CheckboxTypeOption, FieldType::Checkbox);
-
-const YES: &str = "Yes";
-const NO: &str = "No";
-
-impl CellDisplayable<String, bool> for CheckboxTypeOption {
-    fn display_data(
-        &self,
-        cell_data: CellData<String>,
-        _decoded_field_type: &FieldType,
-        _field_rev: &FieldRevision,
-    ) -> FlowyResult<bool> {
-        let s: String = cell_data.try_into_inner()?;
-        Ok(s == YES)
-    }
-}
-
-impl CellDataOperation<String, String> for CheckboxTypeOption {
-    fn decode_cell_data(
-        &self,
-        cell_data: CellData<String>,
-        decoded_field_type: &FieldType,
-        _field_rev: &FieldRevision,
-    ) -> FlowyResult<CellBytes> {
-        if !decoded_field_type.is_checkbox() {
-            return Ok(CellBytes::default());
-        }
-
-        let s: String = cell_data.try_into_inner()?;
-        if s == YES || s == NO {
-            return Ok(CellBytes::new(s));
-        }
-
-        Ok(CellBytes::default())
-    }
-
-    fn apply_changeset(
-        &self,
-        changeset: CellDataChangeset<String>,
-        _cell_rev: Option<CellRevision>,
-    ) -> Result<String, FlowyError> {
-        let changeset = changeset.try_into_inner()?;
-        let s = match string_to_bool(&changeset) {
-            true => YES,
-            false => NO,
-        };
-        Ok(s.to_string())
-    }
-}
-
-fn string_to_bool(bool_str: &str) -> bool {
-    let lower_case_str: &str = &bool_str.to_lowercase();
-    match lower_case_str {
-        "1" => true,
-        "true" => true,
-        "yes" => true,
-        "0" => false,
-        "false" => false,
-        "no" => false,
-        _ => false,
-    }
-}
-
-pub struct CheckboxCellData(pub String);
-
-impl CheckboxCellData {
-    pub fn is_check(&self) -> bool {
-        string_to_bool(&self.0)
-    }
-}
-impl std::convert::TryFrom<AnyCellData> for CheckboxCellData {
-    type Error = FlowyError;
-
-    fn try_from(_value: AnyCellData) -> Result<Self, Self::Error> {
-        todo!()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data};
-    use crate::services::field::type_options::checkbox_type_option::{NO, YES};
-    use crate::services::field::FieldBuilder;
-
-    use crate::entities::FieldType;
-
-    #[test]
-    fn checkout_box_description_test() {
-        let field_rev = FieldBuilder::from_field_type(&FieldType::Checkbox).build();
-        let data = apply_cell_data_changeset("true", None, &field_rev).unwrap();
-        assert_eq!(decode_any_cell_data(data, &field_rev).to_string(), YES);
-
-        let data = apply_cell_data_changeset("1", None, &field_rev).unwrap();
-        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), YES);
-
-        let data = apply_cell_data_changeset("yes", None, &field_rev).unwrap();
-        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), YES);
-
-        let data = apply_cell_data_changeset("false", None, &field_rev).unwrap();
-        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), NO);
-
-        let data = apply_cell_data_changeset("no", None, &field_rev).unwrap();
-        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), NO);
-
-        let data = apply_cell_data_changeset("12", None, &field_rev).unwrap();
-        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), NO);
-    }
-}

+ 75 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_option.rs

@@ -0,0 +1,75 @@
+use crate::entities::FieldType;
+use crate::impl_type_option;
+use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
+use crate::services::field::{BoxTypeOptionBuilder, CheckboxCellData, TypeOptionBuilder};
+use bytes::Bytes;
+use flowy_derive::ProtoBuf;
+use flowy_error::{FlowyError, FlowyResult};
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
+use serde::{Deserialize, Serialize};
+
+#[derive(Default)]
+pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption);
+impl_into_box_type_option_builder!(CheckboxTypeOptionBuilder);
+impl_builder_from_json_str_and_from_bytes!(CheckboxTypeOptionBuilder, CheckboxTypeOption);
+
+impl CheckboxTypeOptionBuilder {
+    pub fn set_selected(mut self, is_selected: bool) -> Self {
+        self.0.is_selected = is_selected;
+        self
+    }
+}
+
+impl TypeOptionBuilder for CheckboxTypeOptionBuilder {
+    fn field_type(&self) -> FieldType {
+        FieldType::Checkbox
+    }
+
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
+pub struct CheckboxTypeOption {
+    #[pb(index = 1)]
+    pub is_selected: bool,
+}
+impl_type_option!(CheckboxTypeOption, FieldType::Checkbox);
+
+impl CellDisplayable<CheckboxCellData> for CheckboxTypeOption {
+    fn display_data(
+        &self,
+        cell_data: CellData<CheckboxCellData>,
+        _decoded_field_type: &FieldType,
+        _field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        let cell_data = cell_data.try_into_inner()?;
+        Ok(CellBytes::new(cell_data))
+    }
+}
+
+impl CellDataOperation<CheckboxCellData, String> for CheckboxTypeOption {
+    fn decode_cell_data(
+        &self,
+        cell_data: CellData<CheckboxCellData>,
+        decoded_field_type: &FieldType,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        if !decoded_field_type.is_checkbox() {
+            return Ok(CellBytes::default());
+        }
+
+        self.display_data(cell_data, decoded_field_type, field_rev)
+    }
+
+    fn apply_changeset(
+        &self,
+        changeset: CellDataChangeset<String>,
+        _cell_rev: Option<CellRevision>,
+    ) -> Result<String, FlowyError> {
+        let changeset = changeset.try_into_inner()?;
+        let cell_data = CheckboxCellData::from_str(&changeset);
+        Ok(cell_data.to_string())
+    }
+}

+ 61 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_option_entities.rs

@@ -0,0 +1,61 @@
+use crate::services::cell::{AnyCellData, FromCellString};
+use flowy_error::{FlowyError, FlowyResult};
+
+pub const YES: &str = "Yes";
+pub const NO: &str = "No";
+
+pub struct CheckboxCellData(pub String);
+
+impl CheckboxCellData {
+    pub fn from_str(s: &str) -> Self {
+        let lower_case_str: &str = &s.to_lowercase();
+        let val = match lower_case_str {
+            "1" => Some(true),
+            "true" => Some(true),
+            "yes" => Some(true),
+            "0" => Some(false),
+            "false" => Some(false),
+            "no" => Some(false),
+            _ => None,
+        };
+
+        match val {
+            Some(true) => Self(YES.to_string()),
+            Some(false) => Self(NO.to_string()),
+            None => Self("".to_string()),
+        }
+    }
+
+    pub fn is_check(&self) -> bool {
+        &self.0 == YES
+    }
+}
+
+impl AsRef<[u8]> for CheckboxCellData {
+    fn as_ref(&self) -> &[u8] {
+        self.0.as_ref()
+    }
+}
+
+impl std::convert::TryFrom<AnyCellData> for CheckboxCellData {
+    type Error = FlowyError;
+
+    fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
+        Ok(Self::from_str(&value.data))
+    }
+}
+
+impl FromCellString for CheckboxCellData {
+    fn from_cell_str(s: &str) -> FlowyResult<Self>
+    where
+        Self: Sized,
+    {
+        Ok(Self::from_str(s))
+    }
+}
+
+impl ToString for CheckboxCellData {
+    fn to_string(&self) -> String {
+        self.0.clone()
+    }
+}

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

@@ -0,0 +1,6 @@
+mod checkbox_option;
+mod checkbox_option_entities;
+mod tests;
+
+pub use checkbox_option::*;
+pub use checkbox_option_entities::*;

+ 30 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/tests.rs

@@ -0,0 +1,30 @@
+#[cfg(test)]
+mod tests {
+    use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data};
+    use crate::services::field::type_options::checkbox_type_option::{NO, YES};
+    use crate::services::field::FieldBuilder;
+
+    use crate::entities::FieldType;
+
+    #[test]
+    fn checkout_box_description_test() {
+        let field_rev = FieldBuilder::from_field_type(&FieldType::Checkbox).build();
+        let data = apply_cell_data_changeset("true", None, &field_rev).unwrap();
+        assert_eq!(decode_any_cell_data(data, &field_rev).to_string(), YES);
+
+        let data = apply_cell_data_changeset("1", None, &field_rev).unwrap();
+        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), YES);
+
+        let data = apply_cell_data_changeset("yes", None, &field_rev).unwrap();
+        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), YES);
+
+        let data = apply_cell_data_changeset("false", None, &field_rev).unwrap();
+        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), NO);
+
+        let data = apply_cell_data_changeset("no", None, &field_rev).unwrap();
+        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), NO);
+
+        let data = apply_cell_data_changeset("12", None, &field_rev).unwrap();
+        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), NO);
+    }
+}

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

@@ -1,672 +0,0 @@
-use crate::entities::{CellChangeset, FieldType};
-use crate::entities::{CellIdentifier, CellIdentifierPayload};
-use crate::impl_type_option;
-use crate::services::cell::{
-    AnyCellData, CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable, FromCellChangeset,
-    FromCellString,
-};
-use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use bytes::Bytes;
-use chrono::format::strftime::StrftimeItems;
-use chrono::{NaiveDateTime, Timelike};
-use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
-use serde::{Deserialize, Serialize};
-use strum_macros::EnumIter;
-
-// Date
-#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct DateTypeOption {
-    #[pb(index = 1)]
-    pub date_format: DateFormat,
-
-    #[pb(index = 2)]
-    pub time_format: TimeFormat,
-
-    #[pb(index = 3)]
-    pub include_time: bool,
-}
-impl_type_option!(DateTypeOption, FieldType::DateTime);
-
-impl DateTypeOption {
-    #[allow(dead_code)]
-    pub fn new() -> Self {
-        Self::default()
-    }
-
-    fn today_desc_from_timestamp(&self, timestamp: i64) -> DateCellData {
-        let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0);
-        self.date_from_native(native)
-    }
-
-    fn date_from_native(&self, native: chrono::NaiveDateTime) -> DateCellData {
-        if native.timestamp() == 0 {
-            return DateCellData::default();
-        }
-
-        let time = native.time();
-        let has_time = time.hour() != 0 || time.second() != 0;
-
-        let utc = self.utc_date_time_from_native(native);
-        let fmt = self.date_format.format_str();
-        let date = format!("{}", utc.format_with_items(StrftimeItems::new(fmt)));
-
-        let mut time = "".to_string();
-        if has_time {
-            let fmt = format!("{} {}", self.date_format.format_str(), self.time_format.format_str());
-            time = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt))).replace(&date, "");
-        }
-
-        let timestamp = native.timestamp();
-        DateCellData { date, time, timestamp }
-    }
-
-    fn date_fmt(&self, time: &Option<String>) -> String {
-        if self.include_time {
-            match time.as_ref() {
-                None => self.date_format.format_str().to_string(),
-                Some(time_str) => {
-                    if time_str.is_empty() {
-                        self.date_format.format_str().to_string()
-                    } else {
-                        format!("{} {}", self.date_format.format_str(), self.time_format.format_str())
-                    }
-                }
-            }
-        } else {
-            self.date_format.format_str().to_string()
-        }
-    }
-
-    fn timestamp_from_utc_with_time(
-        &self,
-        utc: &chrono::DateTime<chrono::Utc>,
-        time: &Option<String>,
-    ) -> FlowyResult<i64> {
-        if let Some(time_str) = time.as_ref() {
-            if !time_str.is_empty() {
-                let date_str = format!(
-                    "{}{}",
-                    utc.format_with_items(StrftimeItems::new(self.date_format.format_str())),
-                    &time_str
-                );
-
-                return match NaiveDateTime::parse_from_str(&date_str, &self.date_fmt(time)) {
-                    Ok(native) => {
-                        let utc = self.utc_date_time_from_native(native);
-                        Ok(utc.timestamp())
-                    }
-                    Err(_e) => {
-                        let msg = format!("Parse {} failed", date_str);
-                        Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg))
-                    }
-                };
-            }
-        }
-
-        Ok(utc.timestamp())
-    }
-
-    fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime<chrono::Utc> {
-        let native = NaiveDateTime::from_timestamp(timestamp, 0);
-        let native2 = NaiveDateTime::from_timestamp(timestamp, 0);
-
-        if native > native2 {}
-
-        self.utc_date_time_from_native(native)
-    }
-
-    fn utc_date_time_from_native(&self, naive: chrono::NaiveDateTime) -> chrono::DateTime<chrono::Utc> {
-        chrono::DateTime::<chrono::Utc>::from_utc(naive, chrono::Utc)
-    }
-}
-
-impl CellDisplayable<DateTimestamp, DateCellData> for DateTypeOption {
-    fn display_data(
-        &self,
-        cell_data: CellData<DateTimestamp>,
-        _decoded_field_type: &FieldType,
-        _field_rev: &FieldRevision,
-    ) -> FlowyResult<DateCellData> {
-        let timestamp = cell_data.try_into_inner()?;
-        Ok(self.today_desc_from_timestamp(timestamp.0))
-    }
-}
-
-impl CellDataOperation<DateTimestamp, DateCellChangeset> for DateTypeOption {
-    fn decode_cell_data(
-        &self,
-        cell_data: CellData<DateTimestamp>,
-        decoded_field_type: &FieldType,
-        field_rev: &FieldRevision,
-    ) -> FlowyResult<CellBytes> {
-        // Return default data if the type_option_cell_data is not FieldType::DateTime.
-        // It happens when switching from one field to another.
-        // For example:
-        // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen.
-        if !decoded_field_type.is_date() {
-            return Ok(CellBytes::default());
-        }
-        CellBytes::from(self.display_data(cell_data, decoded_field_type, field_rev)?)
-    }
-
-    fn apply_changeset(
-        &self,
-        changeset: CellDataChangeset<DateCellChangeset>,
-        _cell_rev: Option<CellRevision>,
-    ) -> Result<String, FlowyError> {
-        let changeset = changeset.try_into_inner()?;
-        let cell_data = match changeset.date_timestamp() {
-            None => 0,
-            Some(date_timestamp) => match (self.include_time, changeset.time) {
-                (true, Some(time)) => {
-                    let time = Some(time.trim().to_uppercase());
-                    let utc = self.utc_date_time_from_timestamp(date_timestamp);
-                    self.timestamp_from_utc_with_time(&utc, &time)?
-                }
-                _ => date_timestamp,
-            },
-        };
-
-        Ok(cell_data.to_string())
-    }
-}
-
-pub struct DateTimestamp(i64);
-impl AsRef<i64> for DateTimestamp {
-    fn as_ref(&self) -> &i64 {
-        &self.0
-    }
-}
-
-impl std::convert::From<DateTimestamp> for i64 {
-    fn from(timestamp: DateTimestamp) -> Self {
-        timestamp.0
-    }
-}
-
-impl FromCellString for DateTimestamp {
-    fn from_cell_str(s: &str) -> FlowyResult<Self>
-    where
-        Self: Sized,
-    {
-        let num = s.parse::<i64>().unwrap_or(0);
-        Ok(DateTimestamp(num))
-    }
-}
-
-impl std::convert::From<AnyCellData> for DateTimestamp {
-    fn from(data: AnyCellData) -> Self {
-        let num = data.data.parse::<i64>().unwrap_or(0);
-        DateTimestamp(num)
-    }
-}
-
-#[derive(Default)]
-pub struct DateTypeOptionBuilder(DateTypeOption);
-impl_into_box_type_option_builder!(DateTypeOptionBuilder);
-impl_builder_from_json_str_and_from_bytes!(DateTypeOptionBuilder, DateTypeOption);
-
-impl DateTypeOptionBuilder {
-    pub fn date_format(mut self, date_format: DateFormat) -> Self {
-        self.0.date_format = date_format;
-        self
-    }
-
-    pub fn time_format(mut self, time_format: TimeFormat) -> Self {
-        self.0.time_format = time_format;
-        self
-    }
-}
-impl TypeOptionBuilder for DateTypeOptionBuilder {
-    fn field_type(&self) -> FieldType {
-        FieldType::DateTime
-    }
-
-    fn entry(&self) -> &dyn TypeOptionDataEntry {
-        &self.0
-    }
-}
-
-#[derive(Clone, Debug, Copy, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
-pub enum DateFormat {
-    Local = 0,
-    US = 1,
-    ISO = 2,
-    Friendly = 3,
-}
-impl std::default::Default for DateFormat {
-    fn default() -> Self {
-        DateFormat::Friendly
-    }
-}
-
-impl std::convert::From<i32> for DateFormat {
-    fn from(value: i32) -> Self {
-        match value {
-            0 => DateFormat::Local,
-            1 => DateFormat::US,
-            2 => DateFormat::ISO,
-            3 => DateFormat::Friendly,
-            _ => {
-                tracing::error!("Unsupported date format, fallback to friendly");
-                DateFormat::Friendly
-            }
-        }
-    }
-}
-
-impl DateFormat {
-    pub fn value(&self) -> i32 {
-        *self as i32
-    }
-    // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
-    pub fn format_str(&self) -> &'static str {
-        match self {
-            DateFormat::Local => "%Y/%m/%d",
-            DateFormat::US => "%Y/%m/%d",
-            DateFormat::ISO => "%Y-%m-%d",
-            DateFormat::Friendly => "%b %d,%Y",
-        }
-    }
-}
-
-#[derive(Clone, Copy, PartialEq, Eq, EnumIter, Debug, Hash, Serialize, Deserialize, ProtoBuf_Enum)]
-pub enum TimeFormat {
-    TwelveHour = 0,
-    TwentyFourHour = 1,
-}
-
-impl std::convert::From<i32> for TimeFormat {
-    fn from(value: i32) -> Self {
-        match value {
-            0 => TimeFormat::TwelveHour,
-            1 => TimeFormat::TwentyFourHour,
-            _ => {
-                tracing::error!("Unsupported time format, fallback to TwentyFourHour");
-                TimeFormat::TwentyFourHour
-            }
-        }
-    }
-}
-
-impl TimeFormat {
-    pub fn value(&self) -> i32 {
-        *self as i32
-    }
-
-    // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
-    pub fn format_str(&self) -> &'static str {
-        match self {
-            TimeFormat::TwelveHour => "%I:%M %p",
-            TimeFormat::TwentyFourHour => "%R",
-        }
-    }
-}
-
-impl std::default::Default for TimeFormat {
-    fn default() -> Self {
-        TimeFormat::TwentyFourHour
-    }
-}
-
-#[derive(Clone, Debug, Default, ProtoBuf)]
-pub struct DateCellData {
-    #[pb(index = 1)]
-    pub date: String,
-
-    #[pb(index = 2)]
-    pub time: String,
-
-    #[pb(index = 3)]
-    pub timestamp: i64,
-}
-
-#[derive(Clone, Debug, Default, ProtoBuf)]
-pub struct DateChangesetPayload {
-    #[pb(index = 1)]
-    pub cell_identifier: CellIdentifierPayload,
-
-    #[pb(index = 2, one_of)]
-    pub date: Option<String>,
-
-    #[pb(index = 3, one_of)]
-    pub time: Option<String>,
-}
-
-pub struct DateChangesetParams {
-    pub cell_identifier: CellIdentifier,
-    pub date: Option<String>,
-    pub time: Option<String>,
-}
-
-impl TryInto<DateChangesetParams> for DateChangesetPayload {
-    type Error = ErrorCode;
-
-    fn try_into(self) -> Result<DateChangesetParams, Self::Error> {
-        let cell_identifier: CellIdentifier = self.cell_identifier.try_into()?;
-        Ok(DateChangesetParams {
-            cell_identifier,
-            date: self.date,
-            time: self.time,
-        })
-    }
-}
-
-impl std::convert::From<DateChangesetParams> for CellChangeset {
-    fn from(params: DateChangesetParams) -> Self {
-        let changeset = DateCellChangeset {
-            date: params.date,
-            time: params.time,
-        };
-        let s = serde_json::to_string(&changeset).unwrap();
-        CellChangeset {
-            grid_id: params.cell_identifier.grid_id,
-            row_id: params.cell_identifier.row_id,
-            field_id: params.cell_identifier.field_id,
-            content: Some(s),
-        }
-    }
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-pub struct DateCellChangeset {
-    pub date: Option<String>,
-    pub time: Option<String>,
-}
-
-impl DateCellChangeset {
-    pub fn date_timestamp(&self) -> Option<i64> {
-        if let Some(date) = &self.date {
-            match date.parse::<i64>() {
-                Ok(date_timestamp) => Some(date_timestamp),
-                Err(_) => None,
-            }
-        } else {
-            None
-        }
-    }
-}
-
-impl FromCellChangeset for DateCellChangeset {
-    fn from_changeset(changeset: String) -> FlowyResult<Self>
-    where
-        Self: Sized,
-    {
-        serde_json::from_str::<DateCellChangeset>(&changeset).map_err(internal_error)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::entities::FieldType;
-    use crate::services::cell::{CellDataChangeset, CellDataOperation};
-    use crate::services::field::FieldBuilder;
-    use crate::services::field::{DateCellChangeset, DateCellData, DateFormat, DateTypeOption, TimeFormat};
-    use flowy_grid_data_model::revision::FieldRevision;
-    use strum::IntoEnumIterator;
-
-    #[test]
-    fn date_type_option_invalid_input_test() {
-        let type_option = DateTypeOption::default();
-        let field_type = FieldType::DateTime;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some("1e".to_string()),
-                time: Some("23:00".to_owned()),
-            },
-            &field_type,
-            &field_rev,
-            "",
-        );
-    }
-
-    #[test]
-    fn date_type_option_date_format_test() {
-        let mut type_option = DateTypeOption::default();
-        let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build();
-        for date_format in DateFormat::iter() {
-            type_option.date_format = date_format;
-            match date_format {
-                DateFormat::Friendly => {
-                    assert_decode_timestamp(1647251762, &type_option, &field_rev, "Mar 14,2022");
-                }
-                DateFormat::US => {
-                    assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022/03/14");
-                }
-                DateFormat::ISO => {
-                    assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022-03-14");
-                }
-                DateFormat::Local => {
-                    assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022/03/14");
-                }
-            }
-        }
-    }
-
-    #[test]
-    fn date_type_option_time_format_test() {
-        let mut type_option = DateTypeOption::default();
-        let field_type = FieldType::DateTime;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-
-        for time_format in TimeFormat::iter() {
-            type_option.time_format = time_format;
-            type_option.include_time = true;
-            match time_format {
-                TimeFormat::TwentyFourHour => {
-                    assert_changeset_result(
-                        &type_option,
-                        DateCellChangeset {
-                            date: Some(1653609600.to_string()),
-                            time: None,
-                        },
-                        &field_type,
-                        &field_rev,
-                        "May 27,2022",
-                    );
-                    assert_changeset_result(
-                        &type_option,
-                        DateCellChangeset {
-                            date: Some(1653609600.to_string()),
-                            time: Some("23:00".to_owned()),
-                        },
-                        &field_type,
-                        &field_rev,
-                        "May 27,2022 23:00",
-                    );
-                }
-                TimeFormat::TwelveHour => {
-                    assert_changeset_result(
-                        &type_option,
-                        DateCellChangeset {
-                            date: Some(1653609600.to_string()),
-                            time: None,
-                        },
-                        &field_type,
-                        &field_rev,
-                        "May 27,2022",
-                    );
-                    //
-                    assert_changeset_result(
-                        &type_option,
-                        DateCellChangeset {
-                            date: Some(1653609600.to_string()),
-                            time: Some("".to_owned()),
-                        },
-                        &field_type,
-                        &field_rev,
-                        "May 27,2022",
-                    );
-
-                    assert_changeset_result(
-                        &type_option,
-                        DateCellChangeset {
-                            date: Some(1653609600.to_string()),
-                            time: Some("11:23 pm".to_owned()),
-                        },
-                        &field_type,
-                        &field_rev,
-                        "May 27,2022 11:23 PM",
-                    );
-                }
-            }
-        }
-    }
-
-    #[test]
-    fn date_type_option_apply_changeset_test() {
-        let mut type_option = DateTypeOption::new();
-        let field_type = FieldType::DateTime;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-        let date_timestamp = "1653609600".to_owned();
-
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some(date_timestamp.clone()),
-                time: None,
-            },
-            &field_type,
-            &field_rev,
-            "May 27,2022",
-        );
-
-        type_option.include_time = true;
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some(date_timestamp.clone()),
-                time: None,
-            },
-            &field_type,
-            &field_rev,
-            "May 27,2022",
-        );
-
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some(date_timestamp.clone()),
-                time: Some("1:00".to_owned()),
-            },
-            &field_type,
-            &field_rev,
-            "May 27,2022 01:00",
-        );
-
-        type_option.time_format = TimeFormat::TwelveHour;
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some(date_timestamp),
-                time: Some("1:00 am".to_owned()),
-            },
-            &field_type,
-            &field_rev,
-            "May 27,2022 01:00 AM",
-        );
-    }
-
-    #[test]
-    #[should_panic]
-    fn date_type_option_apply_changeset_error_test() {
-        let mut type_option = DateTypeOption::new();
-        type_option.include_time = true;
-        let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build();
-        let date_timestamp = "1653609600".to_owned();
-
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some(date_timestamp.clone()),
-                time: Some("1:".to_owned()),
-            },
-            &FieldType::DateTime,
-            &field_rev,
-            "May 27,2022 01:00",
-        );
-
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some(date_timestamp),
-                time: Some("1:00".to_owned()),
-            },
-            &FieldType::DateTime,
-            &field_rev,
-            "May 27,2022 01:00",
-        );
-    }
-
-    #[test]
-    #[should_panic]
-    fn date_type_option_twelve_hours_to_twenty_four_hours() {
-        let mut type_option = DateTypeOption::new();
-        type_option.include_time = true;
-        let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build();
-        let date_timestamp = "1653609600".to_owned();
-
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some(date_timestamp),
-                time: Some("1:00 am".to_owned()),
-            },
-            &FieldType::DateTime,
-            &field_rev,
-            "May 27,2022 01:00",
-        );
-    }
-
-    fn assert_changeset_result(
-        type_option: &DateTypeOption,
-        changeset: DateCellChangeset,
-        _field_type: &FieldType,
-        field_rev: &FieldRevision,
-        expected: &str,
-    ) {
-        let changeset = CellDataChangeset(Some(changeset));
-        let encoded_data = type_option.apply_changeset(changeset, None).unwrap();
-        assert_eq!(
-            expected.to_owned(),
-            decode_cell_data(encoded_data, type_option, field_rev)
-        );
-    }
-
-    fn assert_decode_timestamp(
-        timestamp: i64,
-        type_option: &DateTypeOption,
-        field_rev: &FieldRevision,
-        expected: &str,
-    ) {
-        let s = serde_json::to_string(&DateCellChangeset {
-            date: Some(timestamp.to_string()),
-            time: None,
-        })
-        .unwrap();
-        let encoded_data = type_option.apply_changeset(s.into(), None).unwrap();
-
-        assert_eq!(
-            expected.to_owned(),
-            decode_cell_data(encoded_data, type_option, field_rev)
-        );
-    }
-
-    fn decode_cell_data(encoded_data: String, type_option: &DateTypeOption, field_rev: &FieldRevision) -> String {
-        let decoded_data = type_option
-            .decode_cell_data(encoded_data.into(), &FieldType::DateTime, field_rev)
-            .unwrap()
-            .parse::<DateCellData>()
-            .unwrap();
-
-        if type_option.include_time {
-            format!("{}{}", decoded_data.date, decoded_data.time)
-        } else {
-            decoded_data.date
-        }
-    }
-}

+ 199 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_option.rs

@@ -0,0 +1,199 @@
+use crate::entities::{FieldType};
+
+use crate::impl_type_option;
+use crate::services::cell::{
+    AnyCellData, CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable, FromCellChangeset,
+    FromCellString,
+};
+use crate::services::field::{
+    BoxTypeOptionBuilder, DateCellChangeset, DateCellData, DateFormat, DateTimestamp, TimeFormat, TypeOptionBuilder,
+};
+use bytes::Bytes;
+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, TypeOptionDataEntry};
+use serde::{Deserialize, Serialize};
+
+
+// Date
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
+pub struct DateTypeOption {
+    #[pb(index = 1)]
+    pub date_format: DateFormat,
+
+    #[pb(index = 2)]
+    pub time_format: TimeFormat,
+
+    #[pb(index = 3)]
+    pub include_time: bool,
+}
+impl_type_option!(DateTypeOption, FieldType::DateTime);
+
+impl DateTypeOption {
+    #[allow(dead_code)]
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    fn today_desc_from_timestamp<T: AsRef<i64>>(&self, timestamp: T) -> DateCellData {
+        let timestamp = *timestamp.as_ref();
+        let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0);
+        self.date_from_native(native)
+    }
+
+    fn date_from_native(&self, native: chrono::NaiveDateTime) -> DateCellData {
+        if native.timestamp() == 0 {
+            return DateCellData::default();
+        }
+
+        let time = native.time();
+        let has_time = time.hour() != 0 || time.second() != 0;
+
+        let utc = self.utc_date_time_from_native(native);
+        let fmt = self.date_format.format_str();
+        let date = format!("{}", utc.format_with_items(StrftimeItems::new(fmt)));
+
+        let mut time = "".to_string();
+        if has_time {
+            let fmt = format!("{} {}", self.date_format.format_str(), self.time_format.format_str());
+            time = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt))).replace(&date, "");
+        }
+
+        let timestamp = native.timestamp();
+        DateCellData { date, time, timestamp }
+    }
+
+    fn date_fmt(&self, time: &Option<String>) -> String {
+        if self.include_time {
+            match time.as_ref() {
+                None => self.date_format.format_str().to_string(),
+                Some(time_str) => {
+                    if time_str.is_empty() {
+                        self.date_format.format_str().to_string()
+                    } else {
+                        format!("{} {}", self.date_format.format_str(), self.time_format.format_str())
+                    }
+                }
+            }
+        } else {
+            self.date_format.format_str().to_string()
+        }
+    }
+
+    fn timestamp_from_utc_with_time(
+        &self,
+        utc: &chrono::DateTime<chrono::Utc>,
+        time: &Option<String>,
+    ) -> FlowyResult<i64> {
+        if let Some(time_str) = time.as_ref() {
+            if !time_str.is_empty() {
+                let date_str = format!(
+                    "{}{}",
+                    utc.format_with_items(StrftimeItems::new(self.date_format.format_str())),
+                    &time_str
+                );
+
+                return match NaiveDateTime::parse_from_str(&date_str, &self.date_fmt(time)) {
+                    Ok(native) => {
+                        let utc = self.utc_date_time_from_native(native);
+                        Ok(utc.timestamp())
+                    }
+                    Err(_e) => {
+                        let msg = format!("Parse {} failed", date_str);
+                        Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg))
+                    }
+                };
+            }
+        }
+
+        Ok(utc.timestamp())
+    }
+
+    fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime<chrono::Utc> {
+        let native = NaiveDateTime::from_timestamp(timestamp, 0);
+        self.utc_date_time_from_native(native)
+    }
+
+    fn utc_date_time_from_native(&self, naive: chrono::NaiveDateTime) -> chrono::DateTime<chrono::Utc> {
+        chrono::DateTime::<chrono::Utc>::from_utc(naive, chrono::Utc)
+    }
+}
+
+impl CellDisplayable<DateTimestamp> for DateTypeOption {
+    fn display_data(
+        &self,
+        cell_data: CellData<DateTimestamp>,
+        _decoded_field_type: &FieldType,
+        _field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        let timestamp = cell_data.try_into_inner()?;
+        CellBytes::from(self.today_desc_from_timestamp(timestamp))
+    }
+}
+
+impl CellDataOperation<DateTimestamp, DateCellChangeset> for DateTypeOption {
+    fn decode_cell_data(
+        &self,
+        cell_data: CellData<DateTimestamp>,
+        decoded_field_type: &FieldType,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        // Return default data if the type_option_cell_data is not FieldType::DateTime.
+        // It happens when switching from one field to another.
+        // For example:
+        // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen.
+        if !decoded_field_type.is_date() {
+            return Ok(CellBytes::default());
+        }
+        self.display_data(cell_data, decoded_field_type, field_rev)
+    }
+
+    fn apply_changeset(
+        &self,
+        changeset: CellDataChangeset<DateCellChangeset>,
+        _cell_rev: Option<CellRevision>,
+    ) -> Result<String, FlowyError> {
+        let changeset = changeset.try_into_inner()?;
+        let cell_data = match changeset.date_timestamp() {
+            None => 0,
+            Some(date_timestamp) => match (self.include_time, changeset.time) {
+                (true, Some(time)) => {
+                    let time = Some(time.trim().to_uppercase());
+                    let utc = self.utc_date_time_from_timestamp(date_timestamp);
+                    self.timestamp_from_utc_with_time(&utc, &time)?
+                }
+                _ => date_timestamp,
+            },
+        };
+
+        Ok(cell_data.to_string())
+    }
+}
+
+#[derive(Default)]
+pub struct DateTypeOptionBuilder(DateTypeOption);
+impl_into_box_type_option_builder!(DateTypeOptionBuilder);
+impl_builder_from_json_str_and_from_bytes!(DateTypeOptionBuilder, DateTypeOption);
+
+impl DateTypeOptionBuilder {
+    pub fn date_format(mut self, date_format: DateFormat) -> Self {
+        self.0.date_format = date_format;
+        self
+    }
+
+    pub fn time_format(mut self, time_format: TimeFormat) -> Self {
+        self.0.time_format = time_format;
+        self
+    }
+}
+impl TypeOptionBuilder for DateTypeOptionBuilder {
+    fn field_type(&self) -> FieldType {
+        FieldType::DateTime
+    }
+
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
+    }
+}

+ 206 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_option_entities.rs

@@ -0,0 +1,206 @@
+use crate::entities::CellChangeset;
+use crate::entities::{CellIdentifier, CellIdentifierPayload};
+use crate::services::cell::{AnyCellData, FromCellChangeset, FromCellString};
+
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::{internal_error, ErrorCode, FlowyResult};
+
+use serde::{Deserialize, Serialize};
+use strum_macros::EnumIter;
+
+#[derive(Clone, Debug, Default, ProtoBuf)]
+pub struct DateCellData {
+    #[pb(index = 1)]
+    pub date: String,
+
+    #[pb(index = 2)]
+    pub time: String,
+
+    #[pb(index = 3)]
+    pub timestamp: i64,
+}
+
+#[derive(Clone, Debug, Default, ProtoBuf)]
+pub struct DateChangesetPayload {
+    #[pb(index = 1)]
+    pub cell_identifier: CellIdentifierPayload,
+
+    #[pb(index = 2, one_of)]
+    pub date: Option<String>,
+
+    #[pb(index = 3, one_of)]
+    pub time: Option<String>,
+}
+
+pub struct DateChangesetParams {
+    pub cell_identifier: CellIdentifier,
+    pub date: Option<String>,
+    pub time: Option<String>,
+}
+
+impl TryInto<DateChangesetParams> for DateChangesetPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<DateChangesetParams, Self::Error> {
+        let cell_identifier: CellIdentifier = self.cell_identifier.try_into()?;
+        Ok(DateChangesetParams {
+            cell_identifier,
+            date: self.date,
+            time: self.time,
+        })
+    }
+}
+
+impl std::convert::From<DateChangesetParams> for CellChangeset {
+    fn from(params: DateChangesetParams) -> Self {
+        let changeset = DateCellChangeset {
+            date: params.date,
+            time: params.time,
+        };
+        let s = serde_json::to_string(&changeset).unwrap();
+        CellChangeset {
+            grid_id: params.cell_identifier.grid_id,
+            row_id: params.cell_identifier.row_id,
+            field_id: params.cell_identifier.field_id,
+            content: Some(s),
+        }
+    }
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+pub struct DateCellChangeset {
+    pub date: Option<String>,
+    pub time: Option<String>,
+}
+
+impl DateCellChangeset {
+    pub fn date_timestamp(&self) -> Option<i64> {
+        if let Some(date) = &self.date {
+            match date.parse::<i64>() {
+                Ok(date_timestamp) => Some(date_timestamp),
+                Err(_) => None,
+            }
+        } else {
+            None
+        }
+    }
+}
+
+impl FromCellChangeset for DateCellChangeset {
+    fn from_changeset(changeset: String) -> FlowyResult<Self>
+    where
+        Self: Sized,
+    {
+        serde_json::from_str::<DateCellChangeset>(&changeset).map_err(internal_error)
+    }
+}
+pub struct DateTimestamp(i64);
+impl AsRef<i64> for DateTimestamp {
+    fn as_ref(&self) -> &i64 {
+        &self.0
+    }
+}
+
+impl std::convert::From<DateTimestamp> for i64 {
+    fn from(timestamp: DateTimestamp) -> Self {
+        timestamp.0
+    }
+}
+
+impl FromCellString for DateTimestamp {
+    fn from_cell_str(s: &str) -> FlowyResult<Self>
+    where
+        Self: Sized,
+    {
+        let num = s.parse::<i64>().unwrap_or(0);
+        Ok(DateTimestamp(num))
+    }
+}
+
+impl std::convert::From<AnyCellData> for DateTimestamp {
+    fn from(data: AnyCellData) -> Self {
+        let num = data.data.parse::<i64>().unwrap_or(0);
+        DateTimestamp(num)
+    }
+}
+#[derive(Clone, Debug, Copy, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
+pub enum DateFormat {
+    Local = 0,
+    US = 1,
+    ISO = 2,
+    Friendly = 3,
+}
+impl std::default::Default for DateFormat {
+    fn default() -> Self {
+        DateFormat::Friendly
+    }
+}
+
+impl std::convert::From<i32> for DateFormat {
+    fn from(value: i32) -> Self {
+        match value {
+            0 => DateFormat::Local,
+            1 => DateFormat::US,
+            2 => DateFormat::ISO,
+            3 => DateFormat::Friendly,
+            _ => {
+                tracing::error!("Unsupported date format, fallback to friendly");
+                DateFormat::Friendly
+            }
+        }
+    }
+}
+
+impl DateFormat {
+    pub fn value(&self) -> i32 {
+        *self as i32
+    }
+    // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
+    pub fn format_str(&self) -> &'static str {
+        match self {
+            DateFormat::Local => "%Y/%m/%d",
+            DateFormat::US => "%Y/%m/%d",
+            DateFormat::ISO => "%Y-%m-%d",
+            DateFormat::Friendly => "%b %d,%Y",
+        }
+    }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, EnumIter, Debug, Hash, Serialize, Deserialize, ProtoBuf_Enum)]
+pub enum TimeFormat {
+    TwelveHour = 0,
+    TwentyFourHour = 1,
+}
+
+impl std::convert::From<i32> for TimeFormat {
+    fn from(value: i32) -> Self {
+        match value {
+            0 => TimeFormat::TwelveHour,
+            1 => TimeFormat::TwentyFourHour,
+            _ => {
+                tracing::error!("Unsupported time format, fallback to TwentyFourHour");
+                TimeFormat::TwentyFourHour
+            }
+        }
+    }
+}
+
+impl TimeFormat {
+    pub fn value(&self) -> i32 {
+        *self as i32
+    }
+
+    // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
+    pub fn format_str(&self) -> &'static str {
+        match self {
+            TimeFormat::TwelveHour => "%I:%M %p",
+            TimeFormat::TwentyFourHour => "%R",
+        }
+    }
+}
+
+impl std::default::Default for TimeFormat {
+    fn default() -> Self {
+        TimeFormat::TwentyFourHour
+    }
+}

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

@@ -0,0 +1,6 @@
+mod date_option;
+mod date_option_entities;
+mod tests;
+
+pub use date_option::*;
+pub use date_option_entities::*;

+ 272 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/tests.rs

@@ -0,0 +1,272 @@
+#[cfg(test)]
+mod tests {
+    use crate::entities::FieldType;
+    use crate::services::cell::{CellDataChangeset, CellDataOperation};
+    use crate::services::field::FieldBuilder;
+    use crate::services::field::{DateCellChangeset, DateCellData, DateFormat, DateTypeOption, TimeFormat};
+    use flowy_grid_data_model::revision::FieldRevision;
+    use strum::IntoEnumIterator;
+
+    #[test]
+    fn date_type_option_invalid_input_test() {
+        let type_option = DateTypeOption::default();
+        let field_type = FieldType::DateTime;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some("1e".to_string()),
+                time: Some("23:00".to_owned()),
+            },
+            &field_type,
+            &field_rev,
+            "",
+        );
+    }
+
+    #[test]
+    fn date_type_option_date_format_test() {
+        let mut type_option = DateTypeOption::default();
+        let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build();
+        for date_format in DateFormat::iter() {
+            type_option.date_format = date_format;
+            match date_format {
+                DateFormat::Friendly => {
+                    assert_decode_timestamp(1647251762, &type_option, &field_rev, "Mar 14,2022");
+                }
+                DateFormat::US => {
+                    assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022/03/14");
+                }
+                DateFormat::ISO => {
+                    assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022-03-14");
+                }
+                DateFormat::Local => {
+                    assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022/03/14");
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn date_type_option_time_format_test() {
+        let mut type_option = DateTypeOption::default();
+        let field_type = FieldType::DateTime;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+
+        for time_format in TimeFormat::iter() {
+            type_option.time_format = time_format;
+            type_option.include_time = true;
+            match time_format {
+                TimeFormat::TwentyFourHour => {
+                    assert_changeset_result(
+                        &type_option,
+                        DateCellChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: None,
+                        },
+                        &field_type,
+                        &field_rev,
+                        "May 27,2022",
+                    );
+                    assert_changeset_result(
+                        &type_option,
+                        DateCellChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: Some("23:00".to_owned()),
+                        },
+                        &field_type,
+                        &field_rev,
+                        "May 27,2022 23:00",
+                    );
+                }
+                TimeFormat::TwelveHour => {
+                    assert_changeset_result(
+                        &type_option,
+                        DateCellChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: None,
+                        },
+                        &field_type,
+                        &field_rev,
+                        "May 27,2022",
+                    );
+                    //
+                    assert_changeset_result(
+                        &type_option,
+                        DateCellChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: Some("".to_owned()),
+                        },
+                        &field_type,
+                        &field_rev,
+                        "May 27,2022",
+                    );
+
+                    assert_changeset_result(
+                        &type_option,
+                        DateCellChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: Some("11:23 pm".to_owned()),
+                        },
+                        &field_type,
+                        &field_rev,
+                        "May 27,2022 11:23 PM",
+                    );
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn date_type_option_apply_changeset_test() {
+        let mut type_option = DateTypeOption::new();
+        let field_type = FieldType::DateTime;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+        let date_timestamp = "1653609600".to_owned();
+
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some(date_timestamp.clone()),
+                time: None,
+            },
+            &field_type,
+            &field_rev,
+            "May 27,2022",
+        );
+
+        type_option.include_time = true;
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some(date_timestamp.clone()),
+                time: None,
+            },
+            &field_type,
+            &field_rev,
+            "May 27,2022",
+        );
+
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some(date_timestamp.clone()),
+                time: Some("1:00".to_owned()),
+            },
+            &field_type,
+            &field_rev,
+            "May 27,2022 01:00",
+        );
+
+        type_option.time_format = TimeFormat::TwelveHour;
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some(date_timestamp),
+                time: Some("1:00 am".to_owned()),
+            },
+            &field_type,
+            &field_rev,
+            "May 27,2022 01:00 AM",
+        );
+    }
+
+    #[test]
+    #[should_panic]
+    fn date_type_option_apply_changeset_error_test() {
+        let mut type_option = DateTypeOption::new();
+        type_option.include_time = true;
+        let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build();
+        let date_timestamp = "1653609600".to_owned();
+
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some(date_timestamp.clone()),
+                time: Some("1:".to_owned()),
+            },
+            &FieldType::DateTime,
+            &field_rev,
+            "May 27,2022 01:00",
+        );
+
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some(date_timestamp),
+                time: Some("1:00".to_owned()),
+            },
+            &FieldType::DateTime,
+            &field_rev,
+            "May 27,2022 01:00",
+        );
+    }
+
+    #[test]
+    #[should_panic]
+    fn date_type_option_twelve_hours_to_twenty_four_hours() {
+        let mut type_option = DateTypeOption::new();
+        type_option.include_time = true;
+        let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build();
+        let date_timestamp = "1653609600".to_owned();
+
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some(date_timestamp),
+                time: Some("1:00 am".to_owned()),
+            },
+            &FieldType::DateTime,
+            &field_rev,
+            "May 27,2022 01:00",
+        );
+    }
+
+    fn assert_changeset_result(
+        type_option: &DateTypeOption,
+        changeset: DateCellChangeset,
+        _field_type: &FieldType,
+        field_rev: &FieldRevision,
+        expected: &str,
+    ) {
+        let changeset = CellDataChangeset(Some(changeset));
+        let encoded_data = type_option.apply_changeset(changeset, None).unwrap();
+        assert_eq!(
+            expected.to_owned(),
+            decode_cell_data(encoded_data, type_option, field_rev)
+        );
+    }
+
+    fn assert_decode_timestamp(
+        timestamp: i64,
+        type_option: &DateTypeOption,
+        field_rev: &FieldRevision,
+        expected: &str,
+    ) {
+        let s = serde_json::to_string(&DateCellChangeset {
+            date: Some(timestamp.to_string()),
+            time: None,
+        })
+        .unwrap();
+        let encoded_data = type_option.apply_changeset(s.into(), None).unwrap();
+
+        assert_eq!(
+            expected.to_owned(),
+            decode_cell_data(encoded_data, type_option, field_rev)
+        );
+    }
+
+    fn decode_cell_data(encoded_data: String, type_option: &DateTypeOption, field_rev: &FieldRevision) -> String {
+        let decoded_data = type_option
+            .decode_cell_data(encoded_data.into(), &FieldType::DateTime, field_rev)
+            .unwrap()
+            .parse::<DateCellData>()
+            .unwrap();
+
+        if type_option.include_time {
+            format!("{}{}", decoded_data.date, decoded_data.time)
+        } else {
+            decoded_data.date
+        }
+    }
+}

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

@@ -1,17 +1,14 @@
-mod checkbox_type_option;
-mod date_type_option;
-mod multi_select_type_option;
-mod number_type_option;
-mod single_select_type_option;
-mod text_type_option;
-mod url_type_option;
+pub mod checkbox_type_option;
+pub mod date_type_option;
+pub mod number_type_option;
+pub mod selection_type_option;
+pub mod text_type_option;
+pub mod url_type_option;
 mod util;
 
 pub use checkbox_type_option::*;
 pub use date_type_option::*;
-pub use multi_select_type_option::*;
-pub use multi_select_type_option::*;
 pub use number_type_option::*;
-pub use single_select_type_option::*;
+pub use selection_type_option::*;
 pub use text_type_option::*;
 pub use url_type_option::*;

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

@@ -1,6 +1,9 @@
 #![allow(clippy::module_inception)]
 mod format;
-mod number_type_option;
+mod number_option;
+mod number_option_entities;
+mod tests;
 
 pub use format::*;
-pub use number_type_option::*;
+pub use number_option::*;
+pub use number_option_entities::*;

+ 149 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_option.rs

@@ -0,0 +1,149 @@
+use crate::impl_type_option;
+
+use crate::entities::FieldType;
+use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation};
+use crate::services::field::number_currency::Currency;
+use crate::services::field::type_options::number_type_option::format::*;
+use crate::services::field::{BoxTypeOptionBuilder, NumberCellData, TypeOptionBuilder};
+use bytes::Bytes;
+use flowy_derive::ProtoBuf;
+use flowy_error::{FlowyError, FlowyResult};
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
+
+use rust_decimal::Decimal;
+use rusty_money::Money;
+use serde::{Deserialize, Serialize};
+use std::str::FromStr;
+
+#[derive(Default)]
+pub struct NumberTypeOptionBuilder(NumberTypeOption);
+impl_into_box_type_option_builder!(NumberTypeOptionBuilder);
+impl_builder_from_json_str_and_from_bytes!(NumberTypeOptionBuilder, NumberTypeOption);
+
+impl NumberTypeOptionBuilder {
+    pub fn name(mut self, name: &str) -> Self {
+        self.0.name = name.to_string();
+        self
+    }
+
+    pub fn set_format(mut self, format: NumberFormat) -> Self {
+        self.0.set_format(format);
+        self
+    }
+
+    pub fn scale(mut self, scale: u32) -> Self {
+        self.0.scale = scale;
+        self
+    }
+
+    pub fn positive(mut self, positive: bool) -> Self {
+        self.0.sign_positive = positive;
+        self
+    }
+}
+
+impl TypeOptionBuilder for NumberTypeOptionBuilder {
+    fn field_type(&self) -> FieldType {
+        FieldType::Number
+    }
+
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
+    }
+}
+
+// Number
+#[derive(Clone, Debug, Serialize, Deserialize, ProtoBuf)]
+pub struct NumberTypeOption {
+    #[pb(index = 1)]
+    pub format: NumberFormat,
+
+    #[pb(index = 2)]
+    pub scale: u32,
+
+    #[pb(index = 3)]
+    pub symbol: String,
+
+    #[pb(index = 4)]
+    pub sign_positive: bool,
+
+    #[pb(index = 5)]
+    pub name: String,
+}
+impl_type_option!(NumberTypeOption, FieldType::Number);
+
+impl NumberTypeOption {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    pub(crate) fn format_cell_data(&self, s: &str) -> FlowyResult<NumberCellData> {
+        match self.format {
+            NumberFormat::Num | NumberFormat::Percent => match Decimal::from_str(s) {
+                Ok(value, ..) => Ok(NumberCellData::from_decimal(value)),
+                Err(_) => Ok(NumberCellData::new()),
+            },
+            _ => NumberCellData::from_format_str(s, self.sign_positive, &self.format),
+        }
+    }
+
+    pub fn set_format(&mut self, format: NumberFormat) {
+        self.format = format;
+        self.symbol = format.symbol();
+    }
+}
+
+pub(crate) fn strip_currency_symbol<T: ToString>(s: T) -> String {
+    let mut s = s.to_string();
+    for symbol in CURRENCY_SYMBOL.iter() {
+        if s.starts_with(symbol) {
+            s = s.strip_prefix(symbol).unwrap_or("").to_string();
+            break;
+        }
+    }
+    s
+}
+
+impl CellDataOperation<String, String> for NumberTypeOption {
+    fn decode_cell_data(
+        &self,
+        cell_data: CellData<String>,
+        decoded_field_type: &FieldType,
+        _field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        if decoded_field_type.is_date() {
+            return Ok(CellBytes::default());
+        }
+
+        let cell_data: String = cell_data.try_into_inner()?;
+        match self.format_cell_data(&cell_data) {
+            Ok(num) => Ok(CellBytes::new(num.to_string())),
+            Err(_) => Ok(CellBytes::default()),
+        }
+    }
+
+    fn apply_changeset(
+        &self,
+        changeset: CellDataChangeset<String>,
+        _cell_rev: Option<CellRevision>,
+    ) -> Result<String, FlowyError> {
+        let changeset = changeset.try_into_inner()?;
+        let data = changeset.trim().to_string();
+        let _ = self.format_cell_data(&data)?;
+        Ok(data)
+    }
+}
+
+impl std::default::Default for NumberTypeOption {
+    fn default() -> Self {
+        let format = NumberFormat::default();
+        let symbol = format.symbol();
+        NumberTypeOption {
+            format,
+            scale: 0,
+            symbol,
+            sign_positive: true,
+            name: "Number".to_string(),
+        }
+    }
+}

+ 93 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_option_entities.rs

@@ -0,0 +1,93 @@
+use crate::services::field::number_currency::Currency;
+use crate::services::field::{strip_currency_symbol, NumberFormat, STRIP_SYMBOL};
+use flowy_error::{FlowyError, FlowyResult};
+use rust_decimal::Decimal;
+use rusty_money::Money;
+use std::str::FromStr;
+
+#[derive(Default)]
+pub struct NumberCellData {
+    decimal: Option<Decimal>,
+    money: Option<String>,
+}
+
+impl NumberCellData {
+    pub fn new() -> Self {
+        Self {
+            decimal: Default::default(),
+            money: None,
+        }
+    }
+
+    pub fn from_format_str(s: &str, sign_positive: bool, format: &NumberFormat) -> FlowyResult<Self> {
+        let mut num_str = strip_currency_symbol(s);
+        let currency = format.currency();
+        if num_str.is_empty() {
+            return Ok(Self::default());
+        }
+        match Decimal::from_str(&num_str) {
+            Ok(mut decimal) => {
+                decimal.set_sign_positive(sign_positive);
+                let money = Money::from_decimal(decimal, currency);
+                Ok(Self::from_money(money))
+            }
+            Err(_) => match Money::from_str(&num_str, currency) {
+                Ok(money) => Ok(NumberCellData::from_money(money)),
+                Err(_) => {
+                    num_str.retain(|c| !STRIP_SYMBOL.contains(&c.to_string()));
+                    if num_str.chars().all(char::is_numeric) {
+                        Self::from_format_str(&num_str, sign_positive, format)
+                    } else {
+                        Err(FlowyError::invalid_data().context("Should only contain numbers"))
+                    }
+                }
+            },
+        }
+    }
+
+    pub fn from_decimal(decimal: Decimal) -> Self {
+        Self {
+            decimal: Some(decimal),
+            money: None,
+        }
+    }
+
+    pub fn from_money(money: Money<Currency>) -> Self {
+        Self {
+            decimal: Some(*money.amount()),
+            money: Some(money.to_string()),
+        }
+    }
+
+    pub fn decimal(&self) -> &Option<Decimal> {
+        &self.decimal
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.decimal.is_none()
+    }
+}
+
+impl FromStr for NumberCellData {
+    type Err = rust_decimal::Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        if s.is_empty() {
+            return Ok(Self::default());
+        }
+        let decimal = Decimal::from_str(s)?;
+        Ok(Self::from_decimal(decimal))
+    }
+}
+
+impl ToString for NumberCellData {
+    fn to_string(&self) -> String {
+        match &self.money {
+            None => match self.decimal {
+                None => String::default(),
+                Some(decimal) => decimal.to_string(),
+            },
+            Some(money) => money.to_string(),
+        }
+    }
+}

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

@@ -1,376 +0,0 @@
-use crate::impl_type_option;
-
-use crate::entities::FieldType;
-use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation};
-use crate::services::field::number_currency::Currency;
-use crate::services::field::type_options::number_type_option::format::*;
-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, TypeOptionDataEntry};
-
-use rust_decimal::Decimal;
-use rusty_money::Money;
-use serde::{Deserialize, Serialize};
-use std::str::FromStr;
-
-#[derive(Default)]
-pub struct NumberTypeOptionBuilder(NumberTypeOption);
-impl_into_box_type_option_builder!(NumberTypeOptionBuilder);
-impl_builder_from_json_str_and_from_bytes!(NumberTypeOptionBuilder, NumberTypeOption);
-
-impl NumberTypeOptionBuilder {
-    pub fn name(mut self, name: &str) -> Self {
-        self.0.name = name.to_string();
-        self
-    }
-
-    pub fn set_format(mut self, format: NumberFormat) -> Self {
-        self.0.set_format(format);
-        self
-    }
-
-    pub fn scale(mut self, scale: u32) -> Self {
-        self.0.scale = scale;
-        self
-    }
-
-    pub fn positive(mut self, positive: bool) -> Self {
-        self.0.sign_positive = positive;
-        self
-    }
-}
-
-impl TypeOptionBuilder for NumberTypeOptionBuilder {
-    fn field_type(&self) -> FieldType {
-        FieldType::Number
-    }
-
-    fn entry(&self) -> &dyn TypeOptionDataEntry {
-        &self.0
-    }
-}
-
-// Number
-#[derive(Clone, Debug, Serialize, Deserialize, ProtoBuf)]
-pub struct NumberTypeOption {
-    #[pb(index = 1)]
-    pub format: NumberFormat,
-
-    #[pb(index = 2)]
-    pub scale: u32,
-
-    #[pb(index = 3)]
-    pub symbol: String,
-
-    #[pb(index = 4)]
-    pub sign_positive: bool,
-
-    #[pb(index = 5)]
-    pub name: String,
-}
-impl_type_option!(NumberTypeOption, FieldType::Number);
-
-impl NumberTypeOption {
-    pub fn new() -> Self {
-        Self::default()
-    }
-
-    pub(crate) fn format_cell_data(&self, s: &str) -> FlowyResult<NumberCellData> {
-        match self.format {
-            NumberFormat::Num | NumberFormat::Percent => match Decimal::from_str(s) {
-                Ok(value, ..) => Ok(NumberCellData::from_decimal(value)),
-                Err(_) => Ok(NumberCellData::new()),
-            },
-            _ => NumberCellData::from_format_str(s, self.sign_positive, &self.format),
-        }
-    }
-
-    pub fn set_format(&mut self, format: NumberFormat) {
-        self.format = format;
-        self.symbol = format.symbol();
-    }
-}
-
-pub(crate) fn strip_currency_symbol<T: ToString>(s: T) -> String {
-    let mut s = s.to_string();
-    for symbol in CURRENCY_SYMBOL.iter() {
-        if s.starts_with(symbol) {
-            s = s.strip_prefix(symbol).unwrap_or("").to_string();
-            break;
-        }
-    }
-    s
-}
-
-impl CellDataOperation<String, String> for NumberTypeOption {
-    fn decode_cell_data(
-        &self,
-        cell_data: CellData<String>,
-        decoded_field_type: &FieldType,
-        _field_rev: &FieldRevision,
-    ) -> FlowyResult<CellBytes> {
-        if decoded_field_type.is_date() {
-            return Ok(CellBytes::default());
-        }
-
-        let cell_data: String = cell_data.try_into_inner()?;
-        match self.format_cell_data(&cell_data) {
-            Ok(num) => Ok(CellBytes::new(num.to_string())),
-            Err(_) => Ok(CellBytes::default()),
-        }
-    }
-
-    fn apply_changeset(
-        &self,
-        changeset: CellDataChangeset<String>,
-        _cell_rev: Option<CellRevision>,
-    ) -> Result<String, FlowyError> {
-        let changeset = changeset.try_into_inner()?;
-        let data = changeset.trim().to_string();
-        let _ = self.format_cell_data(&data)?;
-        Ok(data)
-    }
-}
-
-impl std::default::Default for NumberTypeOption {
-    fn default() -> Self {
-        let format = NumberFormat::default();
-        let symbol = format.symbol();
-        NumberTypeOption {
-            format,
-            scale: 0,
-            symbol,
-            sign_positive: true,
-            name: "Number".to_string(),
-        }
-    }
-}
-
-#[derive(Default)]
-pub struct NumberCellData {
-    decimal: Option<Decimal>,
-    money: Option<String>,
-}
-
-impl NumberCellData {
-    pub fn new() -> Self {
-        Self {
-            decimal: Default::default(),
-            money: None,
-        }
-    }
-
-    pub fn from_format_str(s: &str, sign_positive: bool, format: &NumberFormat) -> FlowyResult<Self> {
-        let mut num_str = strip_currency_symbol(s);
-        let currency = format.currency();
-        if num_str.is_empty() {
-            return Ok(Self::default());
-        }
-        match Decimal::from_str(&num_str) {
-            Ok(mut decimal) => {
-                decimal.set_sign_positive(sign_positive);
-                let money = Money::from_decimal(decimal, currency);
-                Ok(Self::from_money(money))
-            }
-            Err(_) => match Money::from_str(&num_str, currency) {
-                Ok(money) => Ok(NumberCellData::from_money(money)),
-                Err(_) => {
-                    num_str.retain(|c| !STRIP_SYMBOL.contains(&c.to_string()));
-                    if num_str.chars().all(char::is_numeric) {
-                        Self::from_format_str(&num_str, sign_positive, format)
-                    } else {
-                        Err(FlowyError::invalid_data().context("Should only contain numbers"))
-                    }
-                }
-            },
-        }
-    }
-
-    pub fn from_decimal(decimal: Decimal) -> Self {
-        Self {
-            decimal: Some(decimal),
-            money: None,
-        }
-    }
-
-    pub fn from_money(money: Money<Currency>) -> Self {
-        Self {
-            decimal: Some(*money.amount()),
-            money: Some(money.to_string()),
-        }
-    }
-
-    pub fn decimal(&self) -> &Option<Decimal> {
-        &self.decimal
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.decimal.is_none()
-    }
-}
-
-impl FromStr for NumberCellData {
-    type Err = rust_decimal::Error;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        if s.is_empty() {
-            return Ok(Self::default());
-        }
-        let decimal = Decimal::from_str(s)?;
-        Ok(Self::from_decimal(decimal))
-    }
-}
-
-impl ToString for NumberCellData {
-    fn to_string(&self) -> String {
-        match &self.money {
-            None => match self.decimal {
-                None => String::default(),
-                Some(decimal) => decimal.to_string(),
-            },
-            Some(money) => money.to_string(),
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::entities::FieldType;
-    use crate::services::cell::CellDataOperation;
-    use crate::services::field::FieldBuilder;
-    use crate::services::field::{strip_currency_symbol, NumberFormat, NumberTypeOption};
-    use flowy_grid_data_model::revision::FieldRevision;
-    use strum::IntoEnumIterator;
-
-    #[test]
-    fn number_type_option_invalid_input_test() {
-        let type_option = NumberTypeOption::default();
-        let field_type = FieldType::Number;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-        assert_equal(&type_option, "", "", &field_type, &field_rev);
-        assert_equal(&type_option, "abc", "", &field_type, &field_rev);
-    }
-
-    #[test]
-    fn number_type_option_strip_symbol_test() {
-        let mut type_option = NumberTypeOption::new();
-        type_option.format = NumberFormat::USD;
-        assert_eq!(strip_currency_symbol("$18,443"), "18,443".to_owned());
-
-        type_option.format = NumberFormat::Yuan;
-        assert_eq!(strip_currency_symbol("$0.2"), "0.2".to_owned());
-    }
-
-    #[test]
-    fn number_type_option_format_number_test() {
-        let mut type_option = NumberTypeOption::default();
-        let field_type = FieldType::Number;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-
-        for format in NumberFormat::iter() {
-            type_option.format = format;
-            match format {
-                NumberFormat::Num => {
-                    assert_equal(&type_option, "18443", "18443", &field_type, &field_rev);
-                }
-                NumberFormat::USD => {
-                    assert_equal(&type_option, "18443", "$18,443", &field_type, &field_rev);
-                }
-                NumberFormat::Yen => {
-                    assert_equal(&type_option, "18443", "¥18,443", &field_type, &field_rev);
-                }
-                NumberFormat::Yuan => {
-                    assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_rev);
-                }
-                NumberFormat::EUR => {
-                    assert_equal(&type_option, "18443", "€18.443", &field_type, &field_rev);
-                }
-                _ => {}
-            }
-        }
-    }
-
-    #[test]
-    fn number_type_option_format_str_test() {
-        let mut type_option = NumberTypeOption::default();
-        let field_type = FieldType::Number;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-
-        for format in NumberFormat::iter() {
-            type_option.format = format;
-            match format {
-                NumberFormat::Num => {
-                    assert_equal(&type_option, "18443", "18443", &field_type, &field_rev);
-                    assert_equal(&type_option, "0.2", "0.2", &field_type, &field_rev);
-                }
-                NumberFormat::USD => {
-                    assert_equal(&type_option, "$18,44", "$1,844", &field_type, &field_rev);
-                    assert_equal(&type_option, "$0.2", "$0.2", &field_type, &field_rev);
-                    assert_equal(&type_option, "", "", &field_type, &field_rev);
-                    assert_equal(&type_option, "abc", "", &field_type, &field_rev);
-                }
-                NumberFormat::Yen => {
-                    assert_equal(&type_option, "¥18,44", "¥1,844", &field_type, &field_rev);
-                    assert_equal(&type_option, "¥1844", "¥1,844", &field_type, &field_rev);
-                }
-                NumberFormat::Yuan => {
-                    assert_equal(&type_option, "CN¥18,44", "CN¥1,844", &field_type, &field_rev);
-                    assert_equal(&type_option, "CN¥1844", "CN¥1,844", &field_type, &field_rev);
-                }
-                NumberFormat::EUR => {
-                    assert_equal(&type_option, "€18.44", "€18,44", &field_type, &field_rev);
-                    assert_equal(&type_option, "€0.5", "€0,5", &field_type, &field_rev);
-                    assert_equal(&type_option, "€1844", "€1.844", &field_type, &field_rev);
-                }
-                _ => {}
-            }
-        }
-    }
-
-    #[test]
-    fn number_description_sign_test() {
-        let mut type_option = NumberTypeOption {
-            sign_positive: false,
-            ..Default::default()
-        };
-        let field_type = FieldType::Number;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-
-        for format in NumberFormat::iter() {
-            type_option.format = format;
-            match format {
-                NumberFormat::Num => {
-                    assert_equal(&type_option, "18443", "18443", &field_type, &field_rev);
-                }
-                NumberFormat::USD => {
-                    assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_rev);
-                }
-                NumberFormat::Yen => {
-                    assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_rev);
-                }
-                NumberFormat::EUR => {
-                    assert_equal(&type_option, "18443", "-€18.443", &field_type, &field_rev);
-                }
-                _ => {}
-            }
-        }
-    }
-
-    fn assert_equal(
-        type_option: &NumberTypeOption,
-        cell_data: &str,
-        expected_str: &str,
-        field_type: &FieldType,
-        field_rev: &FieldRevision,
-    ) {
-        assert_eq!(
-            type_option
-                .decode_cell_data(cell_data.to_owned().into(), field_type, field_rev)
-                .unwrap()
-                .to_string(),
-            expected_str.to_owned()
-        );
-    }
-}

+ 139 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/tests.rs

@@ -0,0 +1,139 @@
+#[cfg(test)]
+mod tests {
+    use crate::entities::FieldType;
+    use crate::services::cell::CellDataOperation;
+    use crate::services::field::FieldBuilder;
+    use crate::services::field::{strip_currency_symbol, NumberFormat, NumberTypeOption};
+    use flowy_grid_data_model::revision::FieldRevision;
+    use strum::IntoEnumIterator;
+
+    #[test]
+    fn number_type_option_invalid_input_test() {
+        let type_option = NumberTypeOption::default();
+        let field_type = FieldType::Number;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+        assert_equal(&type_option, "", "", &field_type, &field_rev);
+        assert_equal(&type_option, "abc", "", &field_type, &field_rev);
+    }
+
+    #[test]
+    fn number_type_option_strip_symbol_test() {
+        let mut type_option = NumberTypeOption::new();
+        type_option.format = NumberFormat::USD;
+        assert_eq!(strip_currency_symbol("$18,443"), "18,443".to_owned());
+
+        type_option.format = NumberFormat::Yuan;
+        assert_eq!(strip_currency_symbol("$0.2"), "0.2".to_owned());
+    }
+
+    #[test]
+    fn number_type_option_format_number_test() {
+        let mut type_option = NumberTypeOption::default();
+        let field_type = FieldType::Number;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+
+        for format in NumberFormat::iter() {
+            type_option.format = format;
+            match format {
+                NumberFormat::Num => {
+                    assert_equal(&type_option, "18443", "18443", &field_type, &field_rev);
+                }
+                NumberFormat::USD => {
+                    assert_equal(&type_option, "18443", "$18,443", &field_type, &field_rev);
+                }
+                NumberFormat::Yen => {
+                    assert_equal(&type_option, "18443", "¥18,443", &field_type, &field_rev);
+                }
+                NumberFormat::Yuan => {
+                    assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_rev);
+                }
+                NumberFormat::EUR => {
+                    assert_equal(&type_option, "18443", "€18.443", &field_type, &field_rev);
+                }
+                _ => {}
+            }
+        }
+    }
+
+    #[test]
+    fn number_type_option_format_str_test() {
+        let mut type_option = NumberTypeOption::default();
+        let field_type = FieldType::Number;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+
+        for format in NumberFormat::iter() {
+            type_option.format = format;
+            match format {
+                NumberFormat::Num => {
+                    assert_equal(&type_option, "18443", "18443", &field_type, &field_rev);
+                    assert_equal(&type_option, "0.2", "0.2", &field_type, &field_rev);
+                }
+                NumberFormat::USD => {
+                    assert_equal(&type_option, "$18,44", "$1,844", &field_type, &field_rev);
+                    assert_equal(&type_option, "$0.2", "$0.2", &field_type, &field_rev);
+                    assert_equal(&type_option, "", "", &field_type, &field_rev);
+                    assert_equal(&type_option, "abc", "", &field_type, &field_rev);
+                }
+                NumberFormat::Yen => {
+                    assert_equal(&type_option, "¥18,44", "¥1,844", &field_type, &field_rev);
+                    assert_equal(&type_option, "¥1844", "¥1,844", &field_type, &field_rev);
+                }
+                NumberFormat::Yuan => {
+                    assert_equal(&type_option, "CN¥18,44", "CN¥1,844", &field_type, &field_rev);
+                    assert_equal(&type_option, "CN¥1844", "CN¥1,844", &field_type, &field_rev);
+                }
+                NumberFormat::EUR => {
+                    assert_equal(&type_option, "€18.44", "€18,44", &field_type, &field_rev);
+                    assert_equal(&type_option, "€0.5", "€0,5", &field_type, &field_rev);
+                    assert_equal(&type_option, "€1844", "€1.844", &field_type, &field_rev);
+                }
+                _ => {}
+            }
+        }
+    }
+
+    #[test]
+    fn number_description_sign_test() {
+        let mut type_option = NumberTypeOption {
+            sign_positive: false,
+            ..Default::default()
+        };
+        let field_type = FieldType::Number;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+
+        for format in NumberFormat::iter() {
+            type_option.format = format;
+            match format {
+                NumberFormat::Num => {
+                    assert_equal(&type_option, "18443", "18443", &field_type, &field_rev);
+                }
+                NumberFormat::USD => {
+                    assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_rev);
+                }
+                NumberFormat::Yen => {
+                    assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_rev);
+                }
+                NumberFormat::EUR => {
+                    assert_equal(&type_option, "18443", "-€18.443", &field_type, &field_rev);
+                }
+                _ => {}
+            }
+        }
+    }
+
+    fn assert_equal(
+        type_option: &NumberTypeOption,
+        cell_data: &str,
+        expected_str: &str,
+        field_type: &FieldType,
+        field_rev: &FieldRevision,
+    ) {
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data.to_owned().into(), field_type, field_rev)
+                .unwrap()
+                .to_string(),
+            expected_str.to_owned()
+        );
+    }
+}

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

@@ -0,0 +1,7 @@
+mod multi_select_type_option;
+mod select_option;
+mod single_select_type_option;
+
+pub use multi_select_type_option::*;
+pub use select_option::*;
+pub use single_select_type_option::*;

+ 6 - 11
frontend/rust-lib/flowy-grid/src/services/field/type_options/multi_select_type_option.rs → frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs

@@ -1,19 +1,15 @@
 use crate::entities::FieldType;
-
 use crate::impl_type_option;
 use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
-use crate::services::field::select_option::{
-    make_selected_select_options, SelectOption, SelectOptionCellChangeset, SelectOptionCellData, SelectOptionIds,
-    SelectOptionOperation, SELECTION_IDS_SEPARATOR,
-};
 use crate::services::field::type_options::util::get_cell_data;
-use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
+use crate::services::field::{
+    make_selected_select_options, BoxTypeOptionBuilder, SelectOption, SelectOptionCellChangeset, SelectOptionCellData,
+    SelectOptionIds, SelectOptionOperation, TypeOptionBuilder, SELECTION_IDS_SEPARATOR,
+};
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::{FlowyError, FlowyResult};
-
 use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
-
 use serde::{Deserialize, Serialize};
 
 // Multiple select
@@ -56,8 +52,7 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for MultiSele
             return Ok(CellBytes::default());
         }
 
-        let cell_data = self.display_data(cell_data, decoded_field_type, field_rev)?;
-        CellBytes::from(cell_data)
+        self.display_data(cell_data, decoded_field_type, field_rev)
     }
 
     fn apply_changeset(
@@ -121,7 +116,7 @@ impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
 mod tests {
     use crate::entities::FieldType;
     use crate::services::cell::CellDataOperation;
-    use crate::services::field::select_option::*;
+    use crate::services::field::type_options::selection_type_option::*;
     use crate::services::field::FieldBuilder;
     use crate::services::field::{MultiSelectTypeOption, MultiSelectTypeOptionBuilder};
     use flowy_grid_data_model::revision::FieldRevision;

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

@@ -1,5 +1,5 @@
 use crate::entities::{CellChangeset, CellIdentifier, CellIdentifierPayload, FieldType};
-use crate::services::cell::{AnyCellData, CellData, CellDisplayable, FromCellChangeset, FromCellString};
+use crate::services::cell::{AnyCellData, CellBytes, CellData, CellDisplayable, FromCellChangeset, FromCellString};
 use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
@@ -106,7 +106,7 @@ pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync {
     fn mut_options(&mut self) -> &mut Vec<SelectOption>;
 }
 
-impl<T> CellDisplayable<SelectOptionIds, SelectOptionCellData> for T
+impl<T> CellDisplayable<SelectOptionIds> for T
 where
     T: SelectOptionOperation,
 {
@@ -115,8 +115,8 @@ where
         cell_data: CellData<SelectOptionIds>,
         _decoded_field_type: &FieldType,
         _field_rev: &FieldRevision,
-    ) -> FlowyResult<SelectOptionCellData> {
-        Ok(self.selected_select_option(cell_data))
+    ) -> FlowyResult<CellBytes> {
+        CellBytes::from(self.selected_select_option(cell_data))
     }
 }
 

+ 3 - 4
frontend/rust-lib/flowy-grid/src/services/field/type_options/single_select_type_option.rs → frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs

@@ -1,7 +1,7 @@
 use crate::entities::FieldType;
 use crate::impl_type_option;
 use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
-use crate::services::field::select_option::{
+use crate::services::field::{
     make_selected_select_options, SelectOption, SelectOptionCellChangeset, SelectOptionCellData, SelectOptionIds,
     SelectOptionOperation,
 };
@@ -54,8 +54,7 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for SingleSel
             return Ok(CellBytes::default());
         }
 
-        let cell_data = self.display_data(cell_data, decoded_field_type, field_rev)?;
-        CellBytes::from(cell_data)
+        self.display_data(cell_data, decoded_field_type, field_rev)
     }
 
     fn apply_changeset(
@@ -103,7 +102,7 @@ impl TypeOptionBuilder for SingleSelectTypeOptionBuilder {
 mod tests {
     use crate::entities::FieldType;
     use crate::services::cell::CellDataOperation;
-    use crate::services::field::select_option::*;
+    
     use crate::services::field::type_options::*;
     use crate::services::field::FieldBuilder;
     use flowy_grid_data_model::revision::FieldRevision;

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

@@ -0,0 +1,2 @@
+mod text_option;
+pub use text_option::*;

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

@@ -32,15 +32,15 @@ pub struct RichTextTypeOption {
 }
 impl_type_option!(RichTextTypeOption, FieldType::RichText);
 
-impl CellDisplayable<String, String> for RichTextTypeOption {
+impl CellDisplayable<String> for RichTextTypeOption {
     fn display_data(
         &self,
         cell_data: CellData<String>,
         _decoded_field_type: &FieldType,
         _field_rev: &FieldRevision,
-    ) -> FlowyResult<String> {
+    ) -> FlowyResult<CellBytes> {
         let cell_str: String = cell_data.try_into_inner()?;
-        Ok(cell_str)
+        Ok(CellBytes::new(cell_str))
     }
 }
 
@@ -58,8 +58,7 @@ impl CellDataOperation<String, String> for RichTextTypeOption {
         {
             try_decode_cell_data(cell_data, field_rev, decoded_field_type, decoded_field_type)
         } else {
-            let content = self.display_data(cell_data, decoded_field_type, field_rev)?;
-            Ok(CellBytes::new(content))
+            self.display_data(cell_data, decoded_field_type, field_rev)
         }
     }
 
@@ -96,7 +95,7 @@ impl std::convert::TryFrom<AnyCellData> for TextCellData {
 mod tests {
     use crate::entities::FieldType;
     use crate::services::cell::CellDataOperation;
-    use crate::services::field::select_option::*;
+    
     use crate::services::field::FieldBuilder;
     use crate::services::field::*;
 

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

@@ -1,206 +0,0 @@
-use crate::entities::FieldType;
-use crate::impl_type_option;
-use crate::services::cell::{
-    AnyCellData, CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable, FromCellString,
-};
-use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use bytes::Bytes;
-use fancy_regex::Regex;
-use flowy_derive::ProtoBuf;
-use flowy_error::{internal_error, FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
-use lazy_static::lazy_static;
-use serde::{Deserialize, Serialize};
-
-#[derive(Default)]
-pub struct URLTypeOptionBuilder(URLTypeOption);
-impl_into_box_type_option_builder!(URLTypeOptionBuilder);
-impl_builder_from_json_str_and_from_bytes!(URLTypeOptionBuilder, URLTypeOption);
-
-impl TypeOptionBuilder for URLTypeOptionBuilder {
-    fn field_type(&self) -> FieldType {
-        FieldType::URL
-    }
-
-    fn entry(&self) -> &dyn TypeOptionDataEntry {
-        &self.0
-    }
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
-pub struct URLTypeOption {
-    #[pb(index = 1)]
-    data: String, //It's not used yet.
-}
-impl_type_option!(URLTypeOption, FieldType::URL);
-
-impl CellDisplayable<URLCellData, URLCellData> for URLTypeOption {
-    fn display_data(
-        &self,
-        cell_data: CellData<URLCellData>,
-        _decoded_field_type: &FieldType,
-        _field_rev: &FieldRevision,
-    ) -> FlowyResult<URLCellData> {
-        let cell_data: URLCellData = cell_data.try_into_inner()?;
-        Ok(cell_data)
-    }
-}
-
-impl CellDataOperation<URLCellData, String> for URLTypeOption {
-    fn decode_cell_data(
-        &self,
-        cell_data: CellData<URLCellData>,
-        decoded_field_type: &FieldType,
-        field_rev: &FieldRevision,
-    ) -> FlowyResult<CellBytes> {
-        if !decoded_field_type.is_url() {
-            return Ok(CellBytes::default());
-        }
-        let cell_data = self.display_data(cell_data, decoded_field_type, field_rev)?;
-        CellBytes::from(cell_data)
-    }
-
-    fn apply_changeset(
-        &self,
-        changeset: CellDataChangeset<String>,
-        _cell_rev: Option<CellRevision>,
-    ) -> Result<String, FlowyError> {
-        let changeset = changeset.try_into_inner()?;
-        let mut url = "".to_string();
-        if let Ok(Some(m)) = URL_REGEX.find(&changeset) {
-            url = auto_append_scheme(m.as_str());
-        }
-        URLCellData {
-            url,
-            content: changeset,
-        }
-        .to_json()
-    }
-}
-
-fn auto_append_scheme(s: &str) -> String {
-    // Only support https scheme by now
-    match url::Url::parse(s) {
-        Ok(url) => {
-            if url.scheme() == "https" {
-                url.into()
-            } else {
-                format!("https://{}", s)
-            }
-        }
-        Err(_) => {
-            format!("https://{}", s)
-        }
-    }
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct URLCellData {
-    #[pb(index = 1)]
-    pub url: String,
-
-    #[pb(index = 2)]
-    pub content: String,
-}
-
-impl URLCellData {
-    pub fn new(s: &str) -> Self {
-        Self {
-            url: "".to_string(),
-            content: s.to_string(),
-        }
-    }
-
-    fn to_json(&self) -> FlowyResult<String> {
-        serde_json::to_string(self).map_err(internal_error)
-    }
-}
-
-impl FromCellString for URLCellData {
-    fn from_cell_str(s: &str) -> FlowyResult<Self> {
-        serde_json::from_str::<URLCellData>(s).map_err(internal_error)
-    }
-}
-
-impl std::convert::TryFrom<AnyCellData> for URLCellData {
-    type Error = FlowyError;
-
-    fn try_from(data: AnyCellData) -> Result<Self, Self::Error> {
-        serde_json::from_str::<URLCellData>(&data.data).map_err(internal_error)
-    }
-}
-
-lazy_static! {
-    static ref URL_REGEX: Regex = Regex::new(
-        "[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)"
-    )
-    .unwrap();
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::entities::FieldType;
-    use crate::services::cell::{CellData, CellDataOperation};
-    use crate::services::field::FieldBuilder;
-    use crate::services::field::{URLCellData, URLTypeOption};
-    use flowy_grid_data_model::revision::FieldRevision;
-
-    #[test]
-    fn url_type_option_test_no_url() {
-        let type_option = URLTypeOption::default();
-        let field_type = FieldType::URL;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-        assert_changeset(&type_option, "123", &field_type, &field_rev, "123", "");
-    }
-
-    #[test]
-    fn url_type_option_test_contains_url() {
-        let type_option = URLTypeOption::default();
-        let field_type = FieldType::URL;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-        assert_changeset(
-            &type_option,
-            "AppFlowy website - https://www.appflowy.io",
-            &field_type,
-            &field_rev,
-            "AppFlowy website - https://www.appflowy.io",
-            "https://www.appflowy.io/",
-        );
-
-        assert_changeset(
-            &type_option,
-            "AppFlowy website appflowy.io",
-            &field_type,
-            &field_rev,
-            "AppFlowy website appflowy.io",
-            "https://appflowy.io",
-        );
-    }
-
-    fn assert_changeset(
-        type_option: &URLTypeOption,
-        cell_data: &str,
-        field_type: &FieldType,
-        field_rev: &FieldRevision,
-        expected: &str,
-        expected_url: &str,
-    ) {
-        let encoded_data = type_option.apply_changeset(cell_data.to_owned().into(), None).unwrap();
-        let decode_cell_data = decode_cell_data(encoded_data, type_option, field_rev, field_type);
-        assert_eq!(expected.to_owned(), decode_cell_data.content);
-        assert_eq!(expected_url.to_owned(), decode_cell_data.url);
-    }
-
-    fn decode_cell_data<T: Into<CellData<URLCellData>>>(
-        encoded_data: T,
-        type_option: &URLTypeOption,
-        field_rev: &FieldRevision,
-        field_type: &FieldType,
-    ) -> URLCellData {
-        type_option
-            .decode_cell_data(encoded_data.into(), field_type, field_rev)
-            .unwrap()
-            .parse::<URLCellData>()
-            .unwrap()
-    }
-}

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

@@ -0,0 +1,6 @@
+mod tests;
+mod url_option;
+mod url_option_entities;
+
+pub use url_option::*;
+pub use url_option_entities::*;

+ 67 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/tests.rs

@@ -0,0 +1,67 @@
+#[cfg(test)]
+mod tests {
+    use crate::entities::FieldType;
+    use crate::services::cell::{CellData, CellDataOperation};
+    use crate::services::field::FieldBuilder;
+    use crate::services::field::{URLCellData, URLTypeOption};
+    use flowy_grid_data_model::revision::FieldRevision;
+
+    #[test]
+    fn url_type_option_test_no_url() {
+        let type_option = URLTypeOption::default();
+        let field_type = FieldType::URL;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+        assert_changeset(&type_option, "123", &field_type, &field_rev, "123", "");
+    }
+
+    #[test]
+    fn url_type_option_test_contains_url() {
+        let type_option = URLTypeOption::default();
+        let field_type = FieldType::URL;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+        assert_changeset(
+            &type_option,
+            "AppFlowy website - https://www.appflowy.io",
+            &field_type,
+            &field_rev,
+            "AppFlowy website - https://www.appflowy.io",
+            "https://www.appflowy.io/",
+        );
+
+        assert_changeset(
+            &type_option,
+            "AppFlowy website appflowy.io",
+            &field_type,
+            &field_rev,
+            "AppFlowy website appflowy.io",
+            "https://appflowy.io",
+        );
+    }
+
+    fn assert_changeset(
+        type_option: &URLTypeOption,
+        cell_data: &str,
+        field_type: &FieldType,
+        field_rev: &FieldRevision,
+        expected: &str,
+        expected_url: &str,
+    ) {
+        let encoded_data = type_option.apply_changeset(cell_data.to_owned().into(), None).unwrap();
+        let decode_cell_data = decode_cell_data(encoded_data, type_option, field_rev, field_type);
+        assert_eq!(expected.to_owned(), decode_cell_data.content);
+        assert_eq!(expected_url.to_owned(), decode_cell_data.url);
+    }
+
+    fn decode_cell_data<T: Into<CellData<URLCellData>>>(
+        encoded_data: T,
+        type_option: &URLTypeOption,
+        field_rev: &FieldRevision,
+        field_type: &FieldType,
+    ) -> URLCellData {
+        type_option
+            .decode_cell_data(encoded_data.into(), field_type, field_rev)
+            .unwrap()
+            .parse::<URLCellData>()
+            .unwrap()
+    }
+}

+ 101 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_option.rs

@@ -0,0 +1,101 @@
+use crate::entities::FieldType;
+use crate::impl_type_option;
+use crate::services::cell::{
+    AnyCellData, CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable, FromCellString,
+};
+use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder, URLCellData};
+use bytes::Bytes;
+use fancy_regex::Regex;
+use flowy_derive::ProtoBuf;
+use flowy_error::{internal_error, FlowyError, FlowyResult};
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
+use lazy_static::lazy_static;
+use serde::{Deserialize, Serialize};
+
+#[derive(Default)]
+pub struct URLTypeOptionBuilder(URLTypeOption);
+impl_into_box_type_option_builder!(URLTypeOptionBuilder);
+impl_builder_from_json_str_and_from_bytes!(URLTypeOptionBuilder, URLTypeOption);
+
+impl TypeOptionBuilder for URLTypeOptionBuilder {
+    fn field_type(&self) -> FieldType {
+        FieldType::URL
+    }
+
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
+pub struct URLTypeOption {
+    #[pb(index = 1)]
+    data: String, //It's not used yet.
+}
+impl_type_option!(URLTypeOption, FieldType::URL);
+
+impl CellDisplayable<URLCellData> for URLTypeOption {
+    fn display_data(
+        &self,
+        cell_data: CellData<URLCellData>,
+        _decoded_field_type: &FieldType,
+        _field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        let cell_data: URLCellData = cell_data.try_into_inner()?;
+        CellBytes::from(cell_data)
+    }
+}
+
+impl CellDataOperation<URLCellData, String> for URLTypeOption {
+    fn decode_cell_data(
+        &self,
+        cell_data: CellData<URLCellData>,
+        decoded_field_type: &FieldType,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        if !decoded_field_type.is_url() {
+            return Ok(CellBytes::default());
+        }
+        self.display_data(cell_data, decoded_field_type, field_rev)
+    }
+
+    fn apply_changeset(
+        &self,
+        changeset: CellDataChangeset<String>,
+        _cell_rev: Option<CellRevision>,
+    ) -> Result<String, FlowyError> {
+        let changeset = changeset.try_into_inner()?;
+        let mut url = "".to_string();
+        if let Ok(Some(m)) = URL_REGEX.find(&changeset) {
+            url = auto_append_scheme(m.as_str());
+        }
+        URLCellData {
+            url,
+            content: changeset,
+        }
+        .to_json()
+    }
+}
+
+fn auto_append_scheme(s: &str) -> String {
+    // Only support https scheme by now
+    match url::Url::parse(s) {
+        Ok(url) => {
+            if url.scheme() == "https" {
+                url.into()
+            } else {
+                format!("https://{}", s)
+            }
+        }
+        Err(_) => {
+            format!("https://{}", s)
+        }
+    }
+}
+
+lazy_static! {
+    static ref URL_REGEX: Regex = Regex::new(
+        "[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)"
+    )
+    .unwrap();
+}

+ 40 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_option_entities.rs

@@ -0,0 +1,40 @@
+use crate::services::cell::{AnyCellData, FromCellString};
+use flowy_derive::ProtoBuf;
+use flowy_error::{internal_error, FlowyError, FlowyResult};
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
+pub struct URLCellData {
+    #[pb(index = 1)]
+    pub url: String,
+
+    #[pb(index = 2)]
+    pub content: String,
+}
+
+impl URLCellData {
+    pub fn new(s: &str) -> Self {
+        Self {
+            url: "".to_string(),
+            content: s.to_string(),
+        }
+    }
+
+    pub(crate) fn to_json(&self) -> FlowyResult<String> {
+        serde_json::to_string(self).map_err(internal_error)
+    }
+}
+
+impl FromCellString for URLCellData {
+    fn from_cell_str(s: &str) -> FlowyResult<Self> {
+        serde_json::from_str::<URLCellData>(s).map_err(internal_error)
+    }
+}
+
+impl std::convert::TryFrom<AnyCellData> for URLCellData {
+    type Error = FlowyError;
+
+    fn try_from(data: AnyCellData) -> Result<Self, Self::Error> {
+        serde_json::from_str::<URLCellData>(&data.data).map_err(internal_error)
+    }
+}

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

@@ -1,4 +1,3 @@
 mod cell_data_util;
 
-pub use crate::services::field::select_option::*;
 pub use cell_data_util::*;

+ 2 - 2
frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs

@@ -2,8 +2,8 @@
 
 use crate::entities::{GridSelectOptionFilter, SelectOptionCondition};
 use crate::services::cell::{AnyCellData, CellFilterOperation};
-use crate::services::field::select_option::{SelectOptionOperation, SelectedSelectOptions};
 use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
+use crate::services::field::{SelectOptionOperation, SelectedSelectOptions};
 use flowy_error::FlowyResult;
 
 impl GridSelectOptionFilter {
@@ -64,7 +64,7 @@ impl CellFilterOperation<GridSelectOptionFilter> for SingleSelectTypeOption {
 mod tests {
     #![allow(clippy::all)]
     use crate::entities::{GridSelectOptionFilter, SelectOptionCondition};
-    use crate::services::field::select_option::{SelectOption, SelectedSelectOptions};
+    use crate::services::field::selection_type_option::{SelectOption, SelectedSelectOptions};
 
     #[test]
     fn select_option_filter_is_test() {

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

@@ -1,5 +1,5 @@
 use crate::services::cell::apply_cell_data_changeset;
-use crate::services::field::select_option::SelectOptionCellChangeset;
+use crate::services::field::SelectOptionCellChangeset;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::revision::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT};
 use indexmap::IndexMap;

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

@@ -1,5 +1,5 @@
 use flowy_grid::entities::FieldType;
-use flowy_grid::services::field::select_option::{SelectOption, SELECTION_IDS_SEPARATOR};
+use flowy_grid::services::field::selection_type_option::{SelectOption, SELECTION_IDS_SEPARATOR};
 use flowy_grid::services::field::{DateCellChangeset, MultiSelectTypeOption, SingleSelectTypeOption};
 use flowy_grid::services::row::RowRevisionBuilder;
 use flowy_grid_data_model::revision::{FieldRevision, RowRevision};

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

@@ -2,7 +2,7 @@ use crate::grid::cell_test::script::CellScript::*;
 use crate::grid::cell_test::script::GridCellTest;
 use crate::grid::field_test::util::make_date_cell_string;
 use flowy_grid::entities::{CellChangeset, FieldType};
-use flowy_grid::services::field::select_option::SelectOptionCellChangeset;
+use flowy_grid::services::field::selection_type_option::SelectOptionCellChangeset;
 use flowy_grid::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
 
 #[tokio::test]

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

@@ -1,7 +1,7 @@
 use crate::grid::field_test::script::FieldScript::*;
 use crate::grid::field_test::script::GridFieldTest;
 use crate::grid::field_test::util::*;
-use flowy_grid::services::field::select_option::SelectOption;
+use flowy_grid::services::field::selection_type_option::SelectOption;
 use flowy_grid::services::field::SingleSelectTypeOption;
 use flowy_grid_data_model::revision::TypeOptionDataEntry;
 use flowy_sync::entities::grid::FieldChangesetParams;

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

@@ -1,5 +1,5 @@
 use flowy_grid::entities::*;
-use flowy_grid::services::field::select_option::SelectOption;
+use flowy_grid::services::field::selection_type_option::SelectOption;
 use flowy_grid::services::field::*;
 use flowy_grid_data_model::revision::*;
 

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

@@ -3,7 +3,7 @@
 #![allow(unused_imports)]
 use bytes::Bytes;
 use flowy_grid::entities::*;
-use flowy_grid::services::field::select_option::SelectOption;
+use flowy_grid::services::field::SelectOption;
 use flowy_grid::services::field::*;
 use flowy_grid::services::grid_editor::{GridPadBuilder, GridRevisionEditor};
 use flowy_grid::services::row::{CreateRowRevisionPayload, RowRevisionBuilder};