Просмотр исходного кода

refactor: separate multi-select and single-select

appflowy 2 лет назад
Родитель
Сommit
e8e719b73f
27 измененных файлов с 850 добавлено и 700 удалено
  1. 1 0
      frontend/rust-lib/flowy-grid/Flowy.toml
  2. 28 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs
  3. 7 7
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs
  4. 7 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs
  5. 6 4
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs
  6. 1 1
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs
  7. 4 2
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  8. 1 0
      frontend/rust-lib/flowy-grid/src/services/field/mod.rs
  9. 316 0
      frontend/rust-lib/flowy-grid/src/services/field/select_option.rs
  10. 12 3
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs
  11. 4 1
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs
  12. 5 2
      frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs
  13. 216 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/multi_select_type_option.rs
  14. 7 3
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs
  15. 0 664
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs
  16. 200 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/single_select_type_option.rs
  17. 9 2
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs
  18. 8 3
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs
  19. 0 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs
  20. 4 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/util/mod.rs
  21. 4 0
      frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs
  22. 1 1
      frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs
  23. 2 1
      frontend/rust-lib/flowy-grid/tests/grid/cell_test.rs
  24. 2 1
      frontend/rust-lib/flowy-grid/tests/grid/field_test.rs
  25. 2 2
      frontend/rust-lib/flowy-grid/tests/grid/field_util.rs
  26. 2 3
      frontend/rust-lib/flowy-grid/tests/grid/row_test.rs
  27. 1 0
      frontend/rust-lib/flowy-grid/tests/grid/script.rs

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

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

+ 28 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs

@@ -1,3 +1,4 @@
+use crate::services::field::CheckboxCellData;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::revision::GridFilterRevision;
@@ -9,6 +10,16 @@ pub struct GridCheckboxFilter {
     pub condition: CheckboxCondition,
 }
 
+impl GridCheckboxFilter {
+    pub fn apply(&self, cell_data: &CheckboxCellData) -> bool {
+        let is_check = cell_data.is_check();
+        match self.condition {
+            CheckboxCondition::IsChecked => is_check,
+            CheckboxCondition::IsUnChecked => !is_check,
+        }
+    }
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
 #[repr(u8)]
 pub enum CheckboxCondition {
@@ -47,3 +58,20 @@ impl std::convert::From<Arc<GridFilterRevision>> for GridCheckboxFilter {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::entities::{CheckboxCondition, GridCheckboxFilter};
+    use crate::services::field::CheckboxCellData;
+
+    #[test]
+    fn checkbox_filter_is_check_test() {
+        let checkbox_filter = GridCheckboxFilter {
+            condition: CheckboxCondition::IsChecked,
+        };
+        for (value, r) in [("true", true), ("yes", true), ("false", false), ("no", false)] {
+            let data = CheckboxCellData(value.to_owned());
+            assert_eq!(checkbox_filter.apply(&data), r);
+        }
+    }
+}

+ 7 - 7
frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs

@@ -3,7 +3,7 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::revision::GridFilterRevision;
 use rust_decimal::prelude::Zero;
-use rust_decimal::{Decimal, Error};
+use rust_decimal::Decimal;
 use std::str::FromStr;
 use std::sync::Arc;
 
@@ -25,7 +25,7 @@ impl GridNumberFilter {
         let content = self.content.as_ref().unwrap();
         let zero_decimal = Decimal::zero();
         let cell_decimal = num_cell_data.decimal().as_ref().unwrap_or(&zero_decimal);
-        match Decimal::from_str(&content) {
+        match Decimal::from_str(content) {
             Ok(decimal) => match self.condition {
                 NumberFilterCondition::Equal => cell_decimal == &decimal,
                 NumberFilterCondition::NotEqual => cell_decimal != &decimal,
@@ -95,7 +95,7 @@ impl std::convert::From<Arc<GridFilterRevision>> for GridNumberFilter {
 #[cfg(test)]
 mod tests {
     use crate::entities::{GridNumberFilter, NumberFilterCondition};
-    use crate::services::field::number_currency::Currency;
+
     use crate::services::field::{NumberCellData, NumberFormat};
     use std::str::FromStr;
     #[test]
@@ -105,13 +105,13 @@ mod tests {
             content: Some("123".to_owned()),
         };
 
-        for (num_str, r) in vec![("123", true), ("1234", false), ("", false)] {
+        for (num_str, r) in [("123", true), ("1234", false), ("", false)] {
             let data = NumberCellData::from_str(num_str).unwrap();
             assert_eq!(number_filter.apply(&data), r);
         }
 
         let format = NumberFormat::USD;
-        for (num_str, r) in vec![("$123", true), ("1234", false), ("", false)] {
+        for (num_str, r) in [("$123", true), ("1234", false), ("", false)] {
             let data = NumberCellData::from_format_str(num_str, true, &format).unwrap();
             assert_eq!(number_filter.apply(&data), r);
         }
@@ -122,7 +122,7 @@ mod tests {
             condition: NumberFilterCondition::GreaterThan,
             content: Some("12".to_owned()),
         };
-        for (num_str, r) in vec![("123", true), ("10", false), ("30", true), ("", false)] {
+        for (num_str, r) in [("123", true), ("10", false), ("30", true), ("", false)] {
             let data = NumberCellData::from_str(num_str).unwrap();
             assert_eq!(number_filter.apply(&data), r);
         }
@@ -134,7 +134,7 @@ mod tests {
             condition: NumberFilterCondition::LessThan,
             content: Some("100".to_owned()),
         };
-        for (num_str, r) in vec![("12", true), ("1234", false), ("30", true), ("", true)] {
+        for (num_str, r) in [("12", true), ("1234", false), ("30", true), ("", true)] {
             let data = NumberCellData::from_str(num_str).unwrap();
             assert_eq!(number_filter.apply(&data), r);
         }

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

@@ -1,3 +1,4 @@
+use crate::services::field::select_option::SelectOptionIds;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::revision::GridFilterRevision;
@@ -12,6 +13,12 @@ pub struct GridSelectOptionFilter {
     pub content: Option<String>,
 }
 
+impl GridSelectOptionFilter {
+    pub fn apply(&self, _ids: &SelectOptionIds) -> bool {
+        false
+    }
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
 #[repr(u8)]
 pub enum SelectOptionCondition {

+ 6 - 4
frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs

@@ -13,8 +13,9 @@ pub struct GridTextFilter {
 }
 
 impl GridTextFilter {
-    pub fn apply(&self, s: &str) -> bool {
-        let s = s.to_lowercase();
+    pub fn apply<T: AsRef<str>>(&self, cell_data: T) -> bool {
+        let cell_data = cell_data.as_ref();
+        let s = cell_data.to_lowercase();
         if let Some(content) = self.content.as_ref() {
             match self.condition {
                 TextFilterCondition::Is => &s == content,
@@ -27,7 +28,7 @@ impl GridTextFilter {
                 TextFilterCondition::TextIsNotEmpty => !s.is_empty(),
             }
         } else {
-            return false;
+            false
         }
     }
 }
@@ -85,6 +86,7 @@ impl std::convert::From<Arc<GridFilterRevision>> for GridTextFilter {
 
 #[cfg(test)]
 mod tests {
+    #![allow(clippy::all)]
     use crate::entities::{GridTextFilter, TextFilterCondition};
 
     #[test]
@@ -94,7 +96,7 @@ mod tests {
             content: Some("appflowy".to_owned()),
         };
 
-        assert_eq!(text_filter.apply("AppFlowy"), true);
+        assert!(text_filter.apply("AppFlowy"));
         assert_eq!(text_filter.apply("appflowy"), true);
         assert_eq!(text_filter.apply("Appflowy"), true);
         assert_eq!(text_filter.apply("AppFlowy.io"), false);

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

@@ -2,7 +2,7 @@ use crate::entities::{
     CheckboxCondition, DateFilterCondition, FieldType, NumberFilterCondition, SelectOptionCondition,
     TextFilterCondition,
 };
-use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_derive::ProtoBuf;
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::parser::NotEmptyStr;
 use flowy_grid_data_model::revision::{FieldRevision, GridFilterRevision};

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

@@ -1,7 +1,9 @@
 use crate::entities::*;
 use crate::manager::GridManager;
-use crate::services::field::type_options::*;
-use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_json_str};
+use crate::services::field::select_option::*;
+use crate::services::field::{
+    default_type_option_builder_from_type, type_option_builder_from_json_str, DateChangesetParams, DateChangesetPayload,
+};
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_grid_data_model::revision::FieldRevision;
 use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams};

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

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

+ 316 - 0
frontend/rust-lib/flowy-grid/src/services/field/select_option.rs

@@ -0,0 +1,316 @@
+use crate::entities::{CellChangeset, CellIdentifier, CellIdentifierPayload, FieldType};
+use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
+use crate::services::row::AnyCellData;
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::{ErrorCode, FlowyError, FlowyResult};
+use flowy_grid_data_model::parser::NotEmptyStr;
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataEntry};
+use nanoid::nanoid;
+use serde::{Deserialize, Serialize};
+use std::str::FromStr;
+
+pub const SELECTION_IDS_SEPARATOR: &str = ",";
+
+#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, ProtoBuf)]
+pub struct SelectOption {
+    #[pb(index = 1)]
+    pub id: String,
+
+    #[pb(index = 2)]
+    pub name: String,
+
+    #[pb(index = 3)]
+    pub color: SelectOptionColor,
+}
+
+impl SelectOption {
+    pub fn new(name: &str) -> Self {
+        SelectOption {
+            id: nanoid!(4),
+            name: name.to_owned(),
+            color: SelectOptionColor::default(),
+        }
+    }
+
+    pub fn with_color(name: &str, color: SelectOptionColor) -> Self {
+        SelectOption {
+            id: nanoid!(4),
+            name: name.to_owned(),
+            color,
+        }
+    }
+}
+
+#[derive(ProtoBuf_Enum, PartialEq, Eq, Serialize, Deserialize, Debug, Clone)]
+#[repr(u8)]
+pub enum SelectOptionColor {
+    Purple = 0,
+    Pink = 1,
+    LightPink = 2,
+    Orange = 3,
+    Yellow = 4,
+    Lime = 5,
+    Green = 6,
+    Aqua = 7,
+    Blue = 8,
+}
+
+impl std::default::Default for SelectOptionColor {
+    fn default() -> Self {
+        SelectOptionColor::Purple
+    }
+}
+
+pub fn make_select_context_from(cell_rev: &Option<CellRevision>, options: &[SelectOption]) -> Vec<SelectOption> {
+    match cell_rev {
+        None => vec![],
+        Some(cell_rev) => {
+            if let Ok(type_option_cell_data) = AnyCellData::from_str(&cell_rev.data) {
+                select_option_ids(type_option_cell_data.cell_data)
+                    .into_iter()
+                    .flat_map(|option_id| options.iter().find(|option| option.id == option_id).cloned())
+                    .collect()
+            } else {
+                vec![]
+            }
+        }
+    }
+}
+
+pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync {
+    fn insert_option(&mut self, new_option: SelectOption) {
+        let options = self.mut_options();
+        if let Some(index) = options
+            .iter()
+            .position(|option| option.id == new_option.id || option.name == new_option.name)
+        {
+            options.remove(index);
+            options.insert(index, new_option);
+        } else {
+            options.insert(0, new_option);
+        }
+    }
+
+    fn delete_option(&mut self, delete_option: SelectOption) {
+        let options = self.mut_options();
+        if let Some(index) = options.iter().position(|option| option.id == delete_option.id) {
+            options.remove(index);
+        }
+    }
+
+    fn create_option(&self, name: &str) -> SelectOption {
+        let color = select_option_color_from_index(self.options().len());
+        SelectOption::with_color(name, color)
+    }
+
+    fn select_option_cell_data(&self, cell_rev: &Option<CellRevision>) -> SelectOptionCellData;
+
+    fn options(&self) -> &Vec<SelectOption>;
+
+    fn mut_options(&mut self) -> &mut Vec<SelectOption>;
+}
+
+pub fn select_option_operation(field_rev: &FieldRevision) -> FlowyResult<Box<dyn SelectOptionOperation>> {
+    let field_type: FieldType = field_rev.field_type_rev.into();
+    match &field_type {
+        FieldType::SingleSelect => {
+            let type_option = SingleSelectTypeOption::from(field_rev);
+            Ok(Box::new(type_option))
+        }
+        FieldType::MultiSelect => {
+            let type_option = MultiSelectTypeOption::from(field_rev);
+            Ok(Box::new(type_option))
+        }
+        ty => {
+            tracing::error!("Unsupported field type: {:?} for this handler", ty);
+            Err(ErrorCode::FieldInvalidOperation.into())
+        }
+    }
+}
+
+pub fn select_option_color_from_index(index: usize) -> SelectOptionColor {
+    match index % 8 {
+        0 => SelectOptionColor::Purple,
+        1 => SelectOptionColor::Pink,
+        2 => SelectOptionColor::LightPink,
+        3 => SelectOptionColor::Orange,
+        4 => SelectOptionColor::Yellow,
+        5 => SelectOptionColor::Lime,
+        6 => SelectOptionColor::Green,
+        7 => SelectOptionColor::Aqua,
+        8 => SelectOptionColor::Blue,
+        _ => SelectOptionColor::Purple,
+    }
+}
+pub struct SelectOptionIds(Vec<String>);
+impl std::convert::TryFrom<AnyCellData> for SelectOptionIds {
+    type Error = FlowyError;
+
+    fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
+        let ids = select_option_ids(value.cell_data);
+        Ok(Self(ids))
+    }
+}
+
+impl std::convert::From<String> for SelectOptionIds {
+    fn from(s: String) -> Self {
+        let ids = select_option_ids(s);
+        Self(ids)
+    }
+}
+
+impl std::ops::Deref for SelectOptionIds {
+    type Target = Vec<String>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl std::ops::DerefMut for SelectOptionIds {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+fn select_option_ids(data: String) -> Vec<String> {
+    data.split(SELECTION_IDS_SEPARATOR)
+        .map(|id| id.to_string())
+        .collect::<Vec<String>>()
+}
+
+#[derive(Clone, Debug, Default, ProtoBuf)]
+pub struct SelectOptionCellChangesetPayload {
+    #[pb(index = 1)]
+    pub cell_identifier: CellIdentifierPayload,
+
+    #[pb(index = 2, one_of)]
+    pub insert_option_id: Option<String>,
+
+    #[pb(index = 3, one_of)]
+    pub delete_option_id: Option<String>,
+}
+
+pub struct SelectOptionCellChangesetParams {
+    pub cell_identifier: CellIdentifier,
+    pub insert_option_id: Option<String>,
+    pub delete_option_id: Option<String>,
+}
+
+impl std::convert::From<SelectOptionCellChangesetParams> for CellChangeset {
+    fn from(params: SelectOptionCellChangesetParams) -> Self {
+        let changeset = SelectOptionCellContentChangeset {
+            insert_option_id: params.insert_option_id,
+            delete_option_id: params.delete_option_id,
+        };
+        let s = serde_json::to_string(&changeset).unwrap();
+        CellChangeset {
+            grid_id: params.cell_identifier.grid_id,
+            row_id: params.cell_identifier.row_id,
+            field_id: params.cell_identifier.field_id,
+            cell_content_changeset: Some(s),
+        }
+    }
+}
+
+impl TryInto<SelectOptionCellChangesetParams> for SelectOptionCellChangesetPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<SelectOptionCellChangesetParams, Self::Error> {
+        let cell_identifier: CellIdentifier = self.cell_identifier.try_into()?;
+        let insert_option_id = match self.insert_option_id {
+            None => None,
+            Some(insert_option_id) => Some(
+                NotEmptyStr::parse(insert_option_id)
+                    .map_err(|_| ErrorCode::OptionIdIsEmpty)?
+                    .0,
+            ),
+        };
+
+        let delete_option_id = match self.delete_option_id {
+            None => None,
+            Some(delete_option_id) => Some(
+                NotEmptyStr::parse(delete_option_id)
+                    .map_err(|_| ErrorCode::OptionIdIsEmpty)?
+                    .0,
+            ),
+        };
+
+        Ok(SelectOptionCellChangesetParams {
+            cell_identifier,
+            insert_option_id,
+            delete_option_id,
+        })
+    }
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+pub struct SelectOptionCellContentChangeset {
+    pub insert_option_id: Option<String>,
+    pub delete_option_id: Option<String>,
+}
+
+impl SelectOptionCellContentChangeset {
+    pub fn from_insert(option_id: &str) -> Self {
+        SelectOptionCellContentChangeset {
+            insert_option_id: Some(option_id.to_string()),
+            delete_option_id: None,
+        }
+    }
+
+    pub fn from_delete(option_id: &str) -> Self {
+        SelectOptionCellContentChangeset {
+            insert_option_id: None,
+            delete_option_id: Some(option_id.to_string()),
+        }
+    }
+
+    pub fn to_str(&self) -> String {
+        serde_json::to_string(self).unwrap()
+    }
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
+pub struct SelectOptionCellData {
+    #[pb(index = 1)]
+    pub options: Vec<SelectOption>,
+
+    #[pb(index = 2)]
+    pub select_options: Vec<SelectOption>,
+}
+
+#[derive(Clone, Debug, Default, ProtoBuf)]
+pub struct SelectOptionChangesetPayload {
+    #[pb(index = 1)]
+    pub cell_identifier: CellIdentifierPayload,
+
+    #[pb(index = 2, one_of)]
+    pub insert_option: Option<SelectOption>,
+
+    #[pb(index = 3, one_of)]
+    pub update_option: Option<SelectOption>,
+
+    #[pb(index = 4, one_of)]
+    pub delete_option: Option<SelectOption>,
+}
+
+pub struct SelectOptionChangeset {
+    pub cell_identifier: CellIdentifier,
+    pub insert_option: Option<SelectOption>,
+    pub update_option: Option<SelectOption>,
+    pub delete_option: Option<SelectOption>,
+}
+
+impl TryInto<SelectOptionChangeset> for SelectOptionChangesetPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<SelectOptionChangeset, Self::Error> {
+        let cell_identifier = self.cell_identifier.try_into()?;
+        Ok(SelectOptionChangeset {
+            cell_identifier,
+            insert_option: self.insert_option,
+            update_option: self.update_option,
+            delete_option: self.delete_option,
+        })
+    }
+}

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

@@ -44,8 +44,11 @@ const NO: &str = "No";
 
 impl CellFilterOperation<GridCheckboxFilter> for CheckboxTypeOption {
     fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridCheckboxFilter) -> FlowyResult<bool> {
+        if !any_cell_data.is_checkbox() {
+            return Ok(true);
+        }
         let checkbox_cell_data: CheckboxCellData = any_cell_data.try_into()?;
-        Ok(false)
+        Ok(filter.apply(&checkbox_cell_data))
     }
 }
 
@@ -97,11 +100,17 @@ fn string_to_bool(bool_str: &str) -> bool {
     }
 }
 
-pub struct CheckboxCellData(String);
+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> {
+    fn try_from(_value: AnyCellData) -> Result<Self, Self::Error> {
         todo!()
     }
 }

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

@@ -118,7 +118,10 @@ impl DateTypeOption {
 }
 
 impl CellFilterOperation<GridDateFilter> for DateTypeOption {
-    fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridDateFilter) -> FlowyResult<bool> {
+    fn apply_filter(&self, any_cell_data: AnyCellData, _filter: &GridDateFilter) -> FlowyResult<bool> {
+        if !any_cell_data.is_date() {
+            return Ok(true);
+        }
         Ok(false)
     }
 }

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

@@ -1,14 +1,17 @@
 mod checkbox_type_option;
 mod date_type_option;
+mod multi_select_type_option;
 mod number_type_option;
-mod selection_type_option;
+mod single_select_type_option;
 mod text_type_option;
 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 selection_type_option::*;
+pub use single_select_type_option::*;
 pub use text_type_option::*;
 pub use url_type_option::*;

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

@@ -0,0 +1,216 @@
+use crate::entities::{FieldType, GridSelectOptionFilter};
+
+use crate::impl_type_option;
+use crate::services::field::select_option::{
+    make_select_context_from, SelectOption, SelectOptionCellContentChangeset, 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::row::{
+    AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
+};
+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
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
+pub struct MultiSelectTypeOption {
+    #[pb(index = 1)]
+    pub options: Vec<SelectOption>,
+
+    #[pb(index = 2)]
+    pub disable_color: bool,
+}
+impl_type_option!(MultiSelectTypeOption, FieldType::MultiSelect);
+
+impl SelectOptionOperation for MultiSelectTypeOption {
+    fn select_option_cell_data(&self, cell_rev: &Option<CellRevision>) -> SelectOptionCellData {
+        let select_options = make_select_context_from(cell_rev, &self.options);
+        SelectOptionCellData {
+            options: self.options.clone(),
+            select_options,
+        }
+    }
+
+    fn options(&self) -> &Vec<SelectOption> {
+        &self.options
+    }
+
+    fn mut_options(&mut self) -> &mut Vec<SelectOption> {
+        &mut self.options
+    }
+}
+impl CellFilterOperation<GridSelectOptionFilter> for MultiSelectTypeOption {
+    fn apply_filter(&self, any_cell_data: AnyCellData, _filter: &GridSelectOptionFilter) -> FlowyResult<bool> {
+        if !any_cell_data.is_multi_select() {
+            return Ok(true);
+        }
+        let _ids: SelectOptionIds = any_cell_data.try_into()?;
+        Ok(false)
+    }
+}
+impl CellDataOperation<String> for MultiSelectTypeOption {
+    fn decode_cell_data<T>(
+        &self,
+        cell_data: T,
+        decoded_field_type: &FieldType,
+        _field_rev: &FieldRevision,
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<String>,
+    {
+        if !decoded_field_type.is_select_option() {
+            return Ok(DecodedCellData::default());
+        }
+
+        let encoded_data = cell_data.into();
+        let ids: SelectOptionIds = encoded_data.into();
+        let select_options = ids
+            .iter()
+            .flat_map(|option_id| self.options.iter().find(|option| &option.id == option_id).cloned())
+            .collect::<Vec<SelectOption>>();
+
+        let cell_data = SelectOptionCellData {
+            options: self.options.clone(),
+            select_options,
+        };
+
+        DecodedCellData::try_from_bytes(cell_data)
+    }
+
+    fn apply_changeset<T>(&self, changeset: T, cell_rev: Option<CellRevision>) -> Result<String, FlowyError>
+    where
+        T: Into<CellContentChangeset>,
+    {
+        let content_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset.into())?;
+        let new_cell_data: String;
+        match cell_rev {
+            None => {
+                new_cell_data = content_changeset.insert_option_id.unwrap_or_else(|| "".to_owned());
+            }
+            Some(cell_rev) => {
+                let cell_data = get_cell_data(&cell_rev);
+                let mut select_ids: SelectOptionIds = cell_data.into();
+                if let Some(insert_option_id) = content_changeset.insert_option_id {
+                    tracing::trace!("Insert multi select option: {}", &insert_option_id);
+                    if select_ids.contains(&insert_option_id) {
+                        select_ids.retain(|id| id != &insert_option_id);
+                    } else {
+                        select_ids.push(insert_option_id);
+                    }
+                }
+
+                if let Some(delete_option_id) = content_changeset.delete_option_id {
+                    tracing::trace!("Delete multi select option: {}", &delete_option_id);
+                    select_ids.retain(|id| id != &delete_option_id);
+                }
+
+                new_cell_data = select_ids.join(SELECTION_IDS_SEPARATOR);
+                tracing::trace!("Multi select cell data: {}", &new_cell_data);
+            }
+        }
+
+        Ok(new_cell_data)
+    }
+}
+
+#[derive(Default)]
+pub struct MultiSelectTypeOptionBuilder(MultiSelectTypeOption);
+impl_into_box_type_option_builder!(MultiSelectTypeOptionBuilder);
+impl_builder_from_json_str_and_from_bytes!(MultiSelectTypeOptionBuilder, MultiSelectTypeOption);
+impl MultiSelectTypeOptionBuilder {
+    pub fn option(mut self, opt: SelectOption) -> Self {
+        self.0.options.push(opt);
+        self
+    }
+}
+
+impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
+    fn field_type(&self) -> FieldType {
+        FieldType::MultiSelect
+    }
+
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
+    }
+}
+#[cfg(test)]
+mod tests {
+    use crate::entities::FieldType;
+    use crate::services::field::select_option::*;
+    use crate::services::field::FieldBuilder;
+    use crate::services::field::{MultiSelectTypeOption, MultiSelectTypeOptionBuilder};
+    use crate::services::row::CellDataOperation;
+    use flowy_grid_data_model::revision::FieldRevision;
+
+    #[test]
+    fn multi_select_test() {
+        let google_option = SelectOption::new("Google");
+        let facebook_option = SelectOption::new("Facebook");
+        let twitter_option = SelectOption::new("Twitter");
+        let multi_select = MultiSelectTypeOptionBuilder::default()
+            .option(google_option.clone())
+            .option(facebook_option.clone())
+            .option(twitter_option);
+
+        let field_rev = FieldBuilder::new(multi_select)
+            .name("Platform")
+            .visibility(true)
+            .build();
+
+        let type_option = MultiSelectTypeOption::from(&field_rev);
+
+        let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
+        let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
+        let cell_data = type_option.apply_changeset(data, None).unwrap();
+        assert_multi_select_options(
+            cell_data,
+            &type_option,
+            &field_rev,
+            vec![google_option.clone(), facebook_option],
+        );
+
+        let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
+        let cell_data = type_option.apply_changeset(data, None).unwrap();
+        assert_multi_select_options(cell_data, &type_option, &field_rev, vec![google_option]);
+
+        // Invalid option id
+        let cell_data = type_option
+            .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
+            .unwrap();
+        assert_multi_select_options(cell_data, &type_option, &field_rev, vec![]);
+
+        // Invalid option id
+        let cell_data = type_option
+            .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None)
+            .unwrap();
+        assert_multi_select_options(cell_data, &type_option, &field_rev, vec![]);
+
+        // Invalid changeset
+        assert!(type_option.apply_changeset("123", None).is_err());
+    }
+
+    fn assert_multi_select_options(
+        cell_data: String,
+        type_option: &MultiSelectTypeOption,
+        field_rev: &FieldRevision,
+        expected: Vec<SelectOption>,
+    ) {
+        let field_type: FieldType = field_rev.field_type_rev.into();
+        assert_eq!(
+            expected,
+            type_option
+                .decode_cell_data(cell_data, &field_type, field_rev)
+                .unwrap()
+                .parse::<SelectOptionCellData>()
+                .unwrap()
+                .select_options,
+        );
+    }
+}

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

@@ -11,8 +11,8 @@ 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::prelude::Zero;
-use rust_decimal::{Decimal, Error};
+
+use rust_decimal::Decimal;
 use rusty_money::Money;
 use serde::{Deserialize, Serialize};
 use std::str::FromStr;
@@ -107,6 +107,10 @@ pub(crate) fn strip_currency_symbol<T: ToString>(s: T) -> String {
 }
 impl CellFilterOperation<GridNumberFilter> for NumberTypeOption {
     fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridNumberFilter) -> FlowyResult<bool> {
+        if !any_cell_data.is_number() {
+            return Ok(true);
+        }
+
         let cell_data = any_cell_data.cell_data;
         let num_cell_data = self.format_cell_data(&cell_data)?;
 
@@ -209,7 +213,7 @@ impl NumberCellData {
 
     pub fn from_money(money: Money<Currency>) -> Self {
         Self {
-            decimal: Some(money.amount().clone()),
+            decimal: Some(*money.amount()),
             money: Some(money.to_string()),
         }
     }

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

@@ -1,664 +0,0 @@
-use crate::entities::{CellChangeset, FieldType, GridSelectOptionFilter};
-use crate::entities::{CellIdentifier, CellIdentifierPayload};
-use crate::impl_type_option;
-use crate::services::field::type_options::util::get_cell_data;
-use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{
-    AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
-};
-use bytes::Bytes;
-use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-use flowy_error::{ErrorCode, FlowyError, FlowyResult};
-use flowy_grid_data_model::parser::NotEmptyStr;
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
-use nanoid::nanoid;
-use serde::{Deserialize, Serialize};
-use std::str::FromStr;
-
-pub const SELECTION_IDS_SEPARATOR: &str = ",";
-
-pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync {
-    fn insert_option(&mut self, new_option: SelectOption) {
-        let options = self.mut_options();
-        if let Some(index) = options
-            .iter()
-            .position(|option| option.id == new_option.id || option.name == new_option.name)
-        {
-            options.remove(index);
-            options.insert(index, new_option);
-        } else {
-            options.insert(0, new_option);
-        }
-    }
-
-    fn delete_option(&mut self, delete_option: SelectOption) {
-        let options = self.mut_options();
-        if let Some(index) = options.iter().position(|option| option.id == delete_option.id) {
-            options.remove(index);
-        }
-    }
-
-    fn create_option(&self, name: &str) -> SelectOption {
-        let color = select_option_color_from_index(self.options().len());
-        SelectOption::with_color(name, color)
-    }
-
-    fn select_option_cell_data(&self, cell_rev: &Option<CellRevision>) -> SelectOptionCellData;
-
-    fn options(&self) -> &Vec<SelectOption>;
-
-    fn mut_options(&mut self) -> &mut Vec<SelectOption>;
-}
-
-pub fn select_option_operation(field_rev: &FieldRevision) -> FlowyResult<Box<dyn SelectOptionOperation>> {
-    let field_type: FieldType = field_rev.field_type_rev.into();
-    match &field_type {
-        FieldType::SingleSelect => {
-            let type_option = SingleSelectTypeOption::from(field_rev);
-            Ok(Box::new(type_option))
-        }
-        FieldType::MultiSelect => {
-            let type_option = MultiSelectTypeOption::from(field_rev);
-            Ok(Box::new(type_option))
-        }
-        ty => {
-            tracing::error!("Unsupported field type: {:?} for this handler", ty);
-            Err(ErrorCode::FieldInvalidOperation.into())
-        }
-    }
-}
-
-// Single select
-#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct SingleSelectTypeOption {
-    #[pb(index = 1)]
-    pub options: Vec<SelectOption>,
-
-    #[pb(index = 2)]
-    pub disable_color: bool,
-}
-impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect);
-
-impl SelectOptionOperation for SingleSelectTypeOption {
-    fn select_option_cell_data(&self, cell_rev: &Option<CellRevision>) -> SelectOptionCellData {
-        let select_options = make_select_context_from(cell_rev, &self.options);
-        SelectOptionCellData {
-            options: self.options.clone(),
-            select_options,
-        }
-    }
-
-    fn options(&self) -> &Vec<SelectOption> {
-        &self.options
-    }
-
-    fn mut_options(&mut self) -> &mut Vec<SelectOption> {
-        &mut self.options
-    }
-}
-
-impl CellFilterOperation<GridSelectOptionFilter> for SingleSelectTypeOption {
-    fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult<bool> {
-        let ids: SelectOptionIds = any_cell_data.try_into()?;
-        Ok(false)
-    }
-}
-
-impl CellDataOperation<String> for SingleSelectTypeOption {
-    fn decode_cell_data<T>(
-        &self,
-        cell_data: T,
-        decoded_field_type: &FieldType,
-        _field_rev: &FieldRevision,
-    ) -> FlowyResult<DecodedCellData>
-    where
-        T: Into<String>,
-    {
-        if !decoded_field_type.is_select_option() {
-            return Ok(DecodedCellData::default());
-        }
-
-        let encoded_data = cell_data.into();
-        let mut cell_data = SelectOptionCellData {
-            options: self.options.clone(),
-            select_options: vec![],
-        };
-        if let Some(option_id) = select_option_ids(encoded_data).first() {
-            if let Some(option) = self.options.iter().find(|option| &option.id == option_id) {
-                cell_data.select_options.push(option.clone());
-            }
-        }
-
-        DecodedCellData::try_from_bytes(cell_data)
-    }
-
-    fn apply_changeset<C>(&self, changeset: C, _cell_rev: Option<CellRevision>) -> Result<String, FlowyError>
-    where
-        C: Into<CellContentChangeset>,
-    {
-        let changeset = changeset.into();
-        let select_option_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset)?;
-        let new_cell_data: String;
-        if let Some(insert_option_id) = select_option_changeset.insert_option_id {
-            tracing::trace!("Insert single select option: {}", &insert_option_id);
-            new_cell_data = insert_option_id;
-        } else {
-            tracing::trace!("Delete single select option");
-            new_cell_data = "".to_string()
-        }
-
-        Ok(new_cell_data)
-    }
-}
-
-#[derive(Default)]
-pub struct SingleSelectTypeOptionBuilder(SingleSelectTypeOption);
-impl_into_box_type_option_builder!(SingleSelectTypeOptionBuilder);
-impl_builder_from_json_str_and_from_bytes!(SingleSelectTypeOptionBuilder, SingleSelectTypeOption);
-
-impl SingleSelectTypeOptionBuilder {
-    pub fn option(mut self, opt: SelectOption) -> Self {
-        self.0.options.push(opt);
-        self
-    }
-}
-
-impl TypeOptionBuilder for SingleSelectTypeOptionBuilder {
-    fn field_type(&self) -> FieldType {
-        FieldType::SingleSelect
-    }
-
-    fn entry(&self) -> &dyn TypeOptionDataEntry {
-        &self.0
-    }
-}
-
-// Multiple select
-#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct MultiSelectTypeOption {
-    #[pb(index = 1)]
-    pub options: Vec<SelectOption>,
-
-    #[pb(index = 2)]
-    pub disable_color: bool,
-}
-impl_type_option!(MultiSelectTypeOption, FieldType::MultiSelect);
-
-impl SelectOptionOperation for MultiSelectTypeOption {
-    fn select_option_cell_data(&self, cell_rev: &Option<CellRevision>) -> SelectOptionCellData {
-        let select_options = make_select_context_from(cell_rev, &self.options);
-        SelectOptionCellData {
-            options: self.options.clone(),
-            select_options,
-        }
-    }
-
-    fn options(&self) -> &Vec<SelectOption> {
-        &self.options
-    }
-
-    fn mut_options(&mut self) -> &mut Vec<SelectOption> {
-        &mut self.options
-    }
-}
-impl CellFilterOperation<GridSelectOptionFilter> for MultiSelectTypeOption {
-    fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult<bool> {
-        let ids: SelectOptionIds = any_cell_data.try_into()?;
-        Ok(false)
-    }
-}
-impl CellDataOperation<String> for MultiSelectTypeOption {
-    fn decode_cell_data<T>(
-        &self,
-        cell_data: T,
-        decoded_field_type: &FieldType,
-        _field_rev: &FieldRevision,
-    ) -> FlowyResult<DecodedCellData>
-    where
-        T: Into<String>,
-    {
-        if !decoded_field_type.is_select_option() {
-            return Ok(DecodedCellData::default());
-        }
-
-        let encoded_data = cell_data.into();
-        let select_options = select_option_ids(encoded_data)
-            .into_iter()
-            .flat_map(|option_id| self.options.iter().find(|option| option.id == option_id).cloned())
-            .collect::<Vec<SelectOption>>();
-
-        let cell_data = SelectOptionCellData {
-            options: self.options.clone(),
-            select_options,
-        };
-
-        DecodedCellData::try_from_bytes(cell_data)
-    }
-
-    fn apply_changeset<T>(&self, changeset: T, cell_rev: Option<CellRevision>) -> Result<String, FlowyError>
-    where
-        T: Into<CellContentChangeset>,
-    {
-        let content_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset.into())?;
-        let new_cell_data: String;
-        match cell_rev {
-            None => {
-                new_cell_data = content_changeset.insert_option_id.unwrap_or_else(|| "".to_owned());
-            }
-            Some(cell_rev) => {
-                let cell_data = get_cell_data(&cell_rev);
-                let mut selected_options = select_option_ids(cell_data);
-                if let Some(insert_option_id) = content_changeset.insert_option_id {
-                    tracing::trace!("Insert multi select option: {}", &insert_option_id);
-                    if selected_options.contains(&insert_option_id) {
-                        selected_options.retain(|id| id != &insert_option_id);
-                    } else {
-                        selected_options.push(insert_option_id);
-                    }
-                }
-
-                if let Some(delete_option_id) = content_changeset.delete_option_id {
-                    tracing::trace!("Delete multi select option: {}", &delete_option_id);
-                    selected_options.retain(|id| id != &delete_option_id);
-                }
-
-                new_cell_data = selected_options.join(SELECTION_IDS_SEPARATOR);
-                tracing::trace!("Multi select cell data: {}", &new_cell_data);
-            }
-        }
-
-        Ok(new_cell_data)
-    }
-}
-
-#[derive(Default)]
-pub struct MultiSelectTypeOptionBuilder(MultiSelectTypeOption);
-impl_into_box_type_option_builder!(MultiSelectTypeOptionBuilder);
-impl_builder_from_json_str_and_from_bytes!(MultiSelectTypeOptionBuilder, MultiSelectTypeOption);
-impl MultiSelectTypeOptionBuilder {
-    pub fn option(mut self, opt: SelectOption) -> Self {
-        self.0.options.push(opt);
-        self
-    }
-}
-
-impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
-    fn field_type(&self) -> FieldType {
-        FieldType::MultiSelect
-    }
-
-    fn entry(&self) -> &dyn TypeOptionDataEntry {
-        &self.0
-    }
-}
-
-pub struct SelectOptionIds(Vec<String>);
-impl std::convert::TryFrom<AnyCellData> for SelectOptionIds {
-    type Error = FlowyError;
-
-    fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
-        let ids = select_option_ids(value.cell_data);
-        Ok(Self(ids))
-    }
-}
-
-fn select_option_ids(data: String) -> Vec<String> {
-    data.split(SELECTION_IDS_SEPARATOR)
-        .map(|id| id.to_string())
-        .collect::<Vec<String>>()
-}
-
-#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, ProtoBuf)]
-pub struct SelectOption {
-    #[pb(index = 1)]
-    pub id: String,
-
-    #[pb(index = 2)]
-    pub name: String,
-
-    #[pb(index = 3)]
-    pub color: SelectOptionColor,
-}
-
-impl SelectOption {
-    pub fn new(name: &str) -> Self {
-        SelectOption {
-            id: nanoid!(4),
-            name: name.to_owned(),
-            color: SelectOptionColor::default(),
-        }
-    }
-
-    pub fn with_color(name: &str, color: SelectOptionColor) -> Self {
-        SelectOption {
-            id: nanoid!(4),
-            name: name.to_owned(),
-            color,
-        }
-    }
-}
-
-#[derive(Clone, Debug, Default, ProtoBuf)]
-pub struct SelectOptionChangesetPayload {
-    #[pb(index = 1)]
-    pub cell_identifier: CellIdentifierPayload,
-
-    #[pb(index = 2, one_of)]
-    pub insert_option: Option<SelectOption>,
-
-    #[pb(index = 3, one_of)]
-    pub update_option: Option<SelectOption>,
-
-    #[pb(index = 4, one_of)]
-    pub delete_option: Option<SelectOption>,
-}
-
-pub struct SelectOptionChangeset {
-    pub cell_identifier: CellIdentifier,
-    pub insert_option: Option<SelectOption>,
-    pub update_option: Option<SelectOption>,
-    pub delete_option: Option<SelectOption>,
-}
-
-impl TryInto<SelectOptionChangeset> for SelectOptionChangesetPayload {
-    type Error = ErrorCode;
-
-    fn try_into(self) -> Result<SelectOptionChangeset, Self::Error> {
-        let cell_identifier = self.cell_identifier.try_into()?;
-        Ok(SelectOptionChangeset {
-            cell_identifier,
-            insert_option: self.insert_option,
-            update_option: self.update_option,
-            delete_option: self.delete_option,
-        })
-    }
-}
-
-#[derive(Clone, Debug, Default, ProtoBuf)]
-pub struct SelectOptionCellChangesetPayload {
-    #[pb(index = 1)]
-    pub cell_identifier: CellIdentifierPayload,
-
-    #[pb(index = 2, one_of)]
-    pub insert_option_id: Option<String>,
-
-    #[pb(index = 3, one_of)]
-    pub delete_option_id: Option<String>,
-}
-
-pub struct SelectOptionCellChangesetParams {
-    pub cell_identifier: CellIdentifier,
-    pub insert_option_id: Option<String>,
-    pub delete_option_id: Option<String>,
-}
-
-impl std::convert::From<SelectOptionCellChangesetParams> for CellChangeset {
-    fn from(params: SelectOptionCellChangesetParams) -> Self {
-        let changeset = SelectOptionCellContentChangeset {
-            insert_option_id: params.insert_option_id,
-            delete_option_id: params.delete_option_id,
-        };
-        let s = serde_json::to_string(&changeset).unwrap();
-        CellChangeset {
-            grid_id: params.cell_identifier.grid_id,
-            row_id: params.cell_identifier.row_id,
-            field_id: params.cell_identifier.field_id,
-            cell_content_changeset: Some(s),
-        }
-    }
-}
-
-impl TryInto<SelectOptionCellChangesetParams> for SelectOptionCellChangesetPayload {
-    type Error = ErrorCode;
-
-    fn try_into(self) -> Result<SelectOptionCellChangesetParams, Self::Error> {
-        let cell_identifier: CellIdentifier = self.cell_identifier.try_into()?;
-        let insert_option_id = match self.insert_option_id {
-            None => None,
-            Some(insert_option_id) => Some(
-                NotEmptyStr::parse(insert_option_id)
-                    .map_err(|_| ErrorCode::OptionIdIsEmpty)?
-                    .0,
-            ),
-        };
-
-        let delete_option_id = match self.delete_option_id {
-            None => None,
-            Some(delete_option_id) => Some(
-                NotEmptyStr::parse(delete_option_id)
-                    .map_err(|_| ErrorCode::OptionIdIsEmpty)?
-                    .0,
-            ),
-        };
-
-        Ok(SelectOptionCellChangesetParams {
-            cell_identifier,
-            insert_option_id,
-            delete_option_id,
-        })
-    }
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-pub struct SelectOptionCellContentChangeset {
-    pub insert_option_id: Option<String>,
-    pub delete_option_id: Option<String>,
-}
-
-impl SelectOptionCellContentChangeset {
-    pub fn from_insert(option_id: &str) -> Self {
-        SelectOptionCellContentChangeset {
-            insert_option_id: Some(option_id.to_string()),
-            delete_option_id: None,
-        }
-    }
-
-    pub fn from_delete(option_id: &str) -> Self {
-        SelectOptionCellContentChangeset {
-            insert_option_id: None,
-            delete_option_id: Some(option_id.to_string()),
-        }
-    }
-
-    pub fn to_str(&self) -> String {
-        serde_json::to_string(self).unwrap()
-    }
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct SelectOptionCellData {
-    #[pb(index = 1)]
-    pub options: Vec<SelectOption>,
-
-    #[pb(index = 2)]
-    pub select_options: Vec<SelectOption>,
-}
-
-#[derive(ProtoBuf_Enum, PartialEq, Eq, Serialize, Deserialize, Debug, Clone)]
-#[repr(u8)]
-pub enum SelectOptionColor {
-    Purple = 0,
-    Pink = 1,
-    LightPink = 2,
-    Orange = 3,
-    Yellow = 4,
-    Lime = 5,
-    Green = 6,
-    Aqua = 7,
-    Blue = 8,
-}
-
-pub fn select_option_color_from_index(index: usize) -> SelectOptionColor {
-    match index % 8 {
-        0 => SelectOptionColor::Purple,
-        1 => SelectOptionColor::Pink,
-        2 => SelectOptionColor::LightPink,
-        3 => SelectOptionColor::Orange,
-        4 => SelectOptionColor::Yellow,
-        5 => SelectOptionColor::Lime,
-        6 => SelectOptionColor::Green,
-        7 => SelectOptionColor::Aqua,
-        8 => SelectOptionColor::Blue,
-        _ => SelectOptionColor::Purple,
-    }
-}
-
-impl std::default::Default for SelectOptionColor {
-    fn default() -> Self {
-        SelectOptionColor::Purple
-    }
-}
-
-fn make_select_context_from(cell_rev: &Option<CellRevision>, options: &[SelectOption]) -> Vec<SelectOption> {
-    match cell_rev {
-        None => vec![],
-        Some(cell_rev) => {
-            if let Ok(type_option_cell_data) = AnyCellData::from_str(&cell_rev.data) {
-                select_option_ids(type_option_cell_data.cell_data)
-                    .into_iter()
-                    .flat_map(|option_id| options.iter().find(|option| option.id == option_id).cloned())
-                    .collect()
-            } else {
-                vec![]
-            }
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::entities::FieldType;
-    use crate::services::field::FieldBuilder;
-    use crate::services::field::{
-        MultiSelectTypeOption, MultiSelectTypeOptionBuilder, SelectOption, SelectOptionCellContentChangeset,
-        SelectOptionCellData, SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR,
-    };
-    use crate::services::row::CellDataOperation;
-    use flowy_grid_data_model::revision::FieldRevision;
-
-    #[test]
-    fn single_select_test() {
-        let google_option = SelectOption::new("Google");
-        let facebook_option = SelectOption::new("Facebook");
-        let twitter_option = SelectOption::new("Twitter");
-        let single_select = SingleSelectTypeOptionBuilder::default()
-            .option(google_option.clone())
-            .option(facebook_option.clone())
-            .option(twitter_option);
-
-        let field_rev = FieldBuilder::new(single_select)
-            .name("Platform")
-            .visibility(true)
-            .build();
-
-        let type_option = SingleSelectTypeOption::from(&field_rev);
-
-        let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR);
-        let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
-        let cell_data = type_option.apply_changeset(data, None).unwrap();
-        assert_single_select_options(cell_data, &type_option, &field_rev, vec![google_option.clone()]);
-
-        let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
-        let cell_data = type_option.apply_changeset(data, None).unwrap();
-        assert_single_select_options(cell_data, &type_option, &field_rev, vec![google_option]);
-
-        // Invalid option id
-        let cell_data = type_option
-            .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
-            .unwrap();
-        assert_single_select_options(cell_data, &type_option, &field_rev, vec![]);
-
-        // Invalid option id
-        let cell_data = type_option
-            .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None)
-            .unwrap();
-
-        assert_single_select_options(cell_data, &type_option, &field_rev, vec![]);
-
-        // Invalid changeset
-        assert!(type_option.apply_changeset("123", None).is_err());
-    }
-
-    #[test]
-    fn multi_select_test() {
-        let google_option = SelectOption::new("Google");
-        let facebook_option = SelectOption::new("Facebook");
-        let twitter_option = SelectOption::new("Twitter");
-        let multi_select = MultiSelectTypeOptionBuilder::default()
-            .option(google_option.clone())
-            .option(facebook_option.clone())
-            .option(twitter_option);
-
-        let field_rev = FieldBuilder::new(multi_select)
-            .name("Platform")
-            .visibility(true)
-            .build();
-
-        let type_option = MultiSelectTypeOption::from(&field_rev);
-
-        let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
-        let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
-        let cell_data = type_option.apply_changeset(data, None).unwrap();
-        assert_multi_select_options(
-            cell_data,
-            &type_option,
-            &field_rev,
-            vec![google_option.clone(), facebook_option],
-        );
-
-        let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
-        let cell_data = type_option.apply_changeset(data, None).unwrap();
-        assert_multi_select_options(cell_data, &type_option, &field_rev, vec![google_option]);
-
-        // Invalid option id
-        let cell_data = type_option
-            .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
-            .unwrap();
-        assert_multi_select_options(cell_data, &type_option, &field_rev, vec![]);
-
-        // Invalid option id
-        let cell_data = type_option
-            .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None)
-            .unwrap();
-        assert_multi_select_options(cell_data, &type_option, &field_rev, vec![]);
-
-        // Invalid changeset
-        assert!(type_option.apply_changeset("123", None).is_err());
-    }
-
-    fn assert_multi_select_options(
-        cell_data: String,
-        type_option: &MultiSelectTypeOption,
-        field_rev: &FieldRevision,
-        expected: Vec<SelectOption>,
-    ) {
-        let field_type: FieldType = field_rev.field_type_rev.into();
-        assert_eq!(
-            expected,
-            type_option
-                .decode_cell_data(cell_data, &field_type, field_rev)
-                .unwrap()
-                .parse::<SelectOptionCellData>()
-                .unwrap()
-                .select_options,
-        );
-    }
-
-    fn assert_single_select_options(
-        cell_data: String,
-        type_option: &SingleSelectTypeOption,
-        field_rev: &FieldRevision,
-        expected: Vec<SelectOption>,
-    ) {
-        let field_type: FieldType = field_rev.field_type_rev.into();
-        assert_eq!(
-            expected,
-            type_option
-                .decode_cell_data(cell_data, &field_type, field_rev)
-                .unwrap()
-                .parse::<SelectOptionCellData>()
-                .unwrap()
-                .select_options,
-        );
-    }
-}

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

@@ -0,0 +1,200 @@
+use crate::entities::{FieldType, GridSelectOptionFilter};
+
+use crate::impl_type_option;
+use crate::services::field::select_option::{
+    make_select_context_from, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData, SelectOptionIds,
+    SelectOptionOperation,
+};
+
+use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
+use crate::services::row::{
+    AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
+};
+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};
+
+// Single select
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
+pub struct SingleSelectTypeOption {
+    #[pb(index = 1)]
+    pub options: Vec<SelectOption>,
+
+    #[pb(index = 2)]
+    pub disable_color: bool,
+}
+impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect);
+
+impl SelectOptionOperation for SingleSelectTypeOption {
+    fn select_option_cell_data(&self, cell_rev: &Option<CellRevision>) -> SelectOptionCellData {
+        let select_options = make_select_context_from(cell_rev, &self.options);
+        SelectOptionCellData {
+            options: self.options.clone(),
+            select_options,
+        }
+    }
+
+    fn options(&self) -> &Vec<SelectOption> {
+        &self.options
+    }
+
+    fn mut_options(&mut self) -> &mut Vec<SelectOption> {
+        &mut self.options
+    }
+}
+
+impl CellFilterOperation<GridSelectOptionFilter> for SingleSelectTypeOption {
+    fn apply_filter(&self, any_cell_data: AnyCellData, _filter: &GridSelectOptionFilter) -> FlowyResult<bool> {
+        if !any_cell_data.is_single_select() {
+            return Ok(true);
+        }
+        let _ids: SelectOptionIds = any_cell_data.try_into()?;
+        Ok(false)
+    }
+}
+
+impl CellDataOperation<String> for SingleSelectTypeOption {
+    fn decode_cell_data<T>(
+        &self,
+        cell_data: T,
+        decoded_field_type: &FieldType,
+        _field_rev: &FieldRevision,
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<String>,
+    {
+        if !decoded_field_type.is_select_option() {
+            return Ok(DecodedCellData::default());
+        }
+
+        let encoded_data = cell_data.into();
+        let mut cell_data = SelectOptionCellData {
+            options: self.options.clone(),
+            select_options: vec![],
+        };
+
+        let ids: SelectOptionIds = encoded_data.into();
+        if let Some(option_id) = ids.first() {
+            if let Some(option) = self.options.iter().find(|option| &option.id == option_id) {
+                cell_data.select_options.push(option.clone());
+            }
+        }
+
+        DecodedCellData::try_from_bytes(cell_data)
+    }
+
+    fn apply_changeset<C>(&self, changeset: C, _cell_rev: Option<CellRevision>) -> Result<String, FlowyError>
+    where
+        C: Into<CellContentChangeset>,
+    {
+        let changeset = changeset.into();
+        let select_option_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset)?;
+        let new_cell_data: String;
+        if let Some(insert_option_id) = select_option_changeset.insert_option_id {
+            tracing::trace!("Insert single select option: {}", &insert_option_id);
+            new_cell_data = insert_option_id;
+        } else {
+            tracing::trace!("Delete single select option");
+            new_cell_data = "".to_string()
+        }
+
+        Ok(new_cell_data)
+    }
+}
+
+#[derive(Default)]
+pub struct SingleSelectTypeOptionBuilder(SingleSelectTypeOption);
+impl_into_box_type_option_builder!(SingleSelectTypeOptionBuilder);
+impl_builder_from_json_str_and_from_bytes!(SingleSelectTypeOptionBuilder, SingleSelectTypeOption);
+
+impl SingleSelectTypeOptionBuilder {
+    pub fn option(mut self, opt: SelectOption) -> Self {
+        self.0.options.push(opt);
+        self
+    }
+}
+
+impl TypeOptionBuilder for SingleSelectTypeOptionBuilder {
+    fn field_type(&self) -> FieldType {
+        FieldType::SingleSelect
+    }
+
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::entities::FieldType;
+    use crate::services::field::select_option::*;
+    use crate::services::field::type_options::*;
+    use crate::services::field::FieldBuilder;
+    use crate::services::row::CellDataOperation;
+    use flowy_grid_data_model::revision::FieldRevision;
+
+    #[test]
+    fn single_select_test() {
+        let google_option = SelectOption::new("Google");
+        let facebook_option = SelectOption::new("Facebook");
+        let twitter_option = SelectOption::new("Twitter");
+        let single_select = SingleSelectTypeOptionBuilder::default()
+            .option(google_option.clone())
+            .option(facebook_option.clone())
+            .option(twitter_option);
+
+        let field_rev = FieldBuilder::new(single_select)
+            .name("Platform")
+            .visibility(true)
+            .build();
+
+        let type_option = SingleSelectTypeOption::from(&field_rev);
+
+        let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR);
+        let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
+        let cell_data = type_option.apply_changeset(data, None).unwrap();
+        assert_single_select_options(cell_data, &type_option, &field_rev, vec![google_option.clone()]);
+
+        let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
+        let cell_data = type_option.apply_changeset(data, None).unwrap();
+        assert_single_select_options(cell_data, &type_option, &field_rev, vec![google_option]);
+
+        // Invalid option id
+        let cell_data = type_option
+            .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
+            .unwrap();
+        assert_single_select_options(cell_data, &type_option, &field_rev, vec![]);
+
+        // Invalid option id
+        let cell_data = type_option
+            .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None)
+            .unwrap();
+
+        assert_single_select_options(cell_data, &type_option, &field_rev, vec![]);
+
+        // Invalid changeset
+        assert!(type_option.apply_changeset("123", None).is_err());
+    }
+
+    fn assert_single_select_options(
+        cell_data: String,
+        type_option: &SingleSelectTypeOption,
+        field_rev: &FieldRevision,
+        expected: Vec<SelectOption>,
+    ) {
+        let field_type: FieldType = field_rev.field_type_rev.into();
+        assert_eq!(
+            expected,
+            type_option
+                .decode_cell_data(cell_data, &field_type, field_rev)
+                .unwrap()
+                .parse::<SelectOptionCellData>()
+                .unwrap()
+                .select_options,
+        );
+    }
+}

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

@@ -39,7 +39,7 @@ impl CellFilterOperation<GridTextFilter> for RichTextTypeOption {
         }
 
         let text_cell_data: TextCellData = any_cell_data.try_into()?;
-        Ok(filter.apply(&text_cell_data.0))
+        Ok(filter.apply(text_cell_data))
     }
 }
 
@@ -78,7 +78,13 @@ impl CellDataOperation<String> for RichTextTypeOption {
     }
 }
 
-pub struct TextCellData(String);
+pub struct TextCellData(pub String);
+impl AsRef<str> for TextCellData {
+    fn as_ref(&self) -> &str {
+        &self.0
+    }
+}
+
 impl std::convert::TryFrom<AnyCellData> for TextCellData {
     type Error = FlowyError;
 
@@ -90,6 +96,7 @@ impl std::convert::TryFrom<AnyCellData> for TextCellData {
 #[cfg(test)]
 mod tests {
     use crate::entities::FieldType;
+    use crate::services::field::select_option::*;
     use crate::services::field::FieldBuilder;
     use crate::services::field::*;
     use crate::services::row::CellDataOperation;

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

@@ -1,6 +1,6 @@
 use crate::entities::{FieldType, GridTextFilter};
 use crate::impl_type_option;
-use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
+use crate::services::field::{BoxTypeOptionBuilder, TextCellData, TypeOptionBuilder};
 use crate::services::row::{
     AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData, EncodedCellData,
 };
@@ -37,7 +37,12 @@ impl_type_option!(URLTypeOption, FieldType::URL);
 
 impl CellFilterOperation<GridTextFilter> for URLTypeOption {
     fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult<bool> {
-        Ok(false)
+        if !any_cell_data.is_url() {
+            return Ok(true);
+        }
+
+        let text_cell_data: TextCellData = any_cell_data.try_into()?;
+        Ok(filter.apply(&text_cell_data))
     }
 }
 
@@ -130,7 +135,7 @@ impl FromStr for URLCellData {
 impl std::convert::TryFrom<AnyCellData> for URLCellData {
     type Error = ();
 
-    fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
+    fn try_from(_value: AnyCellData) -> Result<Self, Self::Error> {
         todo!()
     }
 }

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


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

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

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

@@ -119,6 +119,10 @@ impl AnyCellData {
         self.field_type == FieldType::MultiSelect
     }
 
+    pub fn is_url(&self) -> bool {
+        self.field_type == FieldType::URL
+    }
+
     pub fn is_select_option(&self) -> bool {
         self.field_type == FieldType::MultiSelect || self.field_type == FieldType::SingleSelect
     }

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

@@ -1,4 +1,4 @@
-use crate::services::field::SelectOptionCellContentChangeset;
+use crate::services::field::select_option::SelectOptionCellContentChangeset;
 use crate::services::row::apply_cell_data_changeset;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::revision::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT};

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

@@ -2,7 +2,8 @@ use crate::grid::field_util::make_date_cell_string;
 use crate::grid::script::EditorScript::*;
 use crate::grid::script::*;
 use flowy_grid::entities::{CellChangeset, FieldType};
-use flowy_grid::services::field::{MultiSelectTypeOption, SelectOptionCellContentChangeset, SingleSelectTypeOption};
+use flowy_grid::services::field::select_option::SelectOptionCellContentChangeset;
+use flowy_grid::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
 
 #[tokio::test]
 async fn grid_cell_update() {

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

@@ -1,7 +1,8 @@
 use crate::grid::field_util::*;
 use crate::grid::script::EditorScript::*;
 use crate::grid::script::*;
-use flowy_grid::services::field::{SelectOption, SingleSelectTypeOption};
+use flowy_grid::services::field::select_option::SelectOption;
+use flowy_grid::services::field::SingleSelectTypeOption;
 use flowy_grid_data_model::revision::TypeOptionDataEntry;
 use flowy_sync::entities::grid::FieldChangesetParams;
 

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

@@ -1,6 +1,6 @@
-use flowy_grid::services::field::*;
-
 use flowy_grid::entities::*;
+use flowy_grid::services::field::select_option::SelectOption;
+use flowy_grid::services::field::*;
 use flowy_grid_data_model::revision::*;
 
 pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) {

+ 2 - 3
frontend/rust-lib/flowy-grid/tests/grid/row_test.rs

@@ -4,9 +4,8 @@ use crate::grid::script::EditorScript::*;
 use crate::grid::script::*;
 use chrono::NaiveDateTime;
 use flowy_grid::entities::FieldType;
-use flowy_grid::services::field::{
-    DateCellData, MultiSelectTypeOption, SingleSelectTypeOption, SELECTION_IDS_SEPARATOR,
-};
+use flowy_grid::services::field::select_option::SELECTION_IDS_SEPARATOR;
+use flowy_grid::services::field::{DateCellData, MultiSelectTypeOption, SingleSelectTypeOption};
 use flowy_grid::services::row::{decode_any_cell_data, CreateRowRevisionBuilder};
 use flowy_grid_data_model::revision::RowMetaChangeset;
 

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

@@ -18,6 +18,7 @@ use std::sync::Arc;
 use std::time::Duration;
 use strum::EnumCount;
 use tokio::time::sleep;
+use flowy_grid::services::field::select_option::SelectOption;
 use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, FieldChangesetParams, GridSettingChangesetParams};
 
 pub enum EditorScript {