瀏覽代碼

Merge pull request #596 from AppFlowy-IO/feat/cell_filter_test

Feat/cell filter test
Nathan.fooo 2 年之前
父節點
當前提交
02cdd8ca4c
共有 46 個文件被更改,包括 1699 次插入1219 次删除
  1. 1 1
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart
  2. 1 1
      frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart
  3. 1 1
      frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart
  4. 1 1
      frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart
  5. 1 1
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/edit_select_option_bloc.dart
  6. 2 1
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart
  7. 1 1
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart
  8. 2 1
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart
  9. 1 1
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart
  10. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart
  11. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart
  12. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart
  13. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart
  14. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart
  15. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart
  16. 1 0
      frontend/rust-lib/flowy-grid/Flowy.toml
  17. 0 393
      frontend/rust-lib/flowy-grid/src/entities/filter_entities.rs
  18. 77 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs
  19. 56 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs
  20. 13 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs
  21. 142 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs
  22. 121 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs
  23. 150 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs
  24. 128 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs
  25. 7 3
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  26. 1 0
      frontend/rust-lib/flowy-grid/src/services/field/mod.rs
  27. 337 0
      frontend/rust-lib/flowy-grid/src/services/field/select_option.rs
  28. 19 7
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs
  29. 6 3
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs
  30. 5 2
      frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs
  31. 217 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/multi_select_type_option.rs
  32. 111 104
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs
  33. 0 660
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs
  34. 196 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/single_select_type_option.rs
  35. 21 7
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs
  36. 20 7
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs
  37. 0 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs
  38. 4 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/util/mod.rs
  39. 15 8
      frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs
  40. 25 2
      frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs
  41. 1 1
      frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs
  42. 2 1
      frontend/rust-lib/flowy-grid/tests/grid/cell_test.rs
  43. 2 1
      frontend/rust-lib/flowy-grid/tests/grid/field_test.rs
  44. 2 2
      frontend/rust-lib/flowy-grid/tests/grid/field_util.rs
  45. 2 3
      frontend/rust-lib/flowy-grid/tests/grid/row_test.rs
  46. 1 0
      frontend/rust-lib/flowy-grid/tests/grid/script.rs

+ 1 - 1
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart

@@ -9,7 +9,7 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
 import 'package:flutter/foundation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';

+ 1 - 1
frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart

@@ -1,5 +1,5 @@
 import 'dart:async';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';

+ 1 - 1
frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart

@@ -2,7 +2,7 @@ import 'dart:async';
 import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';

+ 1 - 1
frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart

@@ -2,8 +2,8 @@ import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
 import 'cell_service/cell_service.dart';
 
 class SelectOptionService {

+ 1 - 1
frontend/app_flowy/lib/workspace/application/grid/field/type_option/edit_select_option_bloc.dart

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 2 - 1
frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart

@@ -1,6 +1,7 @@
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
 import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
 import 'dart:async';
 import 'package:protobuf/protobuf.dart';
 import 'select_option_type_option_bloc.dart';

+ 1 - 1
frontend/app_flowy/lib/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 2 - 1
frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart

@@ -1,6 +1,7 @@
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
 import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart';
 import 'dart:async';
 import 'package:protobuf/protobuf.dart';
 import 'select_option_type_option_bloc.dart';

+ 1 - 1
frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart

@@ -6,7 +6,7 @@ import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
 import 'package:protobuf/protobuf.dart';
 
 class TypeOptionService {

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart

@@ -1,7 +1,7 @@
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart

@@ -5,7 +5,7 @@ import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 // ignore: unused_import
 import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart

@@ -10,8 +10,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:easy_localization/easy_localization.dart';

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart

@@ -2,7 +2,7 @@ import 'dart:collection';
 
 import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra/theme.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart

@@ -8,7 +8,7 @@ import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:easy_localization/easy_localization.dart';

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart

@@ -8,7 +8,7 @@ import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:easy_localization/easy_localization.dart';

+ 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"
 ]

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

@@ -1,393 +0,0 @@
-use crate::entities::FieldType;
-use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-use flowy_error::ErrorCode;
-use flowy_grid_data_model::parser::NotEmptyStr;
-use flowy_grid_data_model::revision::{FieldRevision, GridFilterRevision};
-use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams};
-use std::convert::TryInto;
-use std::sync::Arc;
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct GridFilter {
-    #[pb(index = 1)]
-    pub id: String,
-}
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct RepeatedGridFilter {
-    #[pb(index = 1)]
-    pub items: Vec<GridFilter>,
-}
-
-impl std::convert::From<&Arc<GridFilterRevision>> for GridFilter {
-    fn from(rev: &Arc<GridFilterRevision>) -> Self {
-        Self { id: rev.id.clone() }
-    }
-}
-
-impl std::convert::From<&Vec<Arc<GridFilterRevision>>> for RepeatedGridFilter {
-    fn from(revs: &Vec<Arc<GridFilterRevision>>) -> Self {
-        RepeatedGridFilter {
-            items: revs.iter().map(|rev| rev.into()).collect(),
-        }
-    }
-}
-
-impl std::convert::From<Vec<GridFilter>> for RepeatedGridFilter {
-    fn from(items: Vec<GridFilter>) -> Self {
-        Self { items }
-    }
-}
-
-#[derive(ProtoBuf, Debug, Default, Clone)]
-pub struct DeleteFilterPayload {
-    #[pb(index = 1)]
-    pub filter_id: String,
-
-    #[pb(index = 2)]
-    pub field_type: FieldType,
-}
-
-impl TryInto<DeleteFilterParams> for DeleteFilterPayload {
-    type Error = ErrorCode;
-
-    fn try_into(self) -> Result<DeleteFilterParams, Self::Error> {
-        let filter_id = NotEmptyStr::parse(self.filter_id)
-            .map_err(|_| ErrorCode::UnexpectedEmptyString)?
-            .0;
-        Ok(DeleteFilterParams {
-            filter_id,
-            field_type_rev: self.field_type.into(),
-        })
-    }
-}
-
-#[derive(ProtoBuf, Debug, Default, Clone)]
-pub struct CreateGridFilterPayload {
-    #[pb(index = 1)]
-    pub field_id: String,
-
-    #[pb(index = 2)]
-    pub field_type: FieldType,
-
-    #[pb(index = 3)]
-    pub condition: i32,
-
-    #[pb(index = 4, one_of)]
-    pub content: Option<String>,
-}
-
-impl CreateGridFilterPayload {
-    #[allow(dead_code)]
-    pub fn new<T: Into<i32>>(field_rev: &FieldRevision, condition: T, content: Option<String>) -> Self {
-        Self {
-            field_id: field_rev.id.clone(),
-            field_type: field_rev.field_type_rev.into(),
-            condition: condition.into(),
-            content,
-        }
-    }
-}
-
-impl TryInto<CreateGridFilterParams> for CreateGridFilterPayload {
-    type Error = ErrorCode;
-
-    fn try_into(self) -> Result<CreateGridFilterParams, Self::Error> {
-        let field_id = NotEmptyStr::parse(self.field_id)
-            .map_err(|_| ErrorCode::FieldIdIsEmpty)?
-            .0;
-        let condition = self.condition as u8;
-        match self.field_type {
-            FieldType::RichText | FieldType::URL => {
-                let _ = TextFilterCondition::try_from(condition)?;
-            }
-            FieldType::Checkbox => {
-                let _ = CheckboxCondition::try_from(condition)?;
-            }
-            FieldType::Number => {
-                let _ = NumberFilterCondition::try_from(condition)?;
-            }
-            FieldType::DateTime => {
-                let _ = DateFilterCondition::try_from(condition)?;
-            }
-            FieldType::SingleSelect | FieldType::MultiSelect => {
-                let _ = SelectOptionCondition::try_from(condition)?;
-            }
-        }
-
-        Ok(CreateGridFilterParams {
-            field_id,
-            field_type_rev: self.field_type.into(),
-            condition,
-            content: self.content,
-        })
-    }
-}
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct GridTextFilter {
-    #[pb(index = 1)]
-    pub condition: TextFilterCondition,
-
-    #[pb(index = 2, one_of)]
-    pub content: Option<String>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
-#[repr(u8)]
-pub enum TextFilterCondition {
-    Is = 0,
-    IsNot = 1,
-    Contains = 2,
-    DoesNotContain = 3,
-    StartsWith = 4,
-    EndsWith = 5,
-    TextIsEmpty = 6,
-    TextIsNotEmpty = 7,
-}
-impl std::convert::From<TextFilterCondition> for i32 {
-    fn from(value: TextFilterCondition) -> Self {
-        value as i32
-    }
-}
-
-impl std::default::Default for TextFilterCondition {
-    fn default() -> Self {
-        TextFilterCondition::Is
-    }
-}
-impl std::convert::TryFrom<u8> for TextFilterCondition {
-    type Error = ErrorCode;
-
-    fn try_from(value: u8) -> Result<Self, Self::Error> {
-        match value {
-            0 => Ok(TextFilterCondition::Is),
-            1 => Ok(TextFilterCondition::IsNot),
-            2 => Ok(TextFilterCondition::Contains),
-            3 => Ok(TextFilterCondition::DoesNotContain),
-            4 => Ok(TextFilterCondition::StartsWith),
-            5 => Ok(TextFilterCondition::EndsWith),
-            6 => Ok(TextFilterCondition::TextIsEmpty),
-            7 => Ok(TextFilterCondition::TextIsNotEmpty),
-            _ => Err(ErrorCode::InvalidData),
-        }
-    }
-}
-
-impl std::convert::From<Arc<GridFilterRevision>> for GridTextFilter {
-    fn from(rev: Arc<GridFilterRevision>) -> Self {
-        GridTextFilter {
-            condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is),
-            content: rev.content.clone(),
-        }
-    }
-}
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct GridNumberFilter {
-    #[pb(index = 1)]
-    pub condition: NumberFilterCondition,
-
-    #[pb(index = 2, one_of)]
-    pub content: Option<String>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
-#[repr(u8)]
-pub enum NumberFilterCondition {
-    Equal = 0,
-    NotEqual = 1,
-    GreaterThan = 2,
-    LessThan = 3,
-    GreaterThanOrEqualTo = 4,
-    LessThanOrEqualTo = 5,
-    NumberIsEmpty = 6,
-    NumberIsNotEmpty = 7,
-}
-impl std::default::Default for NumberFilterCondition {
-    fn default() -> Self {
-        NumberFilterCondition::Equal
-    }
-}
-
-impl std::convert::From<NumberFilterCondition> for i32 {
-    fn from(value: NumberFilterCondition) -> Self {
-        value as i32
-    }
-}
-impl std::convert::TryFrom<u8> for NumberFilterCondition {
-    type Error = ErrorCode;
-
-    fn try_from(n: u8) -> Result<Self, Self::Error> {
-        match n {
-            0 => Ok(NumberFilterCondition::Equal),
-            1 => Ok(NumberFilterCondition::NotEqual),
-            2 => Ok(NumberFilterCondition::GreaterThan),
-            3 => Ok(NumberFilterCondition::LessThan),
-            4 => Ok(NumberFilterCondition::GreaterThanOrEqualTo),
-            5 => Ok(NumberFilterCondition::LessThanOrEqualTo),
-            6 => Ok(NumberFilterCondition::NumberIsEmpty),
-            7 => Ok(NumberFilterCondition::NumberIsNotEmpty),
-            _ => Err(ErrorCode::InvalidData),
-        }
-    }
-}
-
-impl std::convert::From<Arc<GridFilterRevision>> for GridNumberFilter {
-    fn from(rev: Arc<GridFilterRevision>) -> Self {
-        GridNumberFilter {
-            condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal),
-            content: rev.content.clone(),
-        }
-    }
-}
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct GridSelectOptionFilter {
-    #[pb(index = 1)]
-    pub condition: SelectOptionCondition,
-
-    #[pb(index = 2, one_of)]
-    pub content: Option<String>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
-#[repr(u8)]
-pub enum SelectOptionCondition {
-    OptionIs = 0,
-    OptionIsNot = 1,
-    OptionIsEmpty = 2,
-    OptionIsNotEmpty = 3,
-}
-
-impl std::convert::From<SelectOptionCondition> for i32 {
-    fn from(value: SelectOptionCondition) -> Self {
-        value as i32
-    }
-}
-
-impl std::default::Default for SelectOptionCondition {
-    fn default() -> Self {
-        SelectOptionCondition::OptionIs
-    }
-}
-
-impl std::convert::TryFrom<u8> for SelectOptionCondition {
-    type Error = ErrorCode;
-
-    fn try_from(value: u8) -> Result<Self, Self::Error> {
-        match value {
-            0 => Ok(SelectOptionCondition::OptionIs),
-            1 => Ok(SelectOptionCondition::OptionIsNot),
-            2 => Ok(SelectOptionCondition::OptionIsEmpty),
-            3 => Ok(SelectOptionCondition::OptionIsNotEmpty),
-            _ => Err(ErrorCode::InvalidData),
-        }
-    }
-}
-
-impl std::convert::From<Arc<GridFilterRevision>> for GridSelectOptionFilter {
-    fn from(rev: Arc<GridFilterRevision>) -> Self {
-        GridSelectOptionFilter {
-            condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs),
-            content: rev.content.clone(),
-        }
-    }
-}
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct GridDateFilter {
-    #[pb(index = 1)]
-    pub condition: DateFilterCondition,
-
-    #[pb(index = 2, one_of)]
-    pub content: Option<String>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
-#[repr(u8)]
-pub enum DateFilterCondition {
-    DateIs = 0,
-    DateBefore = 1,
-    DateAfter = 2,
-    DateOnOrBefore = 3,
-    DateOnOrAfter = 4,
-    DateWithIn = 5,
-    DateIsEmpty = 6,
-}
-
-impl std::default::Default for DateFilterCondition {
-    fn default() -> Self {
-        DateFilterCondition::DateIs
-    }
-}
-
-impl std::convert::TryFrom<u8> for DateFilterCondition {
-    type Error = ErrorCode;
-
-    fn try_from(value: u8) -> Result<Self, Self::Error> {
-        match value {
-            0 => Ok(DateFilterCondition::DateIs),
-            1 => Ok(DateFilterCondition::DateBefore),
-            2 => Ok(DateFilterCondition::DateAfter),
-            3 => Ok(DateFilterCondition::DateOnOrBefore),
-            4 => Ok(DateFilterCondition::DateOnOrAfter),
-            5 => Ok(DateFilterCondition::DateWithIn),
-            6 => Ok(DateFilterCondition::DateIsEmpty),
-            _ => Err(ErrorCode::InvalidData),
-        }
-    }
-}
-impl std::convert::From<Arc<GridFilterRevision>> for GridDateFilter {
-    fn from(rev: Arc<GridFilterRevision>) -> Self {
-        GridDateFilter {
-            condition: DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs),
-            content: rev.content.clone(),
-        }
-    }
-}
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct GridCheckboxFilter {
-    #[pb(index = 1)]
-    pub condition: CheckboxCondition,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
-#[repr(u8)]
-pub enum CheckboxCondition {
-    IsChecked = 0,
-    IsUnChecked = 1,
-}
-
-impl std::convert::From<CheckboxCondition> for i32 {
-    fn from(value: CheckboxCondition) -> Self {
-        value as i32
-    }
-}
-
-impl std::default::Default for CheckboxCondition {
-    fn default() -> Self {
-        CheckboxCondition::IsChecked
-    }
-}
-
-impl std::convert::TryFrom<u8> for CheckboxCondition {
-    type Error = ErrorCode;
-
-    fn try_from(value: u8) -> Result<Self, Self::Error> {
-        match value {
-            0 => Ok(CheckboxCondition::IsChecked),
-            1 => Ok(CheckboxCondition::IsUnChecked),
-            _ => Err(ErrorCode::InvalidData),
-        }
-    }
-}
-
-impl std::convert::From<Arc<GridFilterRevision>> for GridCheckboxFilter {
-    fn from(rev: Arc<GridFilterRevision>) -> Self {
-        GridCheckboxFilter {
-            condition: CheckboxCondition::try_from(rev.condition).unwrap_or(CheckboxCondition::IsChecked),
-        }
-    }
-}

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

@@ -0,0 +1,77 @@
+use crate::services::field::CheckboxCellData;
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::ErrorCode;
+use flowy_grid_data_model::revision::GridFilterRevision;
+use std::sync::Arc;
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct GridCheckboxFilter {
+    #[pb(index = 1)]
+    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 {
+    IsChecked = 0,
+    IsUnChecked = 1,
+}
+
+impl std::convert::From<CheckboxCondition> for i32 {
+    fn from(value: CheckboxCondition) -> Self {
+        value as i32
+    }
+}
+
+impl std::default::Default for CheckboxCondition {
+    fn default() -> Self {
+        CheckboxCondition::IsChecked
+    }
+}
+
+impl std::convert::TryFrom<u8> for CheckboxCondition {
+    type Error = ErrorCode;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0 => Ok(CheckboxCondition::IsChecked),
+            1 => Ok(CheckboxCondition::IsUnChecked),
+            _ => Err(ErrorCode::InvalidData),
+        }
+    }
+}
+
+impl std::convert::From<Arc<GridFilterRevision>> for GridCheckboxFilter {
+    fn from(rev: Arc<GridFilterRevision>) -> Self {
+        GridCheckboxFilter {
+            condition: CheckboxCondition::try_from(rev.condition).unwrap_or(CheckboxCondition::IsChecked),
+        }
+    }
+}
+
+#[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);
+        }
+    }
+}

+ 56 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs

@@ -0,0 +1,56 @@
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::ErrorCode;
+use flowy_grid_data_model::revision::GridFilterRevision;
+use std::sync::Arc;
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct GridDateFilter {
+    #[pb(index = 1)]
+    pub condition: DateFilterCondition,
+
+    #[pb(index = 2, one_of)]
+    pub content: Option<String>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
+#[repr(u8)]
+pub enum DateFilterCondition {
+    DateIs = 0,
+    DateBefore = 1,
+    DateAfter = 2,
+    DateOnOrBefore = 3,
+    DateOnOrAfter = 4,
+    DateWithIn = 5,
+    DateIsEmpty = 6,
+}
+
+impl std::default::Default for DateFilterCondition {
+    fn default() -> Self {
+        DateFilterCondition::DateIs
+    }
+}
+
+impl std::convert::TryFrom<u8> for DateFilterCondition {
+    type Error = ErrorCode;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0 => Ok(DateFilterCondition::DateIs),
+            1 => Ok(DateFilterCondition::DateBefore),
+            2 => Ok(DateFilterCondition::DateAfter),
+            3 => Ok(DateFilterCondition::DateOnOrBefore),
+            4 => Ok(DateFilterCondition::DateOnOrAfter),
+            5 => Ok(DateFilterCondition::DateWithIn),
+            6 => Ok(DateFilterCondition::DateIsEmpty),
+            _ => Err(ErrorCode::InvalidData),
+        }
+    }
+}
+impl std::convert::From<Arc<GridFilterRevision>> for GridDateFilter {
+    fn from(rev: Arc<GridFilterRevision>) -> Self {
+        GridDateFilter {
+            condition: DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs),
+            content: rev.content.clone(),
+        }
+    }
+}

+ 13 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs

@@ -0,0 +1,13 @@
+mod checkbox_filter;
+mod date_filter;
+mod number_filter;
+mod select_option_filter;
+mod text_filter;
+mod util;
+
+pub use checkbox_filter::*;
+pub use date_filter::*;
+pub use number_filter::*;
+pub use select_option_filter::*;
+pub use text_filter::*;
+pub use util::*;

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

@@ -0,0 +1,142 @@
+use crate::services::field::NumberCellData;
+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;
+use std::str::FromStr;
+use std::sync::Arc;
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct GridNumberFilter {
+    #[pb(index = 1)]
+    pub condition: NumberFilterCondition,
+
+    #[pb(index = 2, one_of)]
+    pub content: Option<String>,
+}
+
+impl GridNumberFilter {
+    pub fn apply(&self, num_cell_data: &NumberCellData) -> bool {
+        if self.content.is_none() {
+            return false;
+        }
+
+        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) {
+            Ok(decimal) => match self.condition {
+                NumberFilterCondition::Equal => cell_decimal == &decimal,
+                NumberFilterCondition::NotEqual => cell_decimal != &decimal,
+                NumberFilterCondition::GreaterThan => cell_decimal > &decimal,
+                NumberFilterCondition::LessThan => cell_decimal < &decimal,
+                NumberFilterCondition::GreaterThanOrEqualTo => cell_decimal >= &decimal,
+                NumberFilterCondition::LessThanOrEqualTo => cell_decimal <= &decimal,
+                NumberFilterCondition::NumberIsEmpty => num_cell_data.is_empty(),
+                NumberFilterCondition::NumberIsNotEmpty => !num_cell_data.is_empty(),
+            },
+            Err(_) => false,
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
+#[repr(u8)]
+pub enum NumberFilterCondition {
+    Equal = 0,
+    NotEqual = 1,
+    GreaterThan = 2,
+    LessThan = 3,
+    GreaterThanOrEqualTo = 4,
+    LessThanOrEqualTo = 5,
+    NumberIsEmpty = 6,
+    NumberIsNotEmpty = 7,
+}
+
+impl std::default::Default for NumberFilterCondition {
+    fn default() -> Self {
+        NumberFilterCondition::Equal
+    }
+}
+
+impl std::convert::From<NumberFilterCondition> for i32 {
+    fn from(value: NumberFilterCondition) -> Self {
+        value as i32
+    }
+}
+impl std::convert::TryFrom<u8> for NumberFilterCondition {
+    type Error = ErrorCode;
+
+    fn try_from(n: u8) -> Result<Self, Self::Error> {
+        match n {
+            0 => Ok(NumberFilterCondition::Equal),
+            1 => Ok(NumberFilterCondition::NotEqual),
+            2 => Ok(NumberFilterCondition::GreaterThan),
+            3 => Ok(NumberFilterCondition::LessThan),
+            4 => Ok(NumberFilterCondition::GreaterThanOrEqualTo),
+            5 => Ok(NumberFilterCondition::LessThanOrEqualTo),
+            6 => Ok(NumberFilterCondition::NumberIsEmpty),
+            7 => Ok(NumberFilterCondition::NumberIsNotEmpty),
+            _ => Err(ErrorCode::InvalidData),
+        }
+    }
+}
+
+impl std::convert::From<Arc<GridFilterRevision>> for GridNumberFilter {
+    fn from(rev: Arc<GridFilterRevision>) -> Self {
+        GridNumberFilter {
+            condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal),
+            content: rev.content.clone(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::entities::{GridNumberFilter, NumberFilterCondition};
+
+    use crate::services::field::{NumberCellData, NumberFormat};
+    use std::str::FromStr;
+    #[test]
+    fn number_filter_equal_test() {
+        let number_filter = GridNumberFilter {
+            condition: NumberFilterCondition::Equal,
+            content: Some("123".to_owned()),
+        };
+
+        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 [("$123", true), ("1234", false), ("", false)] {
+            let data = NumberCellData::from_format_str(num_str, true, &format).unwrap();
+            assert_eq!(number_filter.apply(&data), r);
+        }
+    }
+    #[test]
+    fn number_filter_greater_than_test() {
+        let number_filter = GridNumberFilter {
+            condition: NumberFilterCondition::GreaterThan,
+            content: Some("12".to_owned()),
+        };
+        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);
+        }
+    }
+
+    #[test]
+    fn number_filter_less_than_test() {
+        let number_filter = GridNumberFilter {
+            condition: NumberFilterCondition::LessThan,
+            content: Some("100".to_owned()),
+        };
+        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);
+        }
+    }
+}

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

@@ -0,0 +1,121 @@
+#![allow(clippy::needless_collect)]
+use crate::services::field::select_option::{SelectOptionIds, SelectedSelectOptions};
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::ErrorCode;
+use flowy_grid_data_model::revision::GridFilterRevision;
+use std::sync::Arc;
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct GridSelectOptionFilter {
+    #[pb(index = 1)]
+    pub condition: SelectOptionCondition,
+
+    #[pb(index = 2)]
+    pub option_ids: Vec<String>,
+}
+
+impl GridSelectOptionFilter {
+    pub fn apply(&self, selected_options: &SelectedSelectOptions) -> bool {
+        let selected_option_ids: Vec<&String> = selected_options.options.iter().map(|option| &option.id).collect();
+        match self.condition {
+            SelectOptionCondition::OptionIs => {
+                // if selected options equal to filter's options, then the required_options will be empty.
+                let required_options = self
+                    .option_ids
+                    .iter()
+                    .filter(|id| !selected_option_ids.contains(id))
+                    .collect::<Vec<_>>();
+
+                // https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect
+                !required_options.is_empty()
+            }
+            SelectOptionCondition::OptionIsNot => {
+                for option_id in selected_option_ids {
+                    if self.option_ids.contains(option_id) {
+                        return true;
+                    }
+                }
+                false
+            }
+            SelectOptionCondition::OptionIsEmpty => selected_option_ids.is_empty(),
+            SelectOptionCondition::OptionIsNotEmpty => !selected_option_ids.is_empty(),
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
+#[repr(u8)]
+pub enum SelectOptionCondition {
+    OptionIs = 0,
+    OptionIsNot = 1,
+    OptionIsEmpty = 2,
+    OptionIsNotEmpty = 3,
+}
+
+impl std::convert::From<SelectOptionCondition> for i32 {
+    fn from(value: SelectOptionCondition) -> Self {
+        value as i32
+    }
+}
+
+impl std::default::Default for SelectOptionCondition {
+    fn default() -> Self {
+        SelectOptionCondition::OptionIs
+    }
+}
+
+impl std::convert::TryFrom<u8> for SelectOptionCondition {
+    type Error = ErrorCode;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0 => Ok(SelectOptionCondition::OptionIs),
+            1 => Ok(SelectOptionCondition::OptionIsNot),
+            2 => Ok(SelectOptionCondition::OptionIsEmpty),
+            3 => Ok(SelectOptionCondition::OptionIsNotEmpty),
+            _ => Err(ErrorCode::InvalidData),
+        }
+    }
+}
+
+impl std::convert::From<Arc<GridFilterRevision>> for GridSelectOptionFilter {
+    fn from(rev: Arc<GridFilterRevision>) -> Self {
+        let ids = SelectOptionIds::from(rev.content.clone());
+        GridSelectOptionFilter {
+            condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs),
+            option_ids: ids.into_inner(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    #![allow(clippy::all)]
+    use crate::entities::{GridSelectOptionFilter, SelectOptionCondition};
+    use crate::services::field::select_option::{SelectOption, SelectedSelectOptions};
+
+    #[test]
+    fn select_option_filter_is_test() {
+        let option_1 = SelectOption::new("A");
+        let option_2 = SelectOption::new("B");
+
+        let filter_1 = GridSelectOptionFilter {
+            condition: SelectOptionCondition::OptionIs,
+            option_ids: vec![option_1.id.clone(), option_2.id.clone()],
+        };
+
+        assert_eq!(
+            filter_1.apply(&SelectedSelectOptions {
+                options: vec![option_1.clone(), option_2.clone()],
+            }),
+            false
+        );
+
+        assert_eq!(
+            filter_1.apply(&SelectedSelectOptions {
+                options: vec![option_1.clone()],
+            }),
+            true,
+        );
+    }
+}

+ 150 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs

@@ -0,0 +1,150 @@
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::ErrorCode;
+use flowy_grid_data_model::revision::GridFilterRevision;
+use std::sync::Arc;
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct GridTextFilter {
+    #[pb(index = 1)]
+    pub condition: TextFilterCondition,
+
+    #[pb(index = 2, one_of)]
+    pub content: Option<String>,
+}
+
+impl GridTextFilter {
+    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,
+                TextFilterCondition::IsNot => &s != content,
+                TextFilterCondition::Contains => s.contains(content),
+                TextFilterCondition::DoesNotContain => !s.contains(content),
+                TextFilterCondition::StartsWith => s.starts_with(content),
+                TextFilterCondition::EndsWith => s.ends_with(content),
+                TextFilterCondition::TextIsEmpty => s.is_empty(),
+                TextFilterCondition::TextIsNotEmpty => !s.is_empty(),
+            }
+        } else {
+            false
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
+#[repr(u8)]
+pub enum TextFilterCondition {
+    Is = 0,
+    IsNot = 1,
+    Contains = 2,
+    DoesNotContain = 3,
+    StartsWith = 4,
+    EndsWith = 5,
+    TextIsEmpty = 6,
+    TextIsNotEmpty = 7,
+}
+
+impl std::convert::From<TextFilterCondition> for i32 {
+    fn from(value: TextFilterCondition) -> Self {
+        value as i32
+    }
+}
+
+impl std::default::Default for TextFilterCondition {
+    fn default() -> Self {
+        TextFilterCondition::Is
+    }
+}
+impl std::convert::TryFrom<u8> for TextFilterCondition {
+    type Error = ErrorCode;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0 => Ok(TextFilterCondition::Is),
+            1 => Ok(TextFilterCondition::IsNot),
+            2 => Ok(TextFilterCondition::Contains),
+            3 => Ok(TextFilterCondition::DoesNotContain),
+            4 => Ok(TextFilterCondition::StartsWith),
+            5 => Ok(TextFilterCondition::EndsWith),
+            6 => Ok(TextFilterCondition::TextIsEmpty),
+            7 => Ok(TextFilterCondition::TextIsNotEmpty),
+            _ => Err(ErrorCode::InvalidData),
+        }
+    }
+}
+
+impl std::convert::From<Arc<GridFilterRevision>> for GridTextFilter {
+    fn from(rev: Arc<GridFilterRevision>) -> Self {
+        GridTextFilter {
+            condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is),
+            content: rev.content.clone(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    #![allow(clippy::all)]
+    use crate::entities::{GridTextFilter, TextFilterCondition};
+
+    #[test]
+    fn text_filter_equal_test() {
+        let text_filter = GridTextFilter {
+            condition: TextFilterCondition::Is,
+            content: Some("appflowy".to_owned()),
+        };
+
+        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);
+    }
+    #[test]
+    fn text_filter_start_with_test() {
+        let text_filter = GridTextFilter {
+            condition: TextFilterCondition::StartsWith,
+            content: Some("appflowy".to_owned()),
+        };
+
+        assert_eq!(text_filter.apply("AppFlowy.io"), true);
+        assert_eq!(text_filter.apply(""), false);
+        assert_eq!(text_filter.apply("https"), false);
+    }
+
+    #[test]
+    fn text_filter_end_with_test() {
+        let text_filter = GridTextFilter {
+            condition: TextFilterCondition::EndsWith,
+            content: Some("appflowy".to_owned()),
+        };
+
+        assert_eq!(text_filter.apply("https://github.com/appflowy"), true);
+        assert_eq!(text_filter.apply("App"), false);
+        assert_eq!(text_filter.apply("appflowy.io"), false);
+    }
+    #[test]
+    fn text_filter_empty_test() {
+        let text_filter = GridTextFilter {
+            condition: TextFilterCondition::TextIsEmpty,
+            content: Some("appflowy".to_owned()),
+        };
+
+        assert_eq!(text_filter.apply(""), true);
+        assert_eq!(text_filter.apply("App"), false);
+    }
+    #[test]
+    fn text_filter_contain_test() {
+        let text_filter = GridTextFilter {
+            condition: TextFilterCondition::Contains,
+            content: Some("appflowy".to_owned()),
+        };
+
+        assert_eq!(text_filter.apply("https://github.com/appflowy"), true);
+        assert_eq!(text_filter.apply("AppFlowy"), true);
+        assert_eq!(text_filter.apply("App"), false);
+        assert_eq!(text_filter.apply(""), false);
+        assert_eq!(text_filter.apply("github"), false);
+    }
+}

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

@@ -0,0 +1,128 @@
+use crate::entities::{
+    CheckboxCondition, DateFilterCondition, FieldType, NumberFilterCondition, SelectOptionCondition,
+    TextFilterCondition,
+};
+use flowy_derive::ProtoBuf;
+use flowy_error::ErrorCode;
+use flowy_grid_data_model::parser::NotEmptyStr;
+use flowy_grid_data_model::revision::{FieldRevision, GridFilterRevision};
+use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams};
+use std::convert::TryInto;
+use std::sync::Arc;
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct GridFilter {
+    #[pb(index = 1)]
+    pub id: String,
+}
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct RepeatedGridFilter {
+    #[pb(index = 1)]
+    pub items: Vec<GridFilter>,
+}
+
+impl std::convert::From<&Arc<GridFilterRevision>> for GridFilter {
+    fn from(rev: &Arc<GridFilterRevision>) -> Self {
+        Self { id: rev.id.clone() }
+    }
+}
+
+impl std::convert::From<&Vec<Arc<GridFilterRevision>>> for RepeatedGridFilter {
+    fn from(revs: &Vec<Arc<GridFilterRevision>>) -> Self {
+        RepeatedGridFilter {
+            items: revs.iter().map(|rev| rev.into()).collect(),
+        }
+    }
+}
+
+impl std::convert::From<Vec<GridFilter>> for RepeatedGridFilter {
+    fn from(items: Vec<GridFilter>) -> Self {
+        Self { items }
+    }
+}
+
+#[derive(ProtoBuf, Debug, Default, Clone)]
+pub struct DeleteFilterPayload {
+    #[pb(index = 1)]
+    pub filter_id: String,
+
+    #[pb(index = 2)]
+    pub field_type: FieldType,
+}
+
+impl TryInto<DeleteFilterParams> for DeleteFilterPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<DeleteFilterParams, Self::Error> {
+        let filter_id = NotEmptyStr::parse(self.filter_id)
+            .map_err(|_| ErrorCode::UnexpectedEmptyString)?
+            .0;
+        Ok(DeleteFilterParams {
+            filter_id,
+            field_type_rev: self.field_type.into(),
+        })
+    }
+}
+
+#[derive(ProtoBuf, Debug, Default, Clone)]
+pub struct CreateGridFilterPayload {
+    #[pb(index = 1)]
+    pub field_id: String,
+
+    #[pb(index = 2)]
+    pub field_type: FieldType,
+
+    #[pb(index = 3)]
+    pub condition: i32,
+
+    #[pb(index = 4, one_of)]
+    pub content: Option<String>,
+}
+
+impl CreateGridFilterPayload {
+    #[allow(dead_code)]
+    pub fn new<T: Into<i32>>(field_rev: &FieldRevision, condition: T, content: Option<String>) -> Self {
+        Self {
+            field_id: field_rev.id.clone(),
+            field_type: field_rev.field_type_rev.into(),
+            condition: condition.into(),
+            content,
+        }
+    }
+}
+
+impl TryInto<CreateGridFilterParams> for CreateGridFilterPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<CreateGridFilterParams, Self::Error> {
+        let field_id = NotEmptyStr::parse(self.field_id)
+            .map_err(|_| ErrorCode::FieldIdIsEmpty)?
+            .0;
+        let condition = self.condition as u8;
+        match self.field_type {
+            FieldType::RichText | FieldType::URL => {
+                let _ = TextFilterCondition::try_from(condition)?;
+            }
+            FieldType::Checkbox => {
+                let _ = CheckboxCondition::try_from(condition)?;
+            }
+            FieldType::Number => {
+                let _ = NumberFilterCondition::try_from(condition)?;
+            }
+            FieldType::DateTime => {
+                let _ = DateFilterCondition::try_from(condition)?;
+            }
+            FieldType::SingleSelect | FieldType::MultiSelect => {
+                let _ = SelectOptionCondition::try_from(condition)?;
+            }
+        }
+
+        Ok(CreateGridFilterParams {
+            field_id,
+            field_type_rev: self.field_type.into(),
+            condition,
+            content: self.content,
+        })
+    }
+}

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

@@ -1,7 +1,10 @@
 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 crate::services::row::AnyCellData;
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_grid_data_model::revision::FieldRevision;
 use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams};
@@ -362,7 +365,8 @@ pub(crate) async fn get_select_option_handler(
         Some(field_rev) => {
             let cell_rev = editor.get_cell_rev(&params.row_id, &params.field_id).await?;
             let type_option = select_option_operation(&field_rev)?;
-            let option_context = type_option.select_option_cell_data(&cell_rev);
+            let any_cell_data: AnyCellData = cell_rev.try_into()?;
+            let option_context = type_option.selected_select_option(any_cell_data);
             data_result(option_context)
         }
     }

+ 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::*;

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

@@ -0,0 +1,337 @@
+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::{FieldRevision, TypeOptionDataEntry};
+use nanoid::nanoid;
+use serde::{Deserialize, Serialize};
+
+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_selected_select_options<T: TryInto<AnyCellData>>(
+    any_cell_data: T,
+    options: &[SelectOption],
+) -> Vec<SelectOption> {
+    if let Ok(type_option_cell_data) = any_cell_data.try_into() {
+        let ids = SelectOptionIds::from(type_option_cell_data.cell_data);
+        ids.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 selected_select_option(&self, any_cell_data: AnyCellData) -> 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 SelectOptionIds {
+    pub fn into_inner(self) -> Vec<String> {
+        self.0
+    }
+}
+
+impl std::convert::TryFrom<AnyCellData> for SelectOptionIds {
+    type Error = FlowyError;
+
+    fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
+        Ok(Self::from(value.cell_data))
+    }
+}
+
+impl std::convert::From<String> for SelectOptionIds {
+    fn from(s: String) -> Self {
+        let ids = s
+            .split(SELECTION_IDS_SEPARATOR)
+            .map(|id| id.to_string())
+            .collect::<Vec<String>>();
+        Self(ids)
+    }
+}
+
+impl std::convert::From<Option<String>> for SelectOptionIds {
+    fn from(s: Option<String>) -> Self {
+        match s {
+            None => Self { 0: vec![] },
+            Some(s) => Self::from(s),
+        }
+    }
+}
+
+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
+    }
+}
+
+#[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,
+        })
+    }
+}
+
+pub struct SelectedSelectOptions {
+    pub(crate) options: Vec<SelectOption>,
+}
+
+impl std::convert::From<SelectOptionCellData> for SelectedSelectOptions {
+    fn from(data: SelectOptionCellData) -> Self {
+        Self {
+            options: data.select_options,
+        }
+    }
+}

+ 19 - 7
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs

@@ -42,9 +42,13 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox);
 const YES: &str = "Yes";
 const NO: &str = "No";
 
-impl CellFilterOperation<GridCheckboxFilter, CheckboxCellData> for CheckboxTypeOption {
-    fn apply_filter(&self, _cell_data: CheckboxCellData, _filter: &GridCheckboxFilter) -> bool {
-        false
+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(filter.apply(&checkbox_cell_data))
     }
 }
 
@@ -96,10 +100,18 @@ fn string_to_bool(bool_str: &str) -> bool {
     }
 }
 
-pub struct CheckboxCellData(String);
-impl std::convert::From<AnyCellData> for CheckboxCellData {
-    fn from(any_cell_data: AnyCellData) -> Self {
-        CheckboxCellData(any_cell_data.cell_data)
+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!()
     }
 }
 

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

@@ -117,9 +117,12 @@ impl DateTypeOption {
     }
 }
 
-impl CellFilterOperation<GridDateFilter, AnyCellData> for DateTypeOption {
-    fn apply_filter(&self, _cell_data: AnyCellData, _filter: &GridDateFilter) -> bool {
-        false
+impl CellFilterOperation<GridDateFilter> for DateTypeOption {
+    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::*;

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

@@ -0,0 +1,217 @@
+use crate::entities::{FieldType, GridSelectOptionFilter};
+
+use crate::impl_type_option;
+use crate::services::field::select_option::{
+    make_selected_select_options, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData,
+    SelectOptionIds, SelectOptionOperation, SelectedSelectOptions, 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 selected_select_option(&self, any_cell_data: AnyCellData) -> SelectOptionCellData {
+        let select_options = make_selected_select_options(any_cell_data, &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 selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data));
+        Ok(filter.apply(&selected_options))
+    }
+}
+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,
+        );
+    }
+}

+ 111 - 104
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs

@@ -1,6 +1,7 @@
 use crate::impl_type_option;
 
 use crate::entities::{FieldType, GridNumberFilter};
+use crate::services::field::number_currency::Currency;
 use crate::services::field::type_options::number_type_option::format::*;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
 use crate::services::row::{
@@ -10,7 +11,9 @@ 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;
 
@@ -76,24 +79,13 @@ impl NumberTypeOption {
         Self::default()
     }
 
-    fn cell_content_from_number_str(&self, s: &str) -> FlowyResult<String> {
+    fn format_cell_data(&self, s: &str) -> FlowyResult<NumberCellData> {
         match self.format {
-            NumberFormat::Num => {
-                if let Ok(v) = s.parse::<f64>() {
-                    return Ok(v.to_string());
-                }
-
-                if let Ok(v) = s.parse::<i64>() {
-                    return Ok(v.to_string());
-                }
-
-                Ok("".to_string())
-            }
-            NumberFormat::Percent => {
-                let content = s.parse::<f64>().map_or(String::new(), |v| v.to_string());
-                Ok(content)
-            }
-            _ => self.money_from_number_str(s),
+            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),
         }
     }
 
@@ -101,49 +93,28 @@ impl NumberTypeOption {
         self.format = format;
         self.symbol = format.symbol();
     }
+}
 
-    fn money_from_number_str(&self, s: &str) -> FlowyResult<String> {
-        let mut number = self.strip_currency_symbol(s);
-
-        if s.is_empty() {
-            return Ok("".to_string());
-        }
-
-        match Decimal::from_str(&number) {
-            Ok(mut decimal) => {
-                decimal.set_sign_positive(self.sign_positive);
-                let money = rusty_money::Money::from_decimal(decimal, self.format.currency()).to_string();
-                Ok(money)
-            }
-            Err(_) => match rusty_money::Money::from_str(&number, self.format.currency()) {
-                Ok(money) => Ok(money.to_string()),
-                Err(_) => {
-                    number.retain(|c| !STRIP_SYMBOL.contains(&c.to_string()));
-                    if number.chars().all(char::is_numeric) {
-                        self.money_from_number_str(&number)
-                    } else {
-                        Err(FlowyError::invalid_data().context("Should only contain numbers"))
-                    }
-                }
-            },
-        }
-    }
-
-    fn strip_currency_symbol<T: ToString>(&self, 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;
-            }
+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
     }
+    s
 }
-impl CellFilterOperation<GridNumberFilter, AnyCellData> for NumberTypeOption {
-    fn apply_filter(&self, any_cell_data: AnyCellData, _filter: &GridNumberFilter) -> bool {
-        let _number_cell_data = NumberCellData::from_number_type_option(self, any_cell_data);
-        false
+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)?;
+
+        Ok(filter.apply(&num_cell_data))
     }
 }
 
@@ -162,28 +133,9 @@ impl CellDataOperation<String> for NumberTypeOption {
         }
 
         let cell_data = cell_data.into();
-        match self.format {
-            NumberFormat::Num => {
-                if let Ok(v) = cell_data.parse::<f64>() {
-                    return Ok(DecodedCellData::new(v.to_string()));
-                }
-
-                if let Ok(v) = cell_data.parse::<i64>() {
-                    return Ok(DecodedCellData::new(v.to_string()));
-                }
-
-                Ok(DecodedCellData::default())
-            }
-            NumberFormat::Percent => {
-                let content = cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string());
-                Ok(DecodedCellData::new(content))
-            }
-            _ => {
-                let content = self
-                    .money_from_number_str(&cell_data)
-                    .unwrap_or_else(|_| "".to_string());
-                Ok(DecodedCellData::new(content))
-            }
+        match self.format_cell_data(&cell_data) {
+            Ok(num) => Ok(DecodedCellData::new(num.to_string())),
+            Err(_) => Ok(DecodedCellData::default()),
         }
     }
 
@@ -193,7 +145,7 @@ impl CellDataOperation<String> for NumberTypeOption {
     {
         let changeset = changeset.into();
         let data = changeset.trim().to_string();
-        let _ = self.cell_content_from_number_str(&data)?;
+        let _ = self.format_cell_data(&data)?;
         Ok(data)
     }
 }
@@ -213,33 +165,88 @@ impl std::default::Default for NumberTypeOption {
 }
 
 #[derive(Default)]
-pub struct NumberCellData(String);
+pub struct NumberCellData {
+    decimal: Option<Decimal>,
+    money: Option<String>,
+}
 
 impl NumberCellData {
-    fn from_number_type_option(type_option: &NumberTypeOption, any_cell_data: AnyCellData) -> Self {
-        let cell_data = any_cell_data.cell_data;
-        match type_option.format {
-            NumberFormat::Num => {
-                if let Ok(v) = cell_data.parse::<f64>() {
-                    return Self(v.to_string());
-                }
+    pub fn new() -> Self {
+        Self {
+            decimal: Default::default(),
+            money: None,
+        }
+    }
 
-                if let Ok(v) = cell_data.parse::<i64>() {
-                    return Self(v.to_string());
+    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"))
+                    }
                 }
+            },
+        }
+    }
 
-                Self::default()
-            }
-            NumberFormat::Percent => {
-                let content = cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string());
-                Self(content)
-            }
-            _ => {
-                let content = type_option
-                    .money_from_number_str(&cell_data)
-                    .unwrap_or_else(|_| "".to_string());
-                Self(content)
-            }
+    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(),
         }
     }
 }
@@ -248,7 +255,7 @@ impl NumberCellData {
 mod tests {
     use crate::entities::FieldType;
     use crate::services::field::FieldBuilder;
-    use crate::services::field::{NumberFormat, NumberTypeOption};
+    use crate::services::field::{strip_currency_symbol, NumberFormat, NumberTypeOption};
     use crate::services::row::CellDataOperation;
     use flowy_grid_data_model::revision::FieldRevision;
     use strum::IntoEnumIterator;
@@ -266,10 +273,10 @@ mod tests {
     fn number_type_option_strip_symbol_test() {
         let mut type_option = NumberTypeOption::new();
         type_option.format = NumberFormat::USD;
-        assert_eq!(type_option.strip_currency_symbol("$18,443"), "18,443".to_owned());
+        assert_eq!(strip_currency_symbol("$18,443"), "18,443".to_owned());
 
         type_option.format = NumberFormat::Yuan;
-        assert_eq!(type_option.strip_currency_symbol("$0.2"), "0.2".to_owned());
+        assert_eq!(strip_currency_symbol("$0.2"), "0.2".to_owned());
     }
 
     #[test]

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

@@ -1,660 +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, SelectOptionIds> for SingleSelectTypeOption {
-    fn apply_filter(&self, _cell_data: SelectOptionIds, _filter: &GridSelectOptionFilter) -> bool {
-        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, SelectOptionIds> for MultiSelectTypeOption {
-    fn apply_filter(&self, _cell_data: SelectOptionIds, _filter: &GridSelectOptionFilter) -> bool {
-        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::From<AnyCellData> for SelectOptionIds {
-    fn from(any_cell_data: AnyCellData) -> Self {
-        let ids = select_option_ids(any_cell_data.cell_data);
-        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,
-        );
-    }
-}

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

@@ -0,0 +1,196 @@
+use crate::entities::{FieldType, GridSelectOptionFilter};
+use crate::impl_type_option;
+use crate::services::field::select_option::{
+    make_selected_select_options, 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 selected_select_option(&self, any_cell_data: AnyCellData) -> SelectOptionCellData {
+        let select_options = make_selected_select_options(any_cell_data, &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,
+        );
+    }
+}

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

@@ -32,9 +32,14 @@ pub struct RichTextTypeOption {
 }
 impl_type_option!(RichTextTypeOption, FieldType::RichText);
 
-impl CellFilterOperation<GridTextFilter, TextCellData> for RichTextTypeOption {
-    fn apply_filter(&self, _cell_data: TextCellData, _filter: &GridTextFilter) -> bool {
-        false
+impl CellFilterOperation<GridTextFilter> for RichTextTypeOption {
+    fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult<bool> {
+        if !any_cell_data.is_text() {
+            return Ok(true);
+        }
+
+        let text_cell_data: TextCellData = any_cell_data.try_into()?;
+        Ok(filter.apply(text_cell_data))
     }
 }
 
@@ -73,16 +78,25 @@ impl CellDataOperation<String> for RichTextTypeOption {
     }
 }
 
-pub struct TextCellData(String);
-impl std::convert::From<AnyCellData> for TextCellData {
-    fn from(any_data: AnyCellData) -> Self {
-        TextCellData(any_data.cell_data)
+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;
+
+    fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
+        Ok(TextCellData(value.cell_data))
     }
 }
 
 #[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;

+ 20 - 7
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,
 };
@@ -35,9 +35,14 @@ pub struct URLTypeOption {
 }
 impl_type_option!(URLTypeOption, FieldType::URL);
 
-impl CellFilterOperation<GridTextFilter, URLCellData> for URLTypeOption {
-    fn apply_filter(&self, _cell_data: URLCellData, _filter: &GridTextFilter) -> bool {
-        false
+impl CellFilterOperation<GridTextFilter> for URLTypeOption {
+    fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult<bool> {
+        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))
     }
 }
 
@@ -121,9 +126,17 @@ impl FromStr for URLCellData {
     }
 }
 
-impl std::convert::From<AnyCellData> for URLCellData {
-    fn from(any_cell_data: AnyCellData) -> Self {
-        URLCellData::from_str(&any_cell_data.cell_data).unwrap_or_default()
+// impl std::convert::From<AnyCellData> for URLCellData {
+//     fn from(any_cell_data: AnyCellData) -> Self {
+//         URLCellData::from_str(&any_cell_data.cell_data).unwrap_or_default()
+//     }
+// }
+
+impl std::convert::TryFrom<AnyCellData> for URLCellData {
+    type 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::*;

+ 15 - 8
frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs

@@ -185,54 +185,61 @@ fn filter_cell(
             Some(
                 field_rev
                     .get_type_option_entry::<RichTextTypeOption>(field_type_rev)?
-                    .apply_filter(any_cell_data.into(), filter.value()),
+                    .apply_filter(any_cell_data, filter.value())
+                    .ok(),
             )
         }),
         FieldType::Number => filter_cache.number_filter.get(&filter_id).and_then(|filter| {
             Some(
                 field_rev
                     .get_type_option_entry::<NumberTypeOption>(field_type_rev)?
-                    .apply_filter(any_cell_data, filter.value()),
+                    .apply_filter(any_cell_data, filter.value())
+                    .ok(),
             )
         }),
         FieldType::DateTime => filter_cache.date_filter.get(&filter_id).and_then(|filter| {
             Some(
                 field_rev
                     .get_type_option_entry::<DateTypeOption>(field_type_rev)?
-                    .apply_filter(any_cell_data, filter.value()),
+                    .apply_filter(any_cell_data, filter.value())
+                    .ok(),
             )
         }),
         FieldType::SingleSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| {
             Some(
                 field_rev
                     .get_type_option_entry::<SingleSelectTypeOption>(field_type_rev)?
-                    .apply_filter(any_cell_data.into(), filter.value()),
+                    .apply_filter(any_cell_data, filter.value())
+                    .ok(),
             )
         }),
         FieldType::MultiSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| {
             Some(
                 field_rev
                     .get_type_option_entry::<MultiSelectTypeOption>(field_type_rev)?
-                    .apply_filter(any_cell_data.into(), filter.value()),
+                    .apply_filter(any_cell_data, filter.value())
+                    .ok(),
             )
         }),
         FieldType::Checkbox => filter_cache.checkbox_filter.get(&filter_id).and_then(|filter| {
             Some(
                 field_rev
                     .get_type_option_entry::<CheckboxTypeOption>(field_type_rev)?
-                    .apply_filter(any_cell_data.into(), filter.value()),
+                    .apply_filter(any_cell_data, filter.value())
+                    .ok(),
             )
         }),
         FieldType::URL => filter_cache.url_filter.get(&filter_id).and_then(|filter| {
             Some(
                 field_rev
                     .get_type_option_entry::<URLTypeOption>(field_type_rev)?
-                    .apply_filter(any_cell_data.into(), filter.value()),
+                    .apply_filter(any_cell_data, filter.value())
+                    .ok(),
             )
         }),
     }?;
 
-    let is_visible = !is_hidden;
+    let is_visible = !is_hidden.unwrap_or(false);
     match filter_result.visible_by_field_id.get(&filter_id) {
         None => {
             if is_visible {

+ 25 - 2
frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs

@@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize};
 use std::fmt::Formatter;
 use std::str::FromStr;
 
-pub trait CellFilterOperation<T, C: From<AnyCellData>> {
-    fn apply_filter(&self, cell_data: C, filter: &T) -> bool;
+pub trait CellFilterOperation<T> {
+    fn apply_filter(&self, any_cell_data: AnyCellData, filter: &T) -> FlowyResult<bool>;
 }
 
 pub trait CellDataOperation<D> {
@@ -83,6 +83,25 @@ impl std::convert::TryFrom<&CellRevision> for AnyCellData {
     }
 }
 
+impl std::convert::TryFrom<&Option<CellRevision>> for AnyCellData {
+    type Error = FlowyError;
+
+    fn try_from(value: &Option<CellRevision>) -> Result<Self, Self::Error> {
+        match value {
+            None => Err(FlowyError::invalid_data().context("Expected CellRevision, but receive None")),
+            Some(cell_rev) => AnyCellData::try_from(cell_rev),
+        }
+    }
+}
+
+impl std::convert::TryFrom<Option<CellRevision>> for AnyCellData {
+    type Error = FlowyError;
+
+    fn try_from(value: Option<CellRevision>) -> Result<Self, Self::Error> {
+        Self::try_from(&value)
+    }
+}
+
 impl AnyCellData {
     pub fn new(content: String, field_type: FieldType) -> Self {
         AnyCellData {
@@ -119,6 +138,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 {