Przeglądaj źródła

feat: sort cell (#1593)

* chore: call cell decode data

* chore: cache cell decoded data

* chore: update cache cell data

* chore: cache cell data

* refactor: separate cell type option functionalities

* refactor: add TypeOptionCellDataFilter trait

* chore: remove unused codes

* chore: fix wanrings

* chore: add sort tests

* chore: sort single select and multi select

Co-authored-by: nathan <[email protected]>
Nathan.fooo 2 lat temu
rodzic
commit
5a30f46b85
62 zmienionych plików z 1623 dodań i 844 usunięć
  1. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_persistence.dart
  2. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart
  3. 3 3
      frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart
  4. 2 2
      frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_lists.dart
  5. 2 2
      frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/emoji_picker/src/emoji_lists.dart
  6. 1 0
      frontend/rust-lib/Cargo.lock
  7. 1 0
      frontend/rust-lib/flowy-grid/Cargo.toml
  8. 9 3
      frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs
  9. 13 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs
  10. 13 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/checklist_filter.rs
  11. 21 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs
  12. 13 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs
  13. 14 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs
  14. 13 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs
  15. 1 1
      frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs
  16. 121 0
      frontend/rust-lib/flowy-grid/src/services/cell/cell_data_cache.rs
  17. 24 32
      frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs
  18. 2 0
      frontend/rust-lib/flowy-grid/src/services/cell/mod.rs
  19. 1 17
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs
  20. 36 9
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs
  21. 1 1
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs
  22. 1 19
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_filter.rs
  23. 1 1
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_tests.rs
  24. 37 8
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs
  25. 10 1
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs
  26. 2 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs
  27. 3 20
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_filter.rs
  28. 28 12
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs
  29. 31 9
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/checklist_type_option.rs
  30. 62 25
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs
  31. 2 40
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_filter.rs
  32. 1 1
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_type_option.rs
  33. 50 17
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs
  34. 0 18
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_filter.rs
  35. 8 8
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_tests.rs
  36. 30 15
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs
  37. 23 192
      frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option.rs
  38. 420 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option_cell.rs
  39. 0 1
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs
  40. 0 18
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_filter.rs
  41. 6 31
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_tests.rs
  42. 30 9
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs
  43. 1 1
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option_entities.rs
  44. 0 117
      frontend/rust-lib/flowy-grid/src/services/filter/cache.rs
  45. 61 116
      frontend/rust-lib/flowy-grid/src/services/filter/controller.rs
  46. 0 2
      frontend/rust-lib/flowy-grid/src/services/filter/mod.rs
  47. 20 1
      frontend/rust-lib/flowy-grid/src/services/filter/task.rs
  48. 43 8
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  49. 4 4
      frontend/rust-lib/flowy-grid/src/services/group/controller.rs
  50. 60 45
      frontend/rust-lib/flowy-grid/src/services/sort/controller.rs
  51. 19 4
      frontend/rust-lib/flowy-grid/src/services/sort/entities.rs
  52. 29 11
      frontend/rust-lib/flowy-grid/src/services/view_editor/editor.rs
  53. 15 3
      frontend/rust-lib/flowy-grid/src/services/view_editor/editor_manager.rs
  54. 9 2
      frontend/rust-lib/flowy-grid/src/services/view_editor/trait_impl.rs
  55. 3 1
      frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs
  56. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/filter_test/select_option_filter_test.rs
  57. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs
  58. 2 2
      frontend/rust-lib/flowy-grid/tests/grid/sort_test/mod.rs
  59. 32 8
      frontend/rust-lib/flowy-grid/tests/grid/sort_test/script.rs
  60. 279 0
      frontend/rust-lib/flowy-grid/tests/grid/sort_test/sort_test.rs
  61. 0 0
      frontend/rust-lib/flowy-grid/tests/grid/sort_test/text_sort_test.rs
  62. 6 0
      shared-lib/grid-rev-model/src/sort_rev.rs

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

@@ -63,7 +63,7 @@ class DateCellDataPersistence
 
 CellPathPB _makeCellPath(GridCellIdentifier cellId) {
   return CellPathPB.create()
-    ..gridId = cellId.gridId
+    ..viewId = cellId.gridId
     ..fieldId = cellId.fieldId
     ..rowId = cellId.rowId;
 }

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

@@ -46,7 +46,7 @@ class CellService {
     required GridCellIdentifier cellId,
   }) {
     final payload = CellPathPB.create()
-      ..gridId = cellId.gridId
+      ..viewId = cellId.gridId
       ..fieldId = cellId.fieldId
       ..rowId = cellId.rowId;
     return GridEventGetCell(payload).send();

+ 3 - 3
frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart

@@ -23,7 +23,7 @@ class SelectOptionFFIService {
         return result.fold(
           (option) {
             final cellIdentifier = CellPathPB.create()
-              ..gridId = gridId
+              ..viewId = gridId
               ..fieldId = fieldId
               ..rowId = rowId;
             final payload = SelectOptionChangesetPB.create()
@@ -62,7 +62,7 @@ class SelectOptionFFIService {
 
   Future<Either<SelectOptionCellDataPB, FlowyError>> getOptionContext() {
     final payload = CellPathPB.create()
-      ..gridId = gridId
+      ..viewId = gridId
       ..fieldId = fieldId
       ..rowId = rowId;
 
@@ -87,7 +87,7 @@ class SelectOptionFFIService {
 
   CellPathPB _cellIdentifier() {
     return CellPathPB.create()
-      ..gridId = gridId
+      ..viewId = gridId
       ..fieldId = fieldId
       ..rowId = rowId;
   }

+ 2 - 2
frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_lists.dart

@@ -2706,7 +2706,7 @@ final Map<String, String> flags = Map.fromIterables([
   'Flag: Armenia',
   'Flag: Angola',
   'Flag: Antarctica',
-  'Flag: Argentina',
+  'Flag: argentina',
   'Flag: American Samoa',
   'Flag: Austria',
   'Flag: Australia',
@@ -2775,7 +2775,7 @@ final Map<String, String> flags = Map.fromIterables([
   'Flag: Falkland Islands',
   'Flag: Micronesia',
   'Flag: Faroe Islands',
-  'Flag: France',
+  'Flag: france',
   'Flag: Gabon',
   'Flag: United Kingdom',
   'Flag: Grenada',

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/emoji_picker/src/emoji_lists.dart

@@ -2706,7 +2706,7 @@ final Map<String, String> flags = Map.fromIterables([
   'Flag: Armenia',
   'Flag: Angola',
   'Flag: Antarctica',
-  'Flag: Argentina',
+  'Flag: argentina',
   'Flag: American Samoa',
   'Flag: Austria',
   'Flag: Australia',
@@ -2775,7 +2775,7 @@ final Map<String, String> flags = Map.fromIterables([
   'Flag: Falkland Islands',
   'Flag: Micronesia',
   'Flag: Faroe Islands',
-  'Flag: France',
+  'Flag: france',
   'Flag: Gabon',
   'Flag: United Kingdom',
   'Flag: Grenada',

+ 1 - 0
frontend/rust-lib/Cargo.lock

@@ -1021,6 +1021,7 @@ dependencies = [
  "lib-infra",
  "lib-ot",
  "nanoid",
+ "parking_lot 0.12.1",
  "protobuf",
  "rayon",
  "regex",

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

@@ -45,6 +45,7 @@ futures = "0.3.15"
 atomic_refcell = "0.1.8"
 crossbeam-utils = "0.8.7"
 async-stream = "0.3.2"
+parking_lot = "0.12.1"
 
 [dev-dependencies]
 flowy-test = { path = "../flowy-test" }

+ 9 - 3
frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs

@@ -41,7 +41,7 @@ impl TryInto<CreateSelectOptionParams> for CreateSelectOptionPayloadPB {
 #[derive(Debug, Clone, Default, ProtoBuf)]
 pub struct CellPathPB {
     #[pb(index = 1)]
-    pub grid_id: String,
+    pub view_id: String,
 
     #[pb(index = 2)]
     pub field_id: String,
@@ -50,6 +50,8 @@ pub struct CellPathPB {
     pub row_id: String,
 }
 
+/// Represents as the cell identifier. It's used to locate the cell in corresponding
+/// view's row with the field id.
 pub struct CellPathParams {
     pub view_id: String,
     pub field_id: String,
@@ -60,7 +62,7 @@ impl TryInto<CellPathParams> for CellPathPB {
     type Error = ErrorCode;
 
     fn try_into(self) -> Result<CellPathParams, Self::Error> {
-        let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+        let grid_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
         let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
         let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
         Ok(CellPathParams {
@@ -70,15 +72,19 @@ impl TryInto<CellPathParams> for CellPathPB {
         })
     }
 }
+
+/// Represents as the data of the cell.
 #[derive(Debug, Default, ProtoBuf)]
 pub struct CellPB {
     #[pb(index = 1)]
     pub field_id: String,
 
-    // The data was encoded in field_type's data type
+    /// Encoded the data using the helper struct `CellProtobufBlob`.
+    /// Check out the `CellProtobufBlob` for more information.
     #[pb(index = 2)]
     pub data: Vec<u8>,
 
+    /// the field_type will be None if the field with field_id is not found
     #[pb(index = 3, one_of)]
     pub field_type: Option<FieldType>,
 }

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

@@ -1,3 +1,4 @@
+use crate::services::filter::FromFilterString;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 use grid_rev_model::FilterRevision;
@@ -39,6 +40,18 @@ impl std::convert::TryFrom<u8> for CheckboxFilterConditionPB {
     }
 }
 
+impl FromFilterString for CheckboxFilterPB {
+    fn from_filter_rev(filter_rev: &FilterRevision) -> Self
+    where
+        Self: Sized,
+    {
+        CheckboxFilterPB {
+            condition: CheckboxFilterConditionPB::try_from(filter_rev.condition)
+                .unwrap_or(CheckboxFilterConditionPB::IsChecked),
+        }
+    }
+}
+
 impl std::convert::From<&FilterRevision> for CheckboxFilterPB {
     fn from(rev: &FilterRevision) -> Self {
         CheckboxFilterPB {

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

@@ -1,3 +1,4 @@
+use crate::services::filter::FromFilterString;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 use grid_rev_model::FilterRevision;
@@ -39,6 +40,18 @@ impl std::convert::TryFrom<u8> for ChecklistFilterConditionPB {
     }
 }
 
+impl FromFilterString for ChecklistFilterPB {
+    fn from_filter_rev(filter_rev: &FilterRevision) -> Self
+    where
+        Self: Sized,
+    {
+        ChecklistFilterPB {
+            condition: ChecklistFilterConditionPB::try_from(filter_rev.condition)
+                .unwrap_or(ChecklistFilterConditionPB::IsIncomplete),
+        }
+    }
+}
+
 impl std::convert::From<&FilterRevision> for ChecklistFilterPB {
     fn from(rev: &FilterRevision) -> Self {
         ChecklistFilterPB {

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

@@ -1,3 +1,4 @@
+use crate::services::filter::FromFilterString;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 use grid_rev_model::FilterRevision;
@@ -80,6 +81,26 @@ impl std::convert::TryFrom<u8> for DateFilterConditionPB {
         }
     }
 }
+impl FromFilterString for DateFilterPB {
+    fn from_filter_rev(filter_rev: &FilterRevision) -> Self
+    where
+        Self: Sized,
+    {
+        let condition = DateFilterConditionPB::try_from(filter_rev.condition).unwrap_or(DateFilterConditionPB::DateIs);
+        let mut filter = DateFilterPB {
+            condition,
+            ..Default::default()
+        };
+
+        if let Ok(content) = DateFilterContentPB::from_str(&filter_rev.content) {
+            filter.start = content.start;
+            filter.end = content.end;
+            filter.timestamp = content.timestamp;
+        };
+
+        filter
+    }
+}
 impl std::convert::From<&FilterRevision> for DateFilterPB {
     fn from(rev: &FilterRevision) -> Self {
         let condition = DateFilterConditionPB::try_from(rev.condition).unwrap_or(DateFilterConditionPB::DateIs);

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

@@ -1,3 +1,4 @@
+use crate::services::filter::FromFilterString;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 use grid_rev_model::FilterRevision;
@@ -53,6 +54,18 @@ impl std::convert::TryFrom<u8> for NumberFilterConditionPB {
     }
 }
 
+impl FromFilterString for NumberFilterPB {
+    fn from_filter_rev(filter_rev: &FilterRevision) -> Self
+    where
+        Self: Sized,
+    {
+        NumberFilterPB {
+            condition: NumberFilterConditionPB::try_from(filter_rev.condition)
+                .unwrap_or(NumberFilterConditionPB::Equal),
+            content: filter_rev.content.clone(),
+        }
+    }
+}
 impl std::convert::From<&FilterRevision> for NumberFilterPB {
     fn from(rev: &FilterRevision) -> Self {
         NumberFilterPB {

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

@@ -1,4 +1,5 @@
 use crate::services::field::SelectOptionIds;
+use crate::services::filter::FromFilterString;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 use grid_rev_model::FilterRevision;
@@ -46,6 +47,19 @@ impl std::convert::TryFrom<u8> for SelectOptionConditionPB {
         }
     }
 }
+impl FromFilterString for SelectOptionFilterPB {
+    fn from_filter_rev(filter_rev: &FilterRevision) -> Self
+    where
+        Self: Sized,
+    {
+        let ids = SelectOptionIds::from(filter_rev.content.clone());
+        SelectOptionFilterPB {
+            condition: SelectOptionConditionPB::try_from(filter_rev.condition)
+                .unwrap_or(SelectOptionConditionPB::OptionIs),
+            option_ids: ids.into_inner(),
+        }
+    }
+}
 
 impl std::convert::From<&FilterRevision> for SelectOptionFilterPB {
     fn from(rev: &FilterRevision) -> Self {

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

@@ -1,3 +1,4 @@
+use crate::services::filter::FromFilterString;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 use grid_rev_model::FilterRevision;
@@ -54,6 +55,18 @@ impl std::convert::TryFrom<u8> for TextFilterConditionPB {
     }
 }
 
+impl FromFilterString for TextFilterPB {
+    fn from_filter_rev(filter_rev: &FilterRevision) -> Self
+    where
+        Self: Sized,
+    {
+        TextFilterPB {
+            condition: TextFilterConditionPB::try_from(filter_rev.condition).unwrap_or(TextFilterConditionPB::Is),
+            content: filter_rev.content.clone(),
+        }
+    }
+}
+
 impl std::convert::From<&FilterRevision> for TextFilterPB {
     fn from(rev: &FilterRevision) -> Self {
         TextFilterPB {

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

@@ -130,7 +130,7 @@ impl TryInto<DeleteSortParams> for DeleteSortPayloadPB {
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct DeleteSortParams {
     pub view_id: String,
     pub sort_type: SortType,

+ 121 - 0
frontend/rust-lib/flowy-grid/src/services/cell/cell_data_cache.rs

@@ -0,0 +1,121 @@
+use parking_lot::RwLock;
+use std::any::{type_name, Any};
+
+use std::collections::HashMap;
+
+use crate::services::filter::FilterType;
+use std::fmt::Debug;
+use std::hash::Hash;
+use std::sync::Arc;
+
+pub type AtomicCellDataCache = Arc<RwLock<AnyTypeCache<u64>>>;
+pub type AtomicCellFilterCache = Arc<RwLock<AnyTypeCache<FilterType>>>;
+
+#[derive(Default, Debug)]
+pub struct AnyTypeCache<TypeValueKey>(HashMap<TypeValueKey, TypeValue>);
+
+impl<TypeValueKey> AnyTypeCache<TypeValueKey>
+where
+    TypeValueKey: Clone + Hash + Eq,
+{
+    pub fn new() -> Arc<RwLock<AnyTypeCache<TypeValueKey>>> {
+        Arc::new(RwLock::new(AnyTypeCache(HashMap::default())))
+    }
+
+    pub fn insert<T>(&mut self, key: &TypeValueKey, val: T) -> Option<T>
+    where
+        T: 'static + Send + Sync,
+    {
+        self.0.insert(key.clone(), TypeValue::new(val)).and_then(downcast_owned)
+    }
+
+    pub fn remove(&mut self, key: &TypeValueKey) {
+        self.0.remove(key);
+    }
+
+    // pub fn remove<T, K: AsRef<TypeValueKey>>(&mut self, key: K) -> Option<T>
+    //     where
+    //         T: 'static + Send + Sync,
+    // {
+    //     self.0.remove(key.as_ref()).and_then(downcast_owned)
+    // }
+
+    pub fn get<T>(&self, key: &TypeValueKey) -> Option<&T>
+    where
+        T: 'static + Send + Sync,
+    {
+        self.0.get(key).and_then(|type_value| type_value.boxed.downcast_ref())
+    }
+
+    pub fn get_mut<T>(&mut self, key: &TypeValueKey) -> Option<&mut T>
+    where
+        T: 'static + Send + Sync,
+    {
+        self.0
+            .get_mut(key)
+            .and_then(|type_value| type_value.boxed.downcast_mut())
+    }
+
+    pub fn contains(&self, key: &TypeValueKey) -> bool {
+        self.0.contains_key(key)
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+}
+
+fn downcast_owned<T: 'static + Send + Sync>(type_value: TypeValue) -> Option<T> {
+    type_value.boxed.downcast().ok().map(|boxed| *boxed)
+}
+
+#[derive(Debug)]
+struct TypeValue {
+    boxed: Box<dyn Any + Send + Sync + 'static>,
+    #[allow(dead_code)]
+    ty: &'static str,
+}
+
+impl TypeValue {
+    pub fn new<T>(value: T) -> Self
+    where
+        T: Send + Sync + 'static,
+    {
+        Self {
+            boxed: Box::new(value),
+            ty: type_name::<T>(),
+        }
+    }
+}
+
+impl std::ops::Deref for TypeValue {
+    type Target = Box<dyn Any + Send + Sync + 'static>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.boxed
+    }
+}
+
+impl std::ops::DerefMut for TypeValue {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.boxed
+    }
+}
+
+// #[cfg(test)]
+// mod tests {
+//     use crate::services::cell::CellDataCache;
+//
+//     #[test]
+//     fn test() {
+//         let mut ext = CellDataCache::new();
+//         ext.insert("1", "a".to_string());
+//         ext.insert("2", 2);
+//
+//         let a: &String = ext.get("1").unwrap();
+//         assert_eq!(a, "a");
+//
+//         let a: Option<&usize> = ext.get("1");
+//         assert!(a.is_none());
+//     }
+// }

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

@@ -1,26 +1,10 @@
 use crate::entities::FieldType;
-use crate::services::cell::{CellProtobufBlob, TypeCellData};
+use crate::services::cell::{AtomicCellDataCache, CellProtobufBlob, TypeCellData};
 use crate::services::field::*;
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use grid_rev_model::{CellRevision, FieldRevision};
-use std::cmp::Ordering;
 use std::fmt::Debug;
 
-/// This trait is used when doing filter/search on the grid.
-pub trait CellFilterable: TypeOptionConfiguration {
-    /// Return true if type_cell_data match the filter condition.
-    fn apply_filter(
-        &self,
-        type_cell_data: TypeCellData,
-        filter: &<Self as TypeOptionConfiguration>::CellFilterConfiguration,
-    ) -> FlowyResult<bool>;
-}
-
-pub trait CellComparable {
-    type CellData;
-    fn apply_cmp(&self, cell_data: &Self::CellData, other_cell_data: &Self::CellData) -> Ordering;
-}
-
 /// Decode the opaque cell data into readable format content
 pub trait CellDataDecoder: TypeOption {
     ///
@@ -57,7 +41,7 @@ pub trait CellDataChangeset: TypeOption {
         &self,
         changeset: <Self as TypeOption>::CellChangeset,
         type_cell_data: Option<TypeCellData>,
-    ) -> FlowyResult<String>;
+    ) -> FlowyResult<<Self as TypeOption>::CellData>;
 }
 
 /// changeset: It will be deserialized into specific data base on the FieldType.
@@ -70,6 +54,7 @@ pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
     changeset: C,
     cell_rev: Option<CellRevision>,
     field_rev: T,
+    cell_data_cache: Option<AtomicCellDataCache>,
 ) -> Result<String, FlowyError> {
     let field_rev = field_rev.as_ref();
     let changeset = changeset.to_string();
@@ -80,9 +65,11 @@ pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
         Err(_) => None,
     });
 
-    let cell_data = match FieldRevisionExt::new(field_rev).get_type_option_cell_data_handler(&field_type) {
+    let cell_data = match TypeOptionCellExt::new_with_cell_data_cache(field_rev, cell_data_cache)
+        .get_type_option_cell_data_handler(&field_type)
+    {
         None => "".to_string(),
-        Some(handler) => handler.handle_cell_changeset(changeset, type_cell_data)?,
+        Some(handler) => handler.handle_cell_changeset(changeset, type_cell_data, field_rev)?,
     };
     Ok(TypeCellData::new(cell_data, field_type).to_json())
 }
@@ -90,12 +77,13 @@ pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
 pub fn decode_type_cell_data<T: TryInto<TypeCellData, Error = FlowyError> + Debug>(
     data: T,
     field_rev: &FieldRevision,
+    cell_data_cache: Option<AtomicCellDataCache>,
 ) -> (FieldType, CellProtobufBlob) {
     let to_field_type = field_rev.ty.into();
     match data.try_into() {
         Ok(type_cell_data) => {
             let TypeCellData { cell_str, field_type } = type_cell_data;
-            match try_decode_cell_str(cell_str, &field_type, &to_field_type, field_rev) {
+            match try_decode_cell_str(cell_str, &field_type, &to_field_type, field_rev, cell_data_cache) {
                 Ok(cell_bytes) => (field_type, cell_bytes),
                 Err(e) => {
                     tracing::error!("Decode cell data failed, {:?}", e);
@@ -134,8 +122,11 @@ pub fn try_decode_cell_str(
     from_field_type: &FieldType,
     to_field_type: &FieldType,
     field_rev: &FieldRevision,
+    cell_data_cache: Option<AtomicCellDataCache>,
 ) -> FlowyResult<CellProtobufBlob> {
-    match FieldRevisionExt::new(field_rev).get_type_option_cell_data_handler(to_field_type) {
+    match TypeOptionCellExt::new_with_cell_data_cache(field_rev, cell_data_cache)
+        .get_type_option_cell_data_handler(to_field_type)
+    {
         None => Ok(CellProtobufBlob::default()),
         Some(handler) => handler.handle_cell_str(cell_str, from_field_type, field_rev),
     }
@@ -146,29 +137,30 @@ pub fn try_decode_cell_str(
 /// empty string. For example, The string of the Multi-Select cell will be a list of the option's name
 /// separated by a comma.
 pub fn stringify_cell_data(
-    cell_data: String,
+    cell_str: String,
     from_field_type: &FieldType,
     to_field_type: &FieldType,
     field_rev: &FieldRevision,
 ) -> String {
-    match FieldRevisionExt::new(field_rev).get_type_option_cell_data_handler(to_field_type) {
+    match TypeOptionCellExt::new_with_cell_data_cache(field_rev, None).get_type_option_cell_data_handler(to_field_type)
+    {
         None => "".to_string(),
-        Some(handler) => handler.stringify_cell_str(cell_data, from_field_type, field_rev),
+        Some(handler) => handler.stringify_cell_str(cell_str, from_field_type, field_rev),
     }
 }
 
 pub fn insert_text_cell(s: String, field_rev: &FieldRevision) -> CellRevision {
-    let data = apply_cell_data_changeset(s, None, field_rev).unwrap();
+    let data = apply_cell_data_changeset(s, None, field_rev, None).unwrap();
     CellRevision::new(data)
 }
 
 pub fn insert_number_cell(num: i64, field_rev: &FieldRevision) -> CellRevision {
-    let data = apply_cell_data_changeset(num, None, field_rev).unwrap();
+    let data = apply_cell_data_changeset(num, None, field_rev, None).unwrap();
     CellRevision::new(data)
 }
 
 pub fn insert_url_cell(url: String, field_rev: &FieldRevision) -> CellRevision {
-    let data = apply_cell_data_changeset(url, None, field_rev).unwrap();
+    let data = apply_cell_data_changeset(url, None, field_rev, None).unwrap();
     CellRevision::new(data)
 }
 
@@ -178,7 +170,7 @@ pub fn insert_checkbox_cell(is_check: bool, field_rev: &FieldRevision) -> CellRe
     } else {
         UNCHECK.to_string()
     };
-    let data = apply_cell_data_changeset(s, None, field_rev).unwrap();
+    let data = apply_cell_data_changeset(s, None, field_rev, None).unwrap();
     CellRevision::new(data)
 }
 
@@ -189,19 +181,19 @@ pub fn insert_date_cell(timestamp: i64, field_rev: &FieldRevision) -> CellRevisi
         is_utc: true,
     })
     .unwrap();
-    let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap();
+    let data = apply_cell_data_changeset(cell_data, None, field_rev, None).unwrap();
     CellRevision::new(data)
 }
 
 pub fn insert_select_option_cell(option_ids: Vec<String>, field_rev: &FieldRevision) -> CellRevision {
     let cell_data = SelectOptionCellChangeset::from_insert_options(option_ids).to_str();
-    let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap();
+    let data = apply_cell_data_changeset(cell_data, None, field_rev, None).unwrap();
     CellRevision::new(data)
 }
 
 pub fn delete_select_option_cell(option_ids: Vec<String>, field_rev: &FieldRevision) -> CellRevision {
     let cell_data = SelectOptionCellChangeset::from_delete_options(option_ids).to_str();
-    let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap();
+    let data = apply_cell_data_changeset(cell_data, None, field_rev, None).unwrap();
     CellRevision::new(data)
 }
 

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

@@ -1,5 +1,7 @@
+mod cell_data_cache;
 mod cell_operation;
 mod type_cell_data;
 
+pub use cell_data_cache::*;
 pub use cell_operation::*;
 pub use type_cell_data::*;

+ 1 - 17
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs

@@ -1,7 +1,5 @@
 use crate::entities::{CheckboxFilterConditionPB, CheckboxFilterPB};
-use crate::services::cell::{CellFilterable, TypeCellData};
-use crate::services::field::{CheckboxCellData, CheckboxTypeOptionPB, TypeOptionCellData, TypeOptionConfiguration};
-use flowy_error::FlowyResult;
+use crate::services::field::CheckboxCellData;
 
 impl CheckboxFilterPB {
     pub fn is_visible(&self, cell_data: &CheckboxCellData) -> bool {
@@ -13,20 +11,6 @@ impl CheckboxFilterPB {
     }
 }
 
-impl CellFilterable for CheckboxTypeOptionPB {
-    fn apply_filter(
-        &self,
-        type_cell_data: TypeCellData,
-        filter: &<Self as TypeOptionConfiguration>::CellFilterConfiguration,
-    ) -> FlowyResult<bool> {
-        if !type_cell_data.is_checkbox() {
-            return Ok(true);
-        }
-        let checkbox_cell_data = self.decode_type_option_cell_str(type_cell_data.cell_str)?;
-        Ok(filter.is_visible(&checkbox_cell_data))
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use crate::entities::{CheckboxFilterConditionPB, CheckboxFilterPB};

+ 36 - 9
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs

@@ -2,14 +2,15 @@ use crate::entities::{CheckboxFilterPB, FieldType};
 use crate::impl_type_option;
 use crate::services::cell::{CellDataChangeset, CellDataDecoder, FromCellString, TypeCellData};
 use crate::services::field::{
-    BoxTypeOptionBuilder, CheckboxCellData, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
-    TypeOptionTransform,
+    default_order, BoxTypeOptionBuilder, CheckboxCellData, TypeOption, TypeOptionBuilder, TypeOptionCellData,
+    TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform,
 };
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::FlowyResult;
 use grid_rev_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
 use serde::{Deserialize, Serialize};
+use std::cmp::Ordering;
 use std::str::FromStr;
 
 #[derive(Default)]
@@ -45,14 +46,11 @@ impl TypeOption for CheckboxTypeOptionPB {
     type CellData = CheckboxCellData;
     type CellChangeset = CheckboxCellChangeset;
     type CellProtobufType = CheckboxCellData;
+    type CellFilter = CheckboxFilterPB;
 }
 
 impl TypeOptionTransform for CheckboxTypeOptionPB {}
 
-impl TypeOptionConfiguration for CheckboxTypeOptionPB {
-    type CellFilterConfiguration = CheckboxFilterPB;
-}
-
 impl TypeOptionCellData for CheckboxTypeOptionPB {
     fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType {
         cell_data
@@ -89,8 +87,37 @@ impl CellDataChangeset for CheckboxTypeOptionPB {
         &self,
         changeset: <Self as TypeOption>::CellChangeset,
         _type_cell_data: Option<TypeCellData>,
-    ) -> FlowyResult<String> {
-        let cell_data = CheckboxCellData::from_str(&changeset)?;
-        Ok(cell_data.to_string())
+    ) -> FlowyResult<<Self as TypeOption>::CellData> {
+        let checkbox_cell_data = CheckboxCellData::from_str(&changeset)?;
+        Ok(checkbox_cell_data)
+    }
+}
+
+impl TypeOptionCellDataFilter for CheckboxTypeOptionPB {
+    fn apply_filter(
+        &self,
+        filter: &<Self as TypeOption>::CellFilter,
+        field_type: &FieldType,
+        cell_data: &<Self as TypeOption>::CellData,
+    ) -> bool {
+        if !field_type.is_checkbox() {
+            return true;
+        }
+        filter.is_visible(cell_data)
+    }
+}
+
+impl TypeOptionCellDataCompare for CheckboxTypeOptionPB {
+    fn apply_cmp(
+        &self,
+        cell_data: &<Self as TypeOption>::CellData,
+        other_cell_data: &<Self as TypeOption>::CellData,
+    ) -> Ordering {
+        match (cell_data.is_check(), other_cell_data.is_check()) {
+            (true, true) => Ordering::Equal,
+            (true, false) => Ordering::Greater,
+            (false, true) => Ordering::Less,
+            (false, false) => default_order(),
+        }
     }
 }

+ 1 - 1
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs

@@ -7,7 +7,7 @@ use std::str::FromStr;
 pub const CHECK: &str = "Yes";
 pub const UNCHECK: &str = "No";
 
-#[derive(Default, Debug)]
+#[derive(Default, Debug, Clone)]
 pub struct CheckboxCellData(String);
 
 impl CheckboxCellData {

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

@@ -1,9 +1,6 @@
 use crate::entities::{DateFilterConditionPB, DateFilterPB};
-use crate::services::cell::{CellFilterable, TypeCellData};
-use crate::services::field::{DateTypeOptionPB, TypeOptionCellData, TypeOptionConfiguration};
-use chrono::NaiveDateTime;
 
-use flowy_error::FlowyResult;
+use chrono::NaiveDateTime;
 
 impl DateFilterPB {
     pub fn is_visible<T: Into<Option<i64>>>(&self, cell_timestamp: T) -> bool {
@@ -60,21 +57,6 @@ impl DateFilterPB {
     }
 }
 
-impl CellFilterable for DateTypeOptionPB {
-    fn apply_filter(
-        &self,
-        type_cell_data: TypeCellData,
-        filter: &<Self as TypeOptionConfiguration>::CellFilterConfiguration,
-    ) -> FlowyResult<bool> {
-        if !type_cell_data.is_date() {
-            return Ok(true);
-        }
-
-        let date_cell_data = self.decode_type_option_cell_str(type_cell_data.cell_str)?;
-        Ok(filter.is_visible(date_cell_data))
-    }
-}
-
 #[cfg(test)]
 mod tests {
     #![allow(clippy::all)]

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

@@ -155,7 +155,7 @@ mod tests {
         let encoded_data = type_option.apply_changeset(changeset, None).unwrap();
 
         assert_eq!(
-            decode_cell_data(encoded_data, type_option, field_rev),
+            decode_cell_data(encoded_data.to_string(), type_option, field_rev),
             expected_str.to_owned(),
         );
     }

+ 37 - 8
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs

@@ -2,8 +2,9 @@ use crate::entities::{DateFilterPB, FieldType};
 use crate::impl_type_option;
 use crate::services::cell::{CellDataChangeset, CellDataDecoder, FromCellString, TypeCellData};
 use crate::services::field::{
-    BoxTypeOptionBuilder, DateCellChangeset, DateCellData, DateCellDataPB, DateFormat, TimeFormat, TypeOption,
-    TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration, TypeOptionTransform,
+    default_order, BoxTypeOptionBuilder, DateCellChangeset, DateCellData, DateCellDataPB, DateFormat, TimeFormat,
+    TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
+    TypeOptionTransform,
 };
 use bytes::Bytes;
 use chrono::format::strftime::StrftimeItems;
@@ -12,6 +13,7 @@ use flowy_derive::ProtoBuf;
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use grid_rev_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
 use serde::{Deserialize, Serialize};
+use std::cmp::Ordering;
 
 // Date
 #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
@@ -31,10 +33,7 @@ impl TypeOption for DateTypeOptionPB {
     type CellData = DateCellData;
     type CellChangeset = DateCellChangeset;
     type CellProtobufType = DateCellDataPB;
-}
-
-impl TypeOptionConfiguration for DateTypeOptionPB {
-    type CellFilterConfiguration = DateFilterPB;
+    type CellFilter = DateFilterPB;
 }
 
 impl TypeOptionCellData for DateTypeOptionPB {
@@ -158,7 +157,7 @@ impl CellDataChangeset for DateTypeOptionPB {
         &self,
         changeset: <Self as TypeOption>::CellChangeset,
         _type_cell_data: Option<TypeCellData>,
-    ) -> FlowyResult<String> {
+    ) -> FlowyResult<<Self as TypeOption>::CellData> {
         let cell_data = match changeset.date_timestamp() {
             None => 0,
             Some(date_timestamp) => match (self.include_time, changeset.time) {
@@ -173,7 +172,37 @@ impl CellDataChangeset for DateTypeOptionPB {
             },
         };
 
-        Ok(cell_data.to_string())
+        Ok(DateCellData(Some(cell_data)))
+    }
+}
+
+impl TypeOptionCellDataFilter for DateTypeOptionPB {
+    fn apply_filter(
+        &self,
+        filter: &<Self as TypeOption>::CellFilter,
+        field_type: &FieldType,
+        cell_data: &<Self as TypeOption>::CellData,
+    ) -> bool {
+        if !field_type.is_date() {
+            return true;
+        }
+
+        filter.is_visible(cell_data.0)
+    }
+}
+
+impl TypeOptionCellDataCompare for DateTypeOptionPB {
+    fn apply_cmp(
+        &self,
+        cell_data: &<Self as TypeOption>::CellData,
+        other_cell_data: &<Self as TypeOption>::CellData,
+    ) -> Ordering {
+        match (cell_data.0, other_cell_data.0) {
+            (Some(left), Some(right)) => left.cmp(&right),
+            (Some(_), None) => Ordering::Greater,
+            (None, Some(_)) => Ordering::Less,
+            (None, None) => default_order(),
+        }
     }
 }
 

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

@@ -68,7 +68,7 @@ impl ToString for DateCellChangeset {
     }
 }
 
-#[derive(Default)]
+#[derive(Default, Clone)]
 pub struct DateCellData(pub Option<i64>);
 
 impl std::convert::From<DateCellData> for i64 {
@@ -93,6 +93,15 @@ impl FromCellString for DateCellData {
     }
 }
 
+impl ToString for DateCellData {
+    fn to_string(&self) -> String {
+        match self.0 {
+            None => "".to_string(),
+            Some(val) => val.to_string(),
+        }
+    }
+}
+
 #[derive(Clone, Debug, Copy, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
 pub enum DateFormat {
     Local = 0,

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

@@ -4,6 +4,7 @@ pub mod number_type_option;
 pub mod selection_type_option;
 pub mod text_type_option;
 mod type_option;
+mod type_option_cell;
 pub mod url_type_option;
 
 pub use checkbox_type_option::*;
@@ -12,4 +13,5 @@ pub use number_type_option::*;
 pub use selection_type_option::*;
 pub use text_type_option::*;
 pub use type_option::*;
+pub use type_option_cell::*;
 pub use url_type_option::*;

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

@@ -1,7 +1,7 @@
 use crate::entities::{NumberFilterConditionPB, NumberFilterPB};
-use crate::services::cell::{CellFilterable, TypeCellData};
-use crate::services::field::{NumberCellData, NumberTypeOptionPB, TypeOptionConfiguration};
-use flowy_error::FlowyResult;
+
+use crate::services::field::NumberCellData;
+
 use rust_decimal::prelude::Zero;
 use rust_decimal::Decimal;
 use std::str::FromStr;
@@ -37,23 +37,6 @@ impl NumberFilterPB {
     }
 }
 
-impl CellFilterable for NumberTypeOptionPB {
-    fn apply_filter(
-        &self,
-        type_cell_data: TypeCellData,
-        filter: &<Self as TypeOptionConfiguration>::CellFilterConfiguration,
-    ) -> FlowyResult<bool> {
-        if !type_cell_data.is_number() {
-            return Ok(true);
-        }
-
-        let cell_data = type_cell_data.cell_str;
-        let num_cell_data = self.format_cell_data(&cell_data)?;
-
-        Ok(filter.is_visible(&num_cell_data))
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use crate::entities::{NumberFilterConditionPB, NumberFilterPB};

+ 28 - 12
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs

@@ -1,10 +1,10 @@
 use crate::entities::{FieldType, NumberFilterPB};
 use crate::impl_type_option;
-use crate::services::cell::{CellComparable, CellDataChangeset, CellDataDecoder, TypeCellData};
+use crate::services::cell::{CellDataChangeset, CellDataDecoder, TypeCellData};
 use crate::services::field::type_options::number_type_option::format::*;
 use crate::services::field::{
     BoxTypeOptionBuilder, NumberCellData, StrCellData, TypeOption, TypeOptionBuilder, TypeOptionCellData,
-    TypeOptionConfiguration, TypeOptionTransform,
+    TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform,
 };
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
@@ -77,10 +77,7 @@ impl TypeOption for NumberTypeOptionPB {
     type CellData = StrCellData;
     type CellChangeset = NumberCellChangeset;
     type CellProtobufType = StrCellData;
-}
-
-impl TypeOptionConfiguration for NumberTypeOptionPB {
-    type CellFilterConfiguration = NumberFilterPB;
+    type CellFilter = NumberFilterPB;
 }
 
 impl TypeOptionCellData for NumberTypeOptionPB {
@@ -158,20 +155,39 @@ impl CellDataChangeset for NumberTypeOptionPB {
         &self,
         changeset: <Self as TypeOption>::CellChangeset,
         _type_cell_data: Option<TypeCellData>,
-    ) -> FlowyResult<String> {
+    ) -> FlowyResult<<Self as TypeOption>::CellData> {
         let data = changeset.trim().to_string();
         let _ = self.format_cell_data(&data)?;
-        Ok(data)
+        Ok(StrCellData(data))
     }
 }
-impl CellComparable for NumberTypeOptionPB {
-    type CellData = NumberCellData;
 
-    fn apply_cmp(&self, _cell_data: &Self::CellData, _other_cell_data: &Self::CellData) -> Ordering {
-        Ordering::Equal
+impl TypeOptionCellDataFilter for NumberTypeOptionPB {
+    fn apply_filter(
+        &self,
+        filter: &<Self as TypeOption>::CellFilter,
+        field_type: &FieldType,
+        cell_data: &<Self as TypeOption>::CellData,
+    ) -> bool {
+        if !field_type.is_number() {
+            return true;
+        }
+        match self.format_cell_data(cell_data) {
+            Ok(cell_data) => filter.is_visible(&cell_data),
+            Err(_) => true,
+        }
     }
 }
 
+impl TypeOptionCellDataCompare for NumberTypeOptionPB {
+    fn apply_cmp(
+        &self,
+        cell_data: &<Self as TypeOption>::CellData,
+        other_cell_data: &<Self as TypeOption>::CellData,
+    ) -> Ordering {
+        cell_data.0.cmp(&other_cell_data.0)
+    }
+}
 impl std::default::Default for NumberTypeOptionPB {
     fn default() -> Self {
         let format = NumberFormat::default();

+ 31 - 9
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/checklist_type_option.rs

@@ -1,16 +1,17 @@
 use crate::entities::{ChecklistFilterPB, FieldType};
 use crate::impl_type_option;
 use crate::services::cell::{CellDataChangeset, FromCellString, TypeCellData};
-
 use crate::services::field::{
     BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, SelectOptionPB,
-    SelectTypeOptionSharedAction, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
+    SelectTypeOptionSharedAction, SelectedSelectOptions, TypeOption, TypeOptionBuilder, TypeOptionCellData,
+    TypeOptionCellDataCompare, TypeOptionCellDataFilter,
 };
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::FlowyResult;
 use grid_rev_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
 use serde::{Deserialize, Serialize};
+use std::cmp::Ordering;
 
 // Multiple select
 #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
@@ -27,10 +28,7 @@ impl TypeOption for ChecklistTypeOptionPB {
     type CellData = SelectOptionIds;
     type CellChangeset = SelectOptionCellChangeset;
     type CellProtobufType = SelectOptionCellDataPB;
-}
-
-impl TypeOptionConfiguration for ChecklistTypeOptionPB {
-    type CellFilterConfiguration = ChecklistFilterPB;
+    type CellFilter = ChecklistFilterPB;
 }
 
 impl TypeOptionCellData for ChecklistTypeOptionPB {
@@ -62,7 +60,7 @@ impl CellDataChangeset for ChecklistTypeOptionPB {
         &self,
         changeset: <Self as TypeOption>::CellChangeset,
         type_cell_data: Option<TypeCellData>,
-    ) -> FlowyResult<String> {
+    ) -> FlowyResult<<Self as TypeOption>::CellData> {
         let insert_option_ids = changeset
             .insert_option_ids
             .into_iter()
@@ -70,7 +68,7 @@ impl CellDataChangeset for ChecklistTypeOptionPB {
             .collect::<Vec<String>>();
 
         match type_cell_data {
-            None => Ok(SelectOptionIds::from(insert_option_ids).to_string()),
+            None => Ok(SelectOptionIds::from(insert_option_ids)),
             Some(type_cell_data) => {
                 let mut select_ids: SelectOptionIds = type_cell_data.cell_str.into();
                 for insert_option_id in insert_option_ids {
@@ -83,11 +81,35 @@ impl CellDataChangeset for ChecklistTypeOptionPB {
                     select_ids.retain(|id| id != &delete_option_id);
                 }
 
-                Ok(select_ids.to_string())
+                Ok(select_ids)
             }
         }
     }
 }
+impl TypeOptionCellDataFilter for ChecklistTypeOptionPB {
+    fn apply_filter(
+        &self,
+        filter: &<Self as TypeOption>::CellFilter,
+        field_type: &FieldType,
+        cell_data: &<Self as TypeOption>::CellData,
+    ) -> bool {
+        if !field_type.is_check_list() {
+            return true;
+        }
+        let selected_options = SelectedSelectOptions::from(self.get_selected_options(cell_data.clone()));
+        filter.is_visible(&self.options, &selected_options)
+    }
+}
+
+impl TypeOptionCellDataCompare for ChecklistTypeOptionPB {
+    fn apply_cmp(
+        &self,
+        cell_data: &<Self as TypeOption>::CellData,
+        other_cell_data: &<Self as TypeOption>::CellData,
+    ) -> Ordering {
+        cell_data.len().cmp(&other_cell_data.len())
+    }
+}
 
 #[derive(Default)]
 pub struct ChecklistTypeOptionBuilder(ChecklistTypeOptionPB);

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

@@ -1,10 +1,12 @@
 use crate::entities::{FieldType, SelectOptionFilterPB};
 use crate::impl_type_option;
 use crate::services::cell::{CellDataChangeset, FromCellString, TypeCellData};
+use std::cmp::{min, Ordering};
 
 use crate::services::field::{
-    BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, SelectOptionPB,
-    SelectTypeOptionSharedAction, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
+    default_order, BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds,
+    SelectOptionPB, SelectTypeOptionSharedAction, SelectedSelectOptions, TypeOption, TypeOptionBuilder,
+    TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
 };
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
@@ -27,10 +29,7 @@ impl TypeOption for MultiSelectTypeOptionPB {
     type CellData = SelectOptionIds;
     type CellChangeset = SelectOptionCellChangeset;
     type CellProtobufType = SelectOptionCellDataPB;
-}
-
-impl TypeOptionConfiguration for MultiSelectTypeOptionPB {
-    type CellFilterConfiguration = SelectOptionFilterPB;
+    type CellFilter = SelectOptionFilterPB;
 }
 
 impl TypeOptionCellData for MultiSelectTypeOptionPB {
@@ -62,18 +61,15 @@ impl CellDataChangeset for MultiSelectTypeOptionPB {
         &self,
         changeset: <Self as TypeOption>::CellChangeset,
         type_cell_data: Option<TypeCellData>,
-    ) -> FlowyResult<String> {
+    ) -> FlowyResult<<Self as TypeOption>::CellData> {
         let insert_option_ids = changeset
             .insert_option_ids
             .into_iter()
             .filter(|insert_option_id| self.options.iter().any(|option| &option.id == insert_option_id))
             .collect::<Vec<String>>();
 
-        let new_cell_data: String;
         match type_cell_data {
-            None => {
-                new_cell_data = SelectOptionIds::from(insert_option_ids).to_string();
-            }
+            None => Ok(SelectOptionIds::from(insert_option_ids)),
             Some(type_cell_data) => {
                 let mut select_ids: SelectOptionIds = type_cell_data.cell_str.into();
                 for insert_option_id in insert_option_ids {
@@ -86,15 +82,56 @@ impl CellDataChangeset for MultiSelectTypeOptionPB {
                     select_ids.retain(|id| id != &delete_option_id);
                 }
 
-                new_cell_data = select_ids.to_string();
-                tracing::trace!("Multi-select cell data: {}", &new_cell_data);
+                tracing::trace!("Multi-select cell data: {}", select_ids.to_string());
+                Ok(select_ids)
             }
         }
+    }
+}
 
-        Ok(new_cell_data)
+impl TypeOptionCellDataFilter for MultiSelectTypeOptionPB {
+    fn apply_filter(
+        &self,
+        filter: &<Self as TypeOption>::CellFilter,
+        field_type: &FieldType,
+        cell_data: &<Self as TypeOption>::CellData,
+    ) -> bool {
+        if !field_type.is_multi_select() {
+            return true;
+        }
+        let selected_options = SelectedSelectOptions::from(self.get_selected_options(cell_data.clone()));
+        filter.is_visible(&selected_options, FieldType::MultiSelect)
     }
 }
 
+impl TypeOptionCellDataCompare for MultiSelectTypeOptionPB {
+    fn apply_cmp(
+        &self,
+        cell_data: &<Self as TypeOption>::CellData,
+        other_cell_data: &<Self as TypeOption>::CellData,
+    ) -> Ordering {
+        for i in 0..min(cell_data.len(), other_cell_data.len()) {
+            let order = match (
+                cell_data
+                    .get(i)
+                    .and_then(|id| self.options.iter().find(|option| &option.id == id)),
+                other_cell_data
+                    .get(i)
+                    .and_then(|id| self.options.iter().find(|option| &option.id == id)),
+            ) {
+                (Some(left), Some(right)) => left.name.cmp(&right.name),
+                (Some(_), None) => Ordering::Greater,
+                (None, Some(_)) => Ordering::Less,
+                (None, None) => default_order(),
+            };
+
+            if order.is_ne() {
+                return order;
+            }
+        }
+        default_order()
+    }
+}
 #[derive(Default)]
 pub struct MultiSelectTypeOptionBuilder(MultiSelectTypeOptionPB);
 impl_into_box_type_option_builder!(MultiSelectTypeOptionBuilder);
@@ -173,7 +210,7 @@ mod tests {
         let type_option = MultiSelectTypeOptionPB::from(&field_rev);
         let option_ids = vec![google.id, facebook.id];
         let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone());
-        let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap().into();
+        let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap();
 
         assert_eq!(&*select_option_ids, &option_ids);
     }
@@ -192,12 +229,12 @@ mod tests {
 
         // insert
         let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone());
-        let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap().into();
+        let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
         assert_eq!(&*select_option_ids, &option_ids);
 
         // delete
         let changeset = SelectOptionCellChangeset::from_delete_options(option_ids);
-        let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap().into();
+        let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
         assert!(select_option_ids.is_empty());
     }
 
@@ -213,8 +250,8 @@ mod tests {
 
         let type_option = MultiSelectTypeOptionPB::from(&field_rev);
         let changeset = SelectOptionCellChangeset::from_insert_option_id(&google.id);
-        let cell_option_ids = type_option.apply_changeset(changeset, None).unwrap();
-        assert_eq!(cell_option_ids, google.id);
+        let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
+        assert_eq!(select_option_ids.to_string(), google.id);
     }
 
     #[test]
@@ -228,8 +265,8 @@ mod tests {
 
         let type_option = MultiSelectTypeOptionPB::from(&field_rev);
         let changeset = SelectOptionCellChangeset::from_insert_option_id(&google.id);
-        let cell_option_ids = type_option.apply_changeset(changeset, None).unwrap();
-        assert!(cell_option_ids.is_empty());
+        let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
+        assert!(select_option_ids.is_empty());
     }
 
     #[test]
@@ -246,11 +283,11 @@ mod tests {
 
         // empty option id string
         let changeset = SelectOptionCellChangeset::from_insert_option_id("");
-        let cell_option_ids = type_option.apply_changeset(changeset, None).unwrap();
-        assert_eq!(cell_option_ids, "");
+        let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
+        assert_eq!(select_option_ids.to_string(), "");
 
         let changeset = SelectOptionCellChangeset::from_insert_option_id("123,456");
-        let cell_option_ids = type_option.apply_changeset(changeset, None).unwrap();
-        assert_eq!(cell_option_ids, "");
+        let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
+        assert!(select_option_ids.is_empty());
     }
 }

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

@@ -1,12 +1,7 @@
 #![allow(clippy::needless_collect)]
 
-use crate::entities::{ChecklistFilterPB, FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
-use crate::services::cell::{CellFilterable, TypeCellData};
-use crate::services::field::{
-    ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB, TypeOptionCellData,
-};
-use crate::services::field::{SelectTypeOptionSharedAction, SelectedSelectOptions};
-use flowy_error::FlowyResult;
+use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
+use crate::services::field::SelectedSelectOptions;
 
 impl SelectOptionFilterPB {
     pub fn is_visible(&self, selected_options: &SelectedSelectOptions, field_type: FieldType) -> bool {
@@ -80,39 +75,6 @@ impl SelectOptionFilterPB {
     }
 }
 
-impl CellFilterable for MultiSelectTypeOptionPB {
-    fn apply_filter(&self, type_cell_data: TypeCellData, filter: &SelectOptionFilterPB) -> FlowyResult<bool> {
-        if !type_cell_data.is_multi_select() {
-            return Ok(true);
-        }
-        let ids = self.decode_type_option_cell_str(type_cell_data.cell_str)?;
-        let selected_options = SelectedSelectOptions::from(self.get_selected_options(ids));
-        Ok(filter.is_visible(&selected_options, FieldType::MultiSelect))
-    }
-}
-
-impl CellFilterable for SingleSelectTypeOptionPB {
-    fn apply_filter(&self, type_cell_data: TypeCellData, filter: &SelectOptionFilterPB) -> FlowyResult<bool> {
-        if !type_cell_data.is_single_select() {
-            return Ok(true);
-        }
-        let ids = self.decode_type_option_cell_str(type_cell_data.cell_str)?;
-        let selected_options = SelectedSelectOptions::from(self.get_selected_options(ids));
-        Ok(filter.is_visible(&selected_options, FieldType::SingleSelect))
-    }
-}
-
-impl CellFilterable for ChecklistTypeOptionPB {
-    fn apply_filter(&self, type_cell_data: TypeCellData, filter: &ChecklistFilterPB) -> FlowyResult<bool> {
-        if !type_cell_data.is_checklist() {
-            return Ok(true);
-        }
-        let ids = self.decode_type_option_cell_str(type_cell_data.cell_str)?;
-        let selected_options = SelectedSelectOptions::from(self.get_selected_options(ids));
-        Ok(filter.is_visible(&self.options, &selected_options))
-    }
-}
-
 #[cfg(test)]
 mod tests {
     #![allow(clippy::all)]

+ 1 - 1
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_type_option.rs

@@ -252,7 +252,7 @@ pub fn new_select_option_color(options: &Vec<SelectOptionPB>) -> SelectOptionCol
 /// Calls [to_string] will return a string consists list of ids,
 /// placing a commas separator between each
 ///
-#[derive(Default)]
+#[derive(Default, Clone)]
 pub struct SelectOptionIds(Vec<String>);
 
 impl SelectOptionIds {

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

@@ -1,10 +1,11 @@
 use crate::entities::{FieldType, SelectOptionFilterPB};
 use crate::impl_type_option;
 use crate::services::cell::{CellDataChangeset, FromCellString, TypeCellData};
+use std::cmp::Ordering;
 
 use crate::services::field::{
-    BoxTypeOptionBuilder, SelectOptionCellDataPB, TypeOption, TypeOptionBuilder, TypeOptionCellData,
-    TypeOptionConfiguration,
+    default_order, BoxTypeOptionBuilder, SelectOptionCellDataPB, SelectedSelectOptions, TypeOption, TypeOptionBuilder,
+    TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
 };
 use crate::services::field::{
     SelectOptionCellChangeset, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction,
@@ -30,10 +31,7 @@ impl TypeOption for SingleSelectTypeOptionPB {
     type CellData = SelectOptionIds;
     type CellChangeset = SelectOptionCellChangeset;
     type CellProtobufType = SelectOptionCellDataPB;
-}
-
-impl TypeOptionConfiguration for SingleSelectTypeOptionPB {
-    type CellFilterConfiguration = SelectOptionFilterPB;
+    type CellFilter = SelectOptionFilterPB;
 }
 
 impl TypeOptionCellData for SingleSelectTypeOptionPB {
@@ -65,7 +63,7 @@ impl CellDataChangeset for SingleSelectTypeOptionPB {
         &self,
         changeset: <Self as TypeOption>::CellChangeset,
         _type_cell_data: Option<TypeCellData>,
-    ) -> FlowyResult<String> {
+    ) -> FlowyResult<<Self as TypeOption>::CellData> {
         let mut insert_option_ids = changeset
             .insert_option_ids
             .into_iter()
@@ -76,15 +74,51 @@ impl CellDataChangeset for SingleSelectTypeOptionPB {
         // Sometimes, the insert_option_ids may contain list of option ids. For example,
         // copy/paste a ids string.
         if insert_option_ids.is_empty() {
-            Ok("".to_string())
+            Ok(SelectOptionIds::from(insert_option_ids))
         } else {
             // Just take the first select option
             let _ = insert_option_ids.drain(1..);
-            Ok(insert_option_ids.pop().unwrap())
+            Ok(SelectOptionIds::from(insert_option_ids))
+        }
+    }
+}
+
+impl TypeOptionCellDataFilter for SingleSelectTypeOptionPB {
+    fn apply_filter(
+        &self,
+        filter: &<Self as TypeOption>::CellFilter,
+        field_type: &FieldType,
+        cell_data: &<Self as TypeOption>::CellData,
+    ) -> bool {
+        if !field_type.is_single_select() {
+            return true;
         }
+        let selected_options = SelectedSelectOptions::from(self.get_selected_options(cell_data.clone()));
+        filter.is_visible(&selected_options, FieldType::SingleSelect)
     }
 }
 
+impl TypeOptionCellDataCompare for SingleSelectTypeOptionPB {
+    fn apply_cmp(
+        &self,
+        cell_data: &<Self as TypeOption>::CellData,
+        other_cell_data: &<Self as TypeOption>::CellData,
+    ) -> Ordering {
+        match (
+            cell_data
+                .first()
+                .and_then(|id| self.options.iter().find(|option| &option.id == id)),
+            other_cell_data
+                .first()
+                .and_then(|id| self.options.iter().find(|option| &option.id == id)),
+        ) {
+            (Some(left), Some(right)) => left.name.cmp(&right.name),
+            (Some(_), None) => Ordering::Greater,
+            (None, Some(_)) => Ordering::Less,
+            (None, None) => default_order(),
+        }
+    }
+}
 #[derive(Default)]
 pub struct SingleSelectTypeOptionBuilder(SingleSelectTypeOptionPB);
 impl_into_box_type_option_builder!(SingleSelectTypeOptionBuilder);
@@ -161,8 +195,7 @@ mod tests {
         let type_option = SingleSelectTypeOptionPB::from(&field_rev);
         let option_ids = vec![google.id.clone(), facebook.id];
         let changeset = SelectOptionCellChangeset::from_insert_options(option_ids);
-        let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap().into();
-
+        let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
         assert_eq!(&*select_option_ids, &vec![google.id]);
     }
 
@@ -180,12 +213,12 @@ mod tests {
 
         // insert
         let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone());
-        let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap().into();
+        let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
         assert_eq!(&*select_option_ids, &vec![google.id]);
 
         // delete
         let changeset = SelectOptionCellChangeset::from_delete_options(option_ids);
-        let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap().into();
+        let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
         assert!(select_option_ids.is_empty());
     }
 
@@ -198,9 +231,9 @@ mod tests {
 
         let option_ids = vec![google.id];
         let changeset = SelectOptionCellChangeset::from_insert_options(option_ids);
-        let cell_option_ids = type_option.apply_changeset(changeset, None).unwrap();
+        let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
 
-        assert!(cell_option_ids.is_empty());
+        assert!(select_option_ids.is_empty());
     }
 
     #[test]
@@ -210,7 +243,7 @@ mod tests {
         let type_option = SingleSelectTypeOptionPB::from(&field_rev);
 
         let changeset = SelectOptionCellChangeset::from_insert_option_id("");
-        let cell_option_ids = type_option.apply_changeset(changeset, None).unwrap();
-        assert_eq!(cell_option_ids, "");
+        let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
+        assert!(select_option_ids.is_empty());
     }
 }

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

@@ -1,7 +1,4 @@
 use crate::entities::{TextFilterConditionPB, TextFilterPB};
-use crate::services::cell::{CellFilterable, TypeCellData};
-use crate::services::field::{RichTextTypeOptionPB, TypeOptionCellData, TypeOptionConfiguration};
-use flowy_error::FlowyResult;
 
 impl TextFilterPB {
     pub fn is_visible<T: AsRef<str>>(&self, cell_data: T) -> bool {
@@ -20,21 +17,6 @@ impl TextFilterPB {
     }
 }
 
-impl CellFilterable for RichTextTypeOptionPB {
-    fn apply_filter(
-        &self,
-        type_cell_data: TypeCellData,
-        filter: &<Self as TypeOptionConfiguration>::CellFilterConfiguration,
-    ) -> FlowyResult<bool> {
-        if !type_cell_data.is_text() {
-            return Ok(false);
-        }
-
-        let text_cell_data = self.decode_type_option_cell_str(type_cell_data.cell_str)?;
-        Ok(filter.is_visible(text_cell_data))
-    }
-}
-
 #[cfg(test)]
 mod tests {
     #![allow(clippy::all)]

+ 8 - 8
frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_tests.rs

@@ -52,28 +52,28 @@ mod tests {
         let text_type_option = RichTextTypeOptionPB::default();
         let field_type = FieldType::MultiSelect;
 
-        let France = SelectOptionPB::new("France");
-        let france_optionId = France.id.clone();
+        let france = SelectOptionPB::new("france");
+        let france_option_id = france.id.clone();
 
-        let Argentina = SelectOptionPB::new("Argentina");
-        let argentina_optionId = Argentina.id.clone();
+        let argentina = SelectOptionPB::new("argentina");
+        let argentina_option_id = argentina.id.clone();
 
         let multi_select = MultiSelectTypeOptionBuilder::default()
-            .add_option(France.clone())
-            .add_option(Argentina.clone());
+            .add_option(france.clone())
+            .add_option(argentina.clone());
 
         let field_rev = FieldBuilder::new(multi_select).build();
 
         assert_eq!(
             text_type_option
                 .decode_cell_str(
-                    format!("{},{}", france_optionId, argentina_optionId),
+                    format!("{},{}", france_option_id, argentina_option_id),
                     &field_type,
                     &field_rev
                 )
                 .unwrap()
                 .to_string(),
-            format!("{},{}", France.name, Argentina.name)
+            format!("{},{}", france.name, argentina.name)
         );
     }
 }

+ 30 - 15
frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs

@@ -1,12 +1,12 @@
 use crate::entities::{FieldType, TextFilterPB};
 use crate::impl_type_option;
 use crate::services::cell::{
-    stringify_cell_data, CellComparable, CellDataChangeset, CellDataDecoder, CellProtobufBlobParser, DecodedCellData,
-    FromCellString, TypeCellData,
+    stringify_cell_data, CellDataChangeset, CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellString,
+    TypeCellData,
 };
 use crate::services::field::{
-    BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
-    TypeOptionTransform,
+    BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare,
+    TypeOptionCellDataFilter, TypeOptionTransform,
 };
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
@@ -45,14 +45,11 @@ impl TypeOption for RichTextTypeOptionPB {
     type CellData = StrCellData;
     type CellChangeset = String;
     type CellProtobufType = StrCellData;
+    type CellFilter = TextFilterPB;
 }
 
 impl TypeOptionTransform for RichTextTypeOptionPB {}
 
-impl TypeOptionConfiguration for RichTextTypeOptionPB {
-    type CellFilterConfiguration = TextFilterPB;
-}
-
 impl TypeOptionCellData for RichTextTypeOptionPB {
     fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType {
         cell_data
@@ -92,23 +89,41 @@ impl CellDataChangeset for RichTextTypeOptionPB {
         &self,
         changeset: <Self as TypeOption>::CellChangeset,
         _type_cell_data: Option<TypeCellData>,
-    ) -> FlowyResult<String> {
+    ) -> FlowyResult<<Self as TypeOption>::CellData> {
         if changeset.len() > 10000 {
             Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000"))
         } else {
-            Ok(changeset)
+            Ok(StrCellData(changeset))
         }
     }
 }
 
-impl CellComparable for RichTextTypeOptionPB {
-    type CellData = String;
+impl TypeOptionCellDataFilter for RichTextTypeOptionPB {
+    fn apply_filter(
+        &self,
+        filter: &<Self as TypeOption>::CellFilter,
+        field_type: &FieldType,
+        cell_data: &<Self as TypeOption>::CellData,
+    ) -> bool {
+        if !field_type.is_text() {
+            return false;
+        }
 
-    fn apply_cmp(&self, cell_data: &Self::CellData, other_cell_data: &Self::CellData) -> Ordering {
-        cell_data.cmp(other_cell_data)
+        filter.is_visible(cell_data)
+    }
+}
+
+impl TypeOptionCellDataCompare for RichTextTypeOptionPB {
+    fn apply_cmp(
+        &self,
+        cell_data: &<Self as TypeOption>::CellData,
+        other_cell_data: &<Self as TypeOption>::CellData,
+    ) -> Ordering {
+        cell_data.0.cmp(&other_cell_data.0)
     }
 }
 
+#[derive(Clone)]
 pub struct TextCellData(pub String);
 impl AsRef<str> for TextCellData {
     fn as_ref(&self) -> &str {
@@ -158,7 +173,7 @@ impl CellProtobufBlobParser for TextCellDataParser {
     }
 }
 
-#[derive(Default, Debug)]
+#[derive(Default, Debug, Clone)]
 pub struct StrCellData(pub String);
 impl std::ops::Deref for StrCellData {
     type Target = String;

+ 23 - 192
frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option.rs

@@ -1,15 +1,12 @@
 use crate::entities::FieldType;
-use crate::services::cell::{
-    CellDataChangeset, CellDataDecoder, CellProtobufBlob, FromCellChangeset, FromCellString, TypeCellData,
-};
-use crate::services::field::{
-    CheckboxTypeOptionPB, ChecklistTypeOptionPB, DateTypeOptionPB, MultiSelectTypeOptionPB, NumberTypeOptionPB,
-    RichTextTypeOptionPB, SingleSelectTypeOptionPB, URLTypeOptionPB,
-};
+use crate::services::cell::{CellDataDecoder, FromCellChangeset, FromCellString};
+
+use crate::services::filter::FromFilterString;
 use bytes::Bytes;
 use flowy_error::FlowyResult;
-use grid_rev_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
+use grid_rev_model::FieldRevision;
 use protobuf::ProtobufError;
+use std::cmp::Ordering;
 use std::fmt::Debug;
 
 pub trait TypeOption {
@@ -23,7 +20,7 @@ pub trait TypeOption {
     ///
     /// Uses `StrCellData` for any `TypeOption` if their cell data is pure `String`.
     ///
-    type CellData: FromCellString + Default;
+    type CellData: FromCellString + ToString + Default + Send + Sync + Clone + 'static;
 
     /// Represents as the corresponding field type cell changeset.
     /// The changeset must implements the `FromCellChangeset` trait. The `CellChangeset` is implemented
@@ -39,6 +36,9 @@ pub trait TypeOption {
     ///     FieldType::URL => URLCellDataPB
     ///
     type CellProtobufType: TryInto<Bytes, Error = ProtobufError> + Debug;
+
+    /// Represents as the filter configuration for this type option.
+    type CellFilter: FromFilterString + Send + Sync + 'static;
 }
 
 pub trait TypeOptionCellData: TypeOption {
@@ -55,10 +55,6 @@ pub trait TypeOptionCellData: TypeOption {
     fn decode_type_option_cell_str(&self, cell_str: String) -> FlowyResult<<Self as TypeOption>::CellData>;
 }
 
-pub trait TypeOptionConfiguration {
-    type CellFilterConfiguration;
-}
-
 pub trait TypeOptionTransform: TypeOption {
     /// Returns true if the current `TypeOption` provides custom type option transformation
     fn transformable(&self) -> bool {
@@ -97,189 +93,24 @@ pub trait TypeOptionTransform: TypeOption {
     }
 }
 
-/// A helper trait that used to erase the `Self` of `TypeOption` trait to make it become a Object-safe trait.
-pub trait TypeOptionTransformHandler {
-    fn transform(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String);
-
-    fn json_str(&self) -> String;
-}
-
-impl<T> TypeOptionTransformHandler for T
-where
-    T: TypeOptionTransform + TypeOptionDataSerializer,
-{
-    fn transform(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String) {
-        if self.transformable() {
-            self.transform_type_option(old_type_option_field_type, old_type_option_data)
-        }
-    }
-
-    fn json_str(&self) -> String {
-        self.json_str()
-    }
-}
-
-/// A helper trait that used to erase the `Self` of `TypeOption` trait to make it become a Object-safe trait
-/// Only object-safe traits can be made into trait objects.
-/// > Object-safe traits are traits with methods that follow these two rules:
-/// 1.the return type is not Self.
-/// 2.there are no generic types parameters.
-///
-pub trait TypeOptionCellDataHandler {
-    fn handle_cell_str(
-        &self,
-        cell_str: String,
-        decoded_field_type: &FieldType,
-        field_rev: &FieldRevision,
-    ) -> FlowyResult<CellProtobufBlob>;
-
-    fn handle_cell_changeset(
-        &self,
-        cell_changeset: String,
-        old_type_cell_data: Option<TypeCellData>,
-    ) -> FlowyResult<String>;
-
-    /// Decode the cell_str to corresponding cell data, and then return the display string of the
-    /// cell data.
-    fn stringify_cell_str(&self, cell_str: String, field_type: &FieldType, field_rev: &FieldRevision) -> String;
-}
-
-impl<T> TypeOptionCellDataHandler for T
-where
-    T: TypeOption + CellDataDecoder + CellDataChangeset + TypeOptionCellData + TypeOptionTransform,
-{
-    fn handle_cell_str(
-        &self,
-        cell_str: String,
-        decoded_field_type: &FieldType,
-        field_rev: &FieldRevision,
-    ) -> FlowyResult<CellProtobufBlob> {
-        let cell_data = if self.transformable() {
-            match self.transform_type_option_cell_str(&cell_str, decoded_field_type, field_rev) {
-                None => self.decode_cell_str(cell_str, decoded_field_type, field_rev)?,
-                Some(cell_data) => cell_data,
-            }
-        } else {
-            self.decode_cell_str(cell_str, decoded_field_type, field_rev)?
-        };
-        CellProtobufBlob::from(self.convert_to_protobuf(cell_data))
-    }
-
-    fn handle_cell_changeset(
-        &self,
-        cell_changeset: String,
-        old_type_cell_data: Option<TypeCellData>,
-    ) -> FlowyResult<String> {
-        let changeset = <Self as TypeOption>::CellChangeset::from_changeset(cell_changeset)?;
-        let cell_data = self.apply_changeset(changeset, old_type_cell_data)?;
-        Ok(cell_data)
-    }
-
-    fn stringify_cell_str(&self, cell_str: String, field_type: &FieldType, field_rev: &FieldRevision) -> String {
-        if self.transformable() {
-            let cell_data = self.transform_type_option_cell_str(&cell_str, field_type, field_rev);
-            if let Some(cell_data) = cell_data {
-                return self.decode_cell_data_to_str(cell_data);
-            }
-        }
-        match <Self as TypeOption>::CellData::from_cell_str(&cell_str) {
-            Ok(cell_data) => self.decode_cell_data_to_str(cell_data),
-            Err(_) => "".to_string(),
-        }
-    }
-}
-
-pub struct FieldRevisionExt<'a> {
-    field_rev: &'a FieldRevision,
-}
-
-impl<'a> FieldRevisionExt<'a> {
-    pub fn new(field_rev: &'a FieldRevision) -> Self {
-        Self { field_rev }
-    }
-
-    pub fn get_type_option_cell_data_handler(
+pub trait TypeOptionCellDataFilter: TypeOption + CellDataDecoder {
+    fn apply_filter(
         &self,
+        filter: &<Self as TypeOption>::CellFilter,
         field_type: &FieldType,
-    ) -> Option<Box<dyn TypeOptionCellDataHandler>> {
-        match field_type {
-            FieldType::RichText => self
-                .field_rev
-                .get_type_option::<RichTextTypeOptionPB>(field_type.into())
-                .map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
-            FieldType::Number => self
-                .field_rev
-                .get_type_option::<NumberTypeOptionPB>(field_type.into())
-                .map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
-            FieldType::DateTime => self
-                .field_rev
-                .get_type_option::<DateTypeOptionPB>(field_type.into())
-                .map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
-            FieldType::SingleSelect => self
-                .field_rev
-                .get_type_option::<SingleSelectTypeOptionPB>(field_type.into())
-                .map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
-            FieldType::MultiSelect => self
-                .field_rev
-                .get_type_option::<MultiSelectTypeOptionPB>(field_type.into())
-                .map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
-            FieldType::Checkbox => self
-                .field_rev
-                .get_type_option::<CheckboxTypeOptionPB>(field_type.into())
-                .map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
-            FieldType::URL => self
-                .field_rev
-                .get_type_option::<URLTypeOptionPB>(field_type.into())
-                .map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
-            FieldType::Checklist => self
-                .field_rev
-                .get_type_option::<ChecklistTypeOptionPB>(field_type.into())
-                .map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
-        }
-    }
+        cell_data: &<Self as TypeOption>::CellData,
+    ) -> bool;
 }
 
-pub fn transform_type_option(
-    type_option_data: &str,
-    new_field_type: &FieldType,
-    old_type_option_data: Option<String>,
-    old_field_type: FieldType,
-) -> String {
-    let mut transform_handler = get_type_option_transform_handler(type_option_data, new_field_type);
-    if let Some(old_type_option_data) = old_type_option_data {
-        transform_handler.transform(old_field_type, old_type_option_data);
-    }
-    transform_handler.json_str()
+#[inline(always)]
+pub fn default_order() -> Ordering {
+    Ordering::Equal
 }
 
-pub fn get_type_option_transform_handler(
-    type_option_data: &str,
-    field_type: &FieldType,
-) -> Box<dyn TypeOptionTransformHandler> {
-    match field_type {
-        FieldType::RichText => {
-            Box::new(RichTextTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
-        }
-        FieldType::Number => {
-            Box::new(NumberTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
-        }
-        FieldType::DateTime => {
-            Box::new(DateTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
-        }
-        FieldType::SingleSelect => {
-            Box::new(SingleSelectTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
-        }
-        FieldType::MultiSelect => {
-            Box::new(MultiSelectTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
-        }
-        FieldType::Checkbox => {
-            Box::new(CheckboxTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
-        }
-        FieldType::URL => {
-            Box::new(URLTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
-        }
-        FieldType::Checklist => {
-            Box::new(ChecklistTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
-        }
-    }
+pub trait TypeOptionCellDataCompare: TypeOption {
+    fn apply_cmp(
+        &self,
+        cell_data: &<Self as TypeOption>::CellData,
+        other_cell_data: &<Self as TypeOption>::CellData,
+    ) -> Ordering;
 }

+ 420 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option_cell.rs

@@ -0,0 +1,420 @@
+use crate::entities::FieldType;
+use crate::services::cell::{
+    AtomicCellDataCache, AtomicCellFilterCache, CellDataChangeset, CellDataDecoder, CellProtobufBlob,
+    FromCellChangeset, FromCellString, TypeCellData,
+};
+use crate::services::field::{
+    default_order, CheckboxTypeOptionPB, ChecklistTypeOptionPB, DateTypeOptionPB, MultiSelectTypeOptionPB,
+    NumberTypeOptionPB, RichTextTypeOptionPB, SingleSelectTypeOptionPB, TypeOption, TypeOptionCellData,
+    TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, URLTypeOptionPB,
+};
+use crate::services::filter::FilterType;
+use flowy_error::FlowyResult;
+use grid_rev_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
+use std::cmp::Ordering;
+use std::collections::hash_map::DefaultHasher;
+use std::hash::Hasher;
+
+/// A helper trait that used to erase the `Self` of `TypeOption` trait to make it become a Object-safe trait
+/// Only object-safe traits can be made into trait objects.
+/// > Object-safe traits are traits with methods that follow these two rules:
+/// 1.the return type is not Self.
+/// 2.there are no generic types parameters.
+///
+pub trait TypeOptionCellDataHandler {
+    fn handle_cell_str(
+        &self,
+        cell_str: String,
+        decoded_field_type: &FieldType,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<CellProtobufBlob>;
+
+    fn handle_cell_changeset(
+        &self,
+        cell_changeset: String,
+        old_type_cell_data: Option<TypeCellData>,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<String>;
+
+    fn handle_cell_compare(&self, left_cell_data: &str, right_cell_data: &str, field_rev: &FieldRevision) -> Ordering;
+
+    fn handle_cell_filter(
+        &self,
+        filter_type: &FilterType,
+        field_rev: &FieldRevision,
+        type_cell_data: TypeCellData,
+    ) -> bool;
+
+    /// Decode the cell_str to corresponding cell data, and then return the display string of the
+    /// cell data.
+    fn stringify_cell_str(&self, cell_str: String, field_type: &FieldType, field_rev: &FieldRevision) -> String;
+}
+
+struct CellDataCacheKey(u64);
+impl CellDataCacheKey {
+    pub fn new(field_rev: &FieldRevision, decoded_field_type: FieldType, cell_str: &str) -> Self {
+        let mut hasher = DefaultHasher::new();
+        hasher.write(field_rev.id.as_bytes());
+        hasher.write_u8(decoded_field_type as u8);
+        hasher.write(cell_str.as_bytes());
+        Self(hasher.finish())
+    }
+}
+
+impl AsRef<u64> for CellDataCacheKey {
+    fn as_ref(&self) -> &u64 {
+        &self.0
+    }
+}
+
+struct TypeOptionCellDataHandlerImpl<T> {
+    inner: T,
+    cell_data_cache: Option<AtomicCellDataCache>,
+    cell_filter_cache: Option<AtomicCellFilterCache>,
+}
+
+impl<T> TypeOptionCellDataHandlerImpl<T>
+where
+    T: TypeOption
+        + CellDataDecoder
+        + CellDataChangeset
+        + TypeOptionCellData
+        + TypeOptionTransform
+        + TypeOptionCellDataFilter
+        + TypeOptionCellDataCompare
+        + 'static,
+{
+    pub fn new_with_boxed(
+        inner: T,
+        cell_filter_cache: Option<AtomicCellFilterCache>,
+        cell_data_cache: Option<AtomicCellDataCache>,
+    ) -> Box<dyn TypeOptionCellDataHandler> {
+        Box::new(Self {
+            inner,
+            cell_data_cache,
+            cell_filter_cache,
+        }) as Box<dyn TypeOptionCellDataHandler>
+    }
+}
+
+impl<T> TypeOptionCellDataHandlerImpl<T>
+where
+    T: TypeOption + CellDataDecoder,
+{
+    fn get_decoded_cell_data(
+        &self,
+        cell_str: String,
+        decoded_field_type: &FieldType,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<<Self as TypeOption>::CellData> {
+        let key = CellDataCacheKey::new(field_rev, decoded_field_type.clone(), &cell_str);
+        if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
+            let read_guard = cell_data_cache.read();
+            if let Some(cell_data) = read_guard.get(key.as_ref()).cloned() {
+                tracing::trace!("Cell cache hit: {}:{}", decoded_field_type, cell_str);
+                return Ok(cell_data);
+            }
+        }
+
+        let cell_data = self.decode_cell_str(cell_str, decoded_field_type, field_rev)?;
+        if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
+            cell_data_cache.write().insert(key.as_ref(), cell_data.clone());
+        }
+        Ok(cell_data)
+    }
+
+    fn set_decoded_cell_data(&self, cell_data: <Self as TypeOption>::CellData, field_rev: &FieldRevision) {
+        if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
+            let field_type: FieldType = field_rev.ty.into();
+            let cell_str = cell_data.to_string();
+            tracing::trace!("Update cell cache {}:{}", field_type, cell_str);
+            let key = CellDataCacheKey::new(field_rev, field_type, &cell_str);
+            cell_data_cache.write().insert(key.as_ref(), cell_data);
+        }
+    }
+}
+
+impl<T> std::ops::Deref for TypeOptionCellDataHandlerImpl<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl<T> TypeOption for TypeOptionCellDataHandlerImpl<T>
+where
+    T: TypeOption,
+{
+    type CellData = T::CellData;
+    type CellChangeset = T::CellChangeset;
+    type CellProtobufType = T::CellProtobufType;
+    type CellFilter = T::CellFilter;
+}
+
+impl<T> TypeOptionCellDataHandler for TypeOptionCellDataHandlerImpl<T>
+where
+    T: TypeOption
+        + CellDataDecoder
+        + CellDataChangeset
+        + TypeOptionCellData
+        + TypeOptionTransform
+        + TypeOptionCellDataFilter
+        + TypeOptionCellDataCompare,
+{
+    fn handle_cell_str(
+        &self,
+        cell_str: String,
+        decoded_field_type: &FieldType,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<CellProtobufBlob> {
+        let cell_data = if self.transformable() {
+            match self.transform_type_option_cell_str(&cell_str, decoded_field_type, field_rev) {
+                None => self.get_decoded_cell_data(cell_str, decoded_field_type, field_rev)?,
+                Some(cell_data) => cell_data,
+            }
+        } else {
+            self.get_decoded_cell_data(cell_str, decoded_field_type, field_rev)?
+        };
+        CellProtobufBlob::from(self.convert_to_protobuf(cell_data))
+    }
+
+    fn handle_cell_changeset(
+        &self,
+        cell_changeset: String,
+        old_type_cell_data: Option<TypeCellData>,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<String> {
+        let changeset = <Self as TypeOption>::CellChangeset::from_changeset(cell_changeset)?;
+        let cell_data = self.apply_changeset(changeset, old_type_cell_data)?;
+        self.set_decoded_cell_data(cell_data.clone(), field_rev);
+        Ok(cell_data.to_string())
+    }
+
+    fn handle_cell_compare(&self, left_cell_data: &str, right_cell_data: &str, field_rev: &FieldRevision) -> Ordering {
+        let field_type: FieldType = field_rev.ty.into();
+        let left = self.get_decoded_cell_data(left_cell_data.to_owned(), &field_type, field_rev);
+        let right = self.get_decoded_cell_data(right_cell_data.to_owned(), &field_type, field_rev);
+
+        match (left, right) {
+            (Ok(left), Ok(right)) => self.apply_cmp(&left, &right),
+            (Ok(_), Err(_)) => Ordering::Greater,
+            (Err(_), Ok(_)) => Ordering::Less,
+            (Err(_), Err(_)) => default_order(),
+        }
+    }
+
+    fn handle_cell_filter(
+        &self,
+        filter_type: &FilterType,
+        field_rev: &FieldRevision,
+        type_cell_data: TypeCellData,
+    ) -> bool {
+        let perform_filter = || {
+            let filter_cache = self.cell_filter_cache.as_ref()?.read();
+            let cell_filter = filter_cache.get::<<Self as TypeOption>::CellFilter>(filter_type)?;
+            let cell_data = self
+                .get_decoded_cell_data(type_cell_data.cell_str, &filter_type.field_type, field_rev)
+                .ok()?;
+            Some(self.apply_filter(cell_filter, &filter_type.field_type, &cell_data))
+        };
+
+        perform_filter().unwrap_or(true)
+    }
+
+    fn stringify_cell_str(&self, cell_str: String, field_type: &FieldType, field_rev: &FieldRevision) -> String {
+        if self.transformable() {
+            let cell_data = self.transform_type_option_cell_str(&cell_str, field_type, field_rev);
+            if let Some(cell_data) = cell_data {
+                return self.decode_cell_data_to_str(cell_data);
+            }
+        }
+        match <Self as TypeOption>::CellData::from_cell_str(&cell_str) {
+            Ok(cell_data) => self.decode_cell_data_to_str(cell_data),
+            Err(_) => "".to_string(),
+        }
+    }
+}
+
+pub struct TypeOptionCellExt<'a> {
+    field_rev: &'a FieldRevision,
+    cell_data_cache: Option<AtomicCellDataCache>,
+    cell_filter_cache: Option<AtomicCellFilterCache>,
+}
+
+impl<'a> TypeOptionCellExt<'a> {
+    pub fn new_with_cell_data_cache(
+        field_rev: &'a FieldRevision,
+        cell_data_cache: Option<AtomicCellDataCache>,
+    ) -> Self {
+        Self {
+            field_rev,
+            cell_data_cache,
+            cell_filter_cache: None,
+        }
+    }
+
+    pub fn new(
+        field_rev: &'a FieldRevision,
+        cell_data_cache: Option<AtomicCellDataCache>,
+        cell_filter_cache: Option<AtomicCellFilterCache>,
+    ) -> Self {
+        let mut this = Self::new_with_cell_data_cache(field_rev, cell_data_cache);
+        this.cell_filter_cache = cell_filter_cache;
+        this
+    }
+
+    pub fn get_type_option_cell_data_handler(
+        &self,
+        field_type: &FieldType,
+    ) -> Option<Box<dyn TypeOptionCellDataHandler>> {
+        match field_type {
+            FieldType::RichText => self
+                .field_rev
+                .get_type_option::<RichTextTypeOptionPB>(field_type.into())
+                .map(|type_option| {
+                    TypeOptionCellDataHandlerImpl::new_with_boxed(
+                        type_option,
+                        self.cell_filter_cache.clone(),
+                        self.cell_data_cache.clone(),
+                    )
+                }),
+            FieldType::Number => self
+                .field_rev
+                .get_type_option::<NumberTypeOptionPB>(field_type.into())
+                .map(|type_option| {
+                    TypeOptionCellDataHandlerImpl::new_with_boxed(
+                        type_option,
+                        self.cell_filter_cache.clone(),
+                        self.cell_data_cache.clone(),
+                    )
+                }),
+            FieldType::DateTime => self
+                .field_rev
+                .get_type_option::<DateTypeOptionPB>(field_type.into())
+                .map(|type_option| {
+                    TypeOptionCellDataHandlerImpl::new_with_boxed(
+                        type_option,
+                        self.cell_filter_cache.clone(),
+                        self.cell_data_cache.clone(),
+                    )
+                }),
+            FieldType::SingleSelect => self
+                .field_rev
+                .get_type_option::<SingleSelectTypeOptionPB>(field_type.into())
+                .map(|type_option| {
+                    TypeOptionCellDataHandlerImpl::new_with_boxed(
+                        type_option,
+                        self.cell_filter_cache.clone(),
+                        self.cell_data_cache.clone(),
+                    )
+                }),
+            FieldType::MultiSelect => self
+                .field_rev
+                .get_type_option::<MultiSelectTypeOptionPB>(field_type.into())
+                .map(|type_option| {
+                    TypeOptionCellDataHandlerImpl::new_with_boxed(
+                        type_option,
+                        self.cell_filter_cache.clone(),
+                        self.cell_data_cache.clone(),
+                    )
+                }),
+            FieldType::Checkbox => self
+                .field_rev
+                .get_type_option::<CheckboxTypeOptionPB>(field_type.into())
+                .map(|type_option| {
+                    TypeOptionCellDataHandlerImpl::new_with_boxed(
+                        type_option,
+                        self.cell_filter_cache.clone(),
+                        self.cell_data_cache.clone(),
+                    )
+                }),
+            FieldType::URL => self
+                .field_rev
+                .get_type_option::<URLTypeOptionPB>(field_type.into())
+                .map(|type_option| {
+                    TypeOptionCellDataHandlerImpl::new_with_boxed(
+                        type_option,
+                        self.cell_filter_cache.clone(),
+                        self.cell_data_cache.clone(),
+                    )
+                }),
+            FieldType::Checklist => self
+                .field_rev
+                .get_type_option::<ChecklistTypeOptionPB>(field_type.into())
+                .map(|type_option| {
+                    TypeOptionCellDataHandlerImpl::new_with_boxed(
+                        type_option,
+                        self.cell_filter_cache.clone(),
+                        self.cell_data_cache.clone(),
+                    )
+                }),
+        }
+    }
+}
+
+pub fn transform_type_option(
+    type_option_data: &str,
+    new_field_type: &FieldType,
+    old_type_option_data: Option<String>,
+    old_field_type: FieldType,
+) -> String {
+    let mut transform_handler = get_type_option_transform_handler(type_option_data, new_field_type);
+    if let Some(old_type_option_data) = old_type_option_data {
+        transform_handler.transform(old_field_type, old_type_option_data);
+    }
+    transform_handler.json_str()
+}
+
+/// A helper trait that used to erase the `Self` of `TypeOption` trait to make it become a Object-safe trait.
+pub trait TypeOptionTransformHandler {
+    fn transform(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String);
+
+    fn json_str(&self) -> String;
+}
+
+impl<T> TypeOptionTransformHandler for T
+where
+    T: TypeOptionTransform + TypeOptionDataSerializer,
+{
+    fn transform(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String) {
+        if self.transformable() {
+            self.transform_type_option(old_type_option_field_type, old_type_option_data)
+        }
+    }
+
+    fn json_str(&self) -> String {
+        self.json_str()
+    }
+}
+fn get_type_option_transform_handler(
+    type_option_data: &str,
+    field_type: &FieldType,
+) -> Box<dyn TypeOptionTransformHandler> {
+    match field_type {
+        FieldType::RichText => {
+            Box::new(RichTextTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
+        }
+        FieldType::Number => {
+            Box::new(NumberTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
+        }
+        FieldType::DateTime => {
+            Box::new(DateTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
+        }
+        FieldType::SingleSelect => {
+            Box::new(SingleSelectTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
+        }
+        FieldType::MultiSelect => {
+            Box::new(MultiSelectTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
+        }
+        FieldType::Checkbox => {
+            Box::new(CheckboxTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
+        }
+        FieldType::URL => {
+            Box::new(URLTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
+        }
+        FieldType::Checklist => {
+            Box::new(ChecklistTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
+        }
+    }
+}

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

@@ -1,5 +1,4 @@
 #![allow(clippy::module_inception)]
-mod url_filter;
 mod url_tests;
 mod url_type_option;
 mod url_type_option_entities;

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

@@ -1,18 +0,0 @@
-use crate::services::cell::{CellFilterable, TypeCellData};
-use crate::services::field::{TypeOptionCellData, TypeOptionConfiguration, URLTypeOptionPB};
-use flowy_error::FlowyResult;
-
-impl CellFilterable for URLTypeOptionPB {
-    fn apply_filter(
-        &self,
-        type_cell_data: TypeCellData,
-        filter: &<Self as TypeOptionConfiguration>::CellFilterConfiguration,
-    ) -> FlowyResult<bool> {
-        if !type_cell_data.is_url() {
-            return Ok(true);
-        }
-
-        let url_cell_data = self.decode_type_option_cell_str(type_cell_data.cell_str)?;
-        Ok(filter.is_visible(&url_cell_data))
-    }
-}

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

@@ -1,10 +1,10 @@
 #[cfg(test)]
 mod tests {
     use crate::entities::FieldType;
-    use crate::services::cell::{CellDataChangeset, CellDataDecoder};
+    use crate::services::cell::CellDataChangeset;
 
     use crate::services::field::FieldBuilder;
-    use crate::services::field::{URLCellData, URLTypeOptionPB};
+    use crate::services::field::URLTypeOptionPB;
     use grid_rev_model::FieldRevision;
 
     /// The expected_str will equal to the input string, but the expected_url will be empty if there's no
@@ -14,8 +14,8 @@ mod tests {
         let type_option = URLTypeOptionPB::default();
         let field_type = FieldType::URL;
         let field_rev = FieldBuilder::from_field_type(&field_type).build();
-        assert_url(&type_option, "123", "123", "", &field_type, &field_rev);
-        assert_url(&type_option, "", "", "", &field_type, &field_rev);
+        assert_url(&type_option, "123", "123", "", &field_rev);
+        assert_url(&type_option, "", "", "", &field_rev);
     }
 
     /// The expected_str will equal to the input string, but the expected_url will not be empty
@@ -30,7 +30,6 @@ mod tests {
             "AppFlowy website - https://www.appflowy.io",
             "AppFlowy website - https://www.appflowy.io",
             "https://www.appflowy.io/",
-            &field_type,
             &field_rev,
         );
 
@@ -39,7 +38,6 @@ mod tests {
             "AppFlowy website appflowy.io",
             "AppFlowy website appflowy.io",
             "https://appflowy.io",
-            &field_type,
             &field_rev,
         );
     }
@@ -55,7 +53,6 @@ mod tests {
             "AppFlowy website - https://www.appflowy.io welcome!",
             "AppFlowy website - https://www.appflowy.io welcome!",
             "https://www.appflowy.io/",
-            &field_type,
             &field_rev,
         );
 
@@ -64,7 +61,6 @@ mod tests {
             "AppFlowy website appflowy.io welcome!",
             "AppFlowy website appflowy.io welcome!",
             "https://appflowy.io",
-            &field_type,
             &field_rev,
         );
     }
@@ -80,7 +76,6 @@ mod tests {
             "AppFlowy website - https://www.appflowy.io!",
             "AppFlowy website - https://www.appflowy.io!",
             "https://www.appflowy.io/",
-            &field_type,
             &field_rev,
         );
 
@@ -89,7 +84,6 @@ mod tests {
             "AppFlowy website appflowy.io!",
             "AppFlowy website appflowy.io!",
             "https://appflowy.io",
-            &field_type,
             &field_rev,
         );
     }
@@ -105,7 +99,6 @@ mod tests {
             "test - https://tester.testgroup.appflowy.io",
             "test - https://tester.testgroup.appflowy.io",
             "https://tester.testgroup.appflowy.io/",
-            &field_type,
             &field_rev,
         );
 
@@ -114,7 +107,6 @@ mod tests {
             "test tester.testgroup.appflowy.io",
             "test tester.testgroup.appflowy.io",
             "https://tester.testgroup.appflowy.io",
-            &field_type,
             &field_rev,
         );
     }
@@ -130,7 +122,6 @@ mod tests {
             "appflowy - https://appflowy.com",
             "appflowy - https://appflowy.com",
             "https://appflowy.com/",
-            &field_type,
             &field_rev,
         );
 
@@ -139,7 +130,6 @@ mod tests {
             "appflowy - https://appflowy.top",
             "appflowy - https://appflowy.top",
             "https://appflowy.top/",
-            &field_type,
             &field_rev,
         );
 
@@ -148,7 +138,6 @@ mod tests {
             "appflowy - https://appflowy.net",
             "appflowy - https://appflowy.net",
             "https://appflowy.net/",
-            &field_type,
             &field_rev,
         );
 
@@ -157,7 +146,6 @@ mod tests {
             "appflowy - https://appflowy.edu",
             "appflowy - https://appflowy.edu",
             "https://appflowy.edu/",
-            &field_type,
             &field_rev,
         );
     }
@@ -167,23 +155,10 @@ mod tests {
         input_str: &str,
         expected_str: &str,
         expected_url: &str,
-        field_type: &FieldType,
-        field_rev: &FieldRevision,
+        _field_rev: &FieldRevision,
     ) {
-        let encoded_data = type_option.apply_changeset(input_str.to_owned(), None).unwrap();
-        let decode_cell_data = decode_cell_data(encoded_data, type_option, field_rev, field_type);
+        let decode_cell_data = type_option.apply_changeset(input_str.to_owned(), None).unwrap();
         assert_eq!(expected_str.to_owned(), decode_cell_data.content);
         assert_eq!(expected_url.to_owned(), decode_cell_data.url);
     }
-
-    fn decode_cell_data(
-        encoded_data: String,
-        type_option: &URLTypeOptionPB,
-        field_rev: &FieldRevision,
-        field_type: &FieldType,
-    ) -> URLCellData {
-        type_option
-            .decode_cell_str(encoded_data, field_type, field_rev)
-            .unwrap()
-    }
 }

+ 30 - 9
frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs

@@ -2,8 +2,8 @@ use crate::entities::{FieldType, TextFilterPB};
 use crate::impl_type_option;
 use crate::services::cell::{CellDataChangeset, CellDataDecoder, FromCellString, TypeCellData};
 use crate::services::field::{
-    BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
-    TypeOptionTransform, URLCellData, URLCellDataPB,
+    BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare,
+    TypeOptionCellDataFilter, TypeOptionTransform, URLCellData, URLCellDataPB,
 };
 use bytes::Bytes;
 use fancy_regex::Regex;
@@ -12,6 +12,7 @@ use flowy_error::FlowyResult;
 use grid_rev_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
 use lazy_static::lazy_static;
 use serde::{Deserialize, Serialize};
+use std::cmp::Ordering;
 
 #[derive(Default)]
 pub struct URLTypeOptionBuilder(URLTypeOptionPB);
@@ -39,14 +40,11 @@ impl TypeOption for URLTypeOptionPB {
     type CellData = URLCellData;
     type CellChangeset = URLCellChangeset;
     type CellProtobufType = URLCellDataPB;
+    type CellFilter = TextFilterPB;
 }
 
 impl TypeOptionTransform for URLTypeOptionPB {}
 
-impl TypeOptionConfiguration for URLTypeOptionPB {
-    type CellFilterConfiguration = TextFilterPB;
-}
-
 impl TypeOptionCellData for URLTypeOptionPB {
     fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType {
         cell_data.into()
@@ -83,19 +81,42 @@ impl CellDataChangeset for URLTypeOptionPB {
         &self,
         changeset: <Self as TypeOption>::CellChangeset,
         _type_cell_data: Option<TypeCellData>,
-    ) -> FlowyResult<String> {
+    ) -> FlowyResult<<Self as TypeOption>::CellData> {
         let mut url = "".to_string();
         if let Ok(Some(m)) = URL_REGEX.find(&changeset) {
             url = auto_append_scheme(m.as_str());
         }
-        URLCellData {
+        Ok(URLCellData {
             url,
             content: changeset,
+        })
+    }
+}
+
+impl TypeOptionCellDataFilter for URLTypeOptionPB {
+    fn apply_filter(
+        &self,
+        filter: &<Self as TypeOption>::CellFilter,
+        field_type: &FieldType,
+        cell_data: &<Self as TypeOption>::CellData,
+    ) -> bool {
+        if !field_type.is_url() {
+            return true;
         }
-        .to_json()
+
+        filter.is_visible(&cell_data)
     }
 }
 
+impl TypeOptionCellDataCompare for URLTypeOptionPB {
+    fn apply_cmp(
+        &self,
+        cell_data: &<Self as TypeOption>::CellData,
+        other_cell_data: &<Self as TypeOption>::CellData,
+    ) -> Ordering {
+        cell_data.content.cmp(&other_cell_data.content)
+    }
+}
 fn auto_append_scheme(s: &str) -> String {
     // Only support https scheme by now
     match url::Url::parse(s) {

+ 1 - 1
frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option_entities.rs

@@ -44,7 +44,7 @@ impl URLCellData {
         }
     }
 
-    pub(crate) fn to_json(&self) -> FlowyResult<String> {
+    pub fn to_json(&self) -> FlowyResult<String> {
         serde_json::to_string(self).map_err(internal_error)
     }
 }

+ 0 - 117
frontend/rust-lib/flowy-grid/src/services/filter/cache.rs

@@ -1,117 +0,0 @@
-use crate::entities::{
-    CheckboxFilterPB, ChecklistFilterPB, DateFilterPB, FieldType, NumberFilterPB, SelectOptionFilterPB, TextFilterPB,
-};
-use crate::services::filter::FilterType;
-use std::collections::HashMap;
-
-#[derive(Default, Debug)]
-pub(crate) struct FilterMap {
-    pub(crate) text_filter: HashMap<FilterType, TextFilterPB>,
-    pub(crate) url_filter: HashMap<FilterType, TextFilterPB>,
-    pub(crate) number_filter: HashMap<FilterType, NumberFilterPB>,
-    pub(crate) date_filter: HashMap<FilterType, DateFilterPB>,
-    pub(crate) select_option_filter: HashMap<FilterType, SelectOptionFilterPB>,
-    pub(crate) checkbox_filter: HashMap<FilterType, CheckboxFilterPB>,
-    pub(crate) checklist_filter: HashMap<FilterType, ChecklistFilterPB>,
-}
-
-impl FilterMap {
-    pub(crate) fn new() -> Self {
-        Self::default()
-    }
-
-    pub(crate) fn has_filter(&self, filter_type: &FilterType) -> bool {
-        match filter_type.field_type {
-            FieldType::RichText => self.text_filter.get(filter_type).is_some(),
-            FieldType::Number => self.number_filter.get(filter_type).is_some(),
-            FieldType::DateTime => self.date_filter.get(filter_type).is_some(),
-            FieldType::SingleSelect => self.select_option_filter.get(filter_type).is_some(),
-            FieldType::MultiSelect => self.select_option_filter.get(filter_type).is_some(),
-            FieldType::Checkbox => self.checkbox_filter.get(filter_type).is_some(),
-            FieldType::URL => self.url_filter.get(filter_type).is_some(),
-            FieldType::Checklist => self.checklist_filter.get(filter_type).is_some(),
-        }
-    }
-
-    pub(crate) fn is_empty(&self) -> bool {
-        if !self.text_filter.is_empty() {
-            return false;
-        }
-
-        if !self.url_filter.is_empty() {
-            return false;
-        }
-
-        if !self.number_filter.is_empty() {
-            return false;
-        }
-
-        if !self.number_filter.is_empty() {
-            return false;
-        }
-
-        if !self.date_filter.is_empty() {
-            return false;
-        }
-
-        if !self.select_option_filter.is_empty() {
-            return false;
-        }
-
-        if !self.checkbox_filter.is_empty() {
-            return false;
-        }
-        if !self.checklist_filter.is_empty() {
-            return false;
-        }
-        true
-    }
-
-    pub(crate) fn remove(&mut self, filter_id: &FilterType) {
-        let _ = match filter_id.field_type {
-            FieldType::RichText => {
-                let _ = self.text_filter.remove(filter_id);
-            }
-            FieldType::Number => {
-                let _ = self.number_filter.remove(filter_id);
-            }
-            FieldType::DateTime => {
-                let _ = self.date_filter.remove(filter_id);
-            }
-            FieldType::SingleSelect => {
-                let _ = self.select_option_filter.remove(filter_id);
-            }
-            FieldType::MultiSelect => {
-                let _ = self.select_option_filter.remove(filter_id);
-            }
-            FieldType::Checkbox => {
-                let _ = self.checkbox_filter.remove(filter_id);
-            }
-            FieldType::URL => {
-                let _ = self.url_filter.remove(filter_id);
-            }
-            FieldType::Checklist => {
-                let _ = self.checklist_filter.remove(filter_id);
-            }
-        };
-    }
-}
-
-/// Refresh the filter according to the field id.
-#[derive(Default)]
-pub(crate) struct FilterResult {
-    pub(crate) visible_by_filter_id: HashMap<FilterType, bool>,
-}
-
-impl FilterResult {
-    pub(crate) fn is_visible(&self) -> bool {
-        let mut is_visible = true;
-        for visible in self.visible_by_filter_id.values() {
-            if !is_visible {
-                break;
-            }
-            is_visible = *visible;
-        }
-        is_visible
-    }
-}

+ 61 - 116
frontend/rust-lib/flowy-grid/src/services/filter/controller.rs

@@ -1,8 +1,8 @@
 use crate::entities::filter_entities::*;
 use crate::entities::{FieldType, InsertedRowPB, RowPB};
-use crate::services::cell::{CellFilterable, TypeCellData};
+use crate::services::cell::{AnyTypeCache, AtomicCellDataCache, AtomicCellFilterCache, TypeCellData};
 use crate::services::field::*;
-use crate::services::filter::{FilterChangeset, FilterMap, FilterResult, FilterResultNotification, FilterType};
+use crate::services::filter::{FilterChangeset, FilterResult, FilterResultNotification, FilterType};
 use crate::services::row::GridBlockRowRevision;
 use crate::services::view_editor::{GridViewChanged, GridViewChangedNotifier};
 use flowy_error::FlowyResult;
@@ -24,12 +24,19 @@ pub trait FilterDelegate: Send + Sync + 'static {
     fn get_row_rev(&self, rows_id: &str) -> Fut<Option<(usize, Arc<RowRevision>)>>;
 }
 
+pub trait FromFilterString {
+    fn from_filter_rev(filter_rev: &FilterRevision) -> Self
+    where
+        Self: Sized;
+}
+
 pub struct FilterController {
     view_id: String,
     handler_id: String,
     delegate: Box<dyn FilterDelegate>,
-    filter_map: FilterMap,
     result_by_row_id: HashMap<RowId, FilterResult>,
+    cell_data_cache: AtomicCellDataCache,
+    cell_filter_cache: AtomicCellFilterCache,
     task_scheduler: Arc<RwLock<TaskDispatcher>>,
     notifier: GridViewChangedNotifier,
 }
@@ -41,6 +48,7 @@ impl FilterController {
         delegate: T,
         task_scheduler: Arc<RwLock<TaskDispatcher>>,
         filter_revs: Vec<Arc<FilterRevision>>,
+        cell_data_cache: AtomicCellDataCache,
         notifier: GridViewChangedNotifier,
     ) -> Self
     where
@@ -50,8 +58,9 @@ impl FilterController {
             view_id: view_id.to_string(),
             handler_id: handler_id.to_string(),
             delegate: Box::new(delegate),
-            filter_map: FilterMap::new(),
             result_by_row_id: HashMap::default(),
+            cell_data_cache,
+            cell_filter_cache: AnyTypeCache::<FilterType>::new(),
             task_scheduler,
             notifier,
         };
@@ -75,16 +84,17 @@ impl FilterController {
     }
 
     pub async fn filter_row_revs(&mut self, row_revs: &mut Vec<Arc<RowRevision>>) {
-        if self.filter_map.is_empty() {
+        if self.cell_filter_cache.read().is_empty() {
             return;
         }
         let field_rev_by_field_id = self.get_filter_revs_map().await;
         row_revs.iter().for_each(|row_rev| {
             let _ = filter_row(
                 row_rev,
-                &self.filter_map,
                 &mut self.result_by_row_id,
                 &field_rev_by_field_id,
+                &self.cell_data_cache,
+                &self.cell_filter_cache,
             );
         });
 
@@ -121,9 +131,10 @@ impl FilterController {
             let mut notification = FilterResultNotification::new(self.view_id.clone(), row_rev.block_id.clone());
             if let Some((row_id, is_visible)) = filter_row(
                 &row_rev,
-                &self.filter_map,
                 &mut self.result_by_row_id,
                 &field_rev_by_field_id,
+                &self.cell_data_cache,
+                &self.cell_filter_cache,
             ) {
                 if is_visible {
                     if let Some((index, row_rev)) = self.delegate.get_row_rev(&row_id).await {
@@ -154,9 +165,10 @@ impl FilterController {
             for (index, row_rev) in block.row_revs.iter().enumerate() {
                 if let Some((row_id, is_visible)) = filter_row(
                     row_rev,
-                    &self.filter_map,
                     &mut self.result_by_row_id,
                     &field_rev_by_field_id,
+                    &self.cell_data_cache,
+                    &self.cell_filter_cache,
                 ) {
                     if is_visible {
                         let row_pb = RowPB::from(row_rev.as_ref());
@@ -233,7 +245,7 @@ impl FilterController {
             if let Some(filter) = self.filter_from_filter_type(filter_type).await {
                 notification = Some(FilterChangesetNotificationPB::from_delete(&self.view_id, vec![filter]));
             }
-            self.filter_map.remove(filter_type);
+            self.cell_filter_cache.write().remove(filter_type);
         }
 
         let _ = self
@@ -258,46 +270,39 @@ impl FilterController {
                 tracing::trace!("Create filter with type: {:?}", filter_type);
                 match &filter_type.field_type {
                     FieldType::RichText => {
-                        let _ = self
-                            .filter_map
-                            .text_filter
-                            .insert(filter_type, TextFilterPB::from(filter_rev.as_ref()));
+                        self.cell_filter_cache
+                            .write()
+                            .insert(&filter_type, TextFilterPB::from_filter_rev(filter_rev.as_ref()));
                     }
                     FieldType::Number => {
-                        let _ = self
-                            .filter_map
-                            .number_filter
-                            .insert(filter_type, NumberFilterPB::from(filter_rev.as_ref()));
+                        self.cell_filter_cache
+                            .write()
+                            .insert(&filter_type, NumberFilterPB::from_filter_rev(filter_rev.as_ref()));
                     }
                     FieldType::DateTime => {
-                        let _ = self
-                            .filter_map
-                            .date_filter
-                            .insert(filter_type, DateFilterPB::from(filter_rev.as_ref()));
+                        self.cell_filter_cache
+                            .write()
+                            .insert(&filter_type, DateFilterPB::from_filter_rev(filter_rev.as_ref()));
                     }
                     FieldType::SingleSelect | FieldType::MultiSelect => {
-                        let _ = self
-                            .filter_map
-                            .select_option_filter
-                            .insert(filter_type, SelectOptionFilterPB::from(filter_rev.as_ref()));
+                        self.cell_filter_cache
+                            .write()
+                            .insert(&filter_type, SelectOptionFilterPB::from_filter_rev(filter_rev.as_ref()));
                     }
                     FieldType::Checkbox => {
-                        let _ = self
-                            .filter_map
-                            .checkbox_filter
-                            .insert(filter_type, CheckboxFilterPB::from(filter_rev.as_ref()));
+                        self.cell_filter_cache
+                            .write()
+                            .insert(&filter_type, CheckboxFilterPB::from_filter_rev(filter_rev.as_ref()));
                     }
                     FieldType::URL => {
-                        let _ = self
-                            .filter_map
-                            .url_filter
-                            .insert(filter_type, TextFilterPB::from(filter_rev.as_ref()));
+                        self.cell_filter_cache
+                            .write()
+                            .insert(&filter_type, TextFilterPB::from_filter_rev(filter_rev.as_ref()));
                     }
                     FieldType::Checklist => {
-                        let _ = self
-                            .filter_map
-                            .checklist_filter
-                            .insert(filter_type, ChecklistFilterPB::from(filter_rev.as_ref()));
+                        self.cell_filter_cache
+                            .write()
+                            .insert(&filter_type, ChecklistFilterPB::from_filter_rev(filter_rev.as_ref()));
                     }
                 }
             }
@@ -309,9 +314,10 @@ impl FilterController {
 #[tracing::instrument(level = "trace", skip_all)]
 fn filter_row(
     row_rev: &Arc<RowRevision>,
-    filter_map: &FilterMap,
     result_by_row_id: &mut HashMap<RowId, FilterResult>,
     field_rev_by_field_id: &HashMap<FieldId, Arc<FieldRevision>>,
+    cell_data_cache: &AtomicCellDataCache,
+    cell_filter_cache: &AtomicCellFilterCache,
 ) -> Option<(String, bool)> {
     // Create a filter result cache if it's not exist
     let filter_result = result_by_row_id
@@ -322,7 +328,7 @@ fn filter_row(
     // Iterate each cell of the row to check its visibility
     for (field_id, field_rev) in field_rev_by_field_id {
         let filter_type = FilterType::from(field_rev);
-        if !filter_map.has_filter(&filter_type) {
+        if !cell_filter_cache.read().contains(&filter_type) {
             filter_result.visible_by_filter_id.remove(&filter_type);
             continue;
         }
@@ -330,7 +336,7 @@ fn filter_row(
         let cell_rev = row_rev.cells.get(field_id);
         // if the visibility of the cell_rew is changed, which means the visibility of the
         // row is changed too.
-        if let Some(is_visible) = filter_cell(&filter_type, field_rev, filter_map, cell_rev) {
+        if let Some(is_visible) = filter_cell(&filter_type, field_rev, cell_rev, cell_data_cache, cell_filter_cache) {
             filter_result.visible_by_filter_id.insert(filter_type, is_visible);
         }
     }
@@ -348,93 +354,32 @@ fn filter_row(
 
 #[tracing::instrument(level = "trace", skip_all, fields(cell_content))]
 fn filter_cell(
-    filter_id: &FilterType,
+    filter_type: &FilterType,
     field_rev: &Arc<FieldRevision>,
-    filter_map: &FilterMap,
     cell_rev: Option<&CellRevision>,
+    cell_data_cache: &AtomicCellDataCache,
+    cell_filter_cache: &AtomicCellFilterCache,
 ) -> Option<bool> {
     let type_cell_data = match cell_rev {
-        None => TypeCellData::from_field_type(&filter_id.field_type),
+        None => TypeCellData::from_field_type(&filter_type.field_type),
         Some(cell_rev) => match TypeCellData::try_from(cell_rev) {
             Ok(cell_data) => cell_data,
             Err(err) => {
                 tracing::error!("Deserialize TypeCellData failed: {}", err);
-                TypeCellData::from_field_type(&filter_id.field_type)
+                TypeCellData::from_field_type(&filter_type.field_type)
             }
         },
     };
-    let cloned_type_cell_data = type_cell_data.cell_str.clone();
-    let is_visible = match &filter_id.field_type {
-        FieldType::RichText => filter_map.text_filter.get(filter_id).and_then(|filter| {
-            Some(
-                field_rev
-                    .get_type_option::<RichTextTypeOptionPB>(field_rev.ty)?
-                    .apply_filter(type_cell_data, filter)
-                    .ok(),
-            )
-        }),
-        FieldType::Number => filter_map.number_filter.get(filter_id).and_then(|filter| {
-            Some(
-                field_rev
-                    .get_type_option::<NumberTypeOptionPB>(field_rev.ty)?
-                    .apply_filter(type_cell_data, filter)
-                    .ok(),
-            )
-        }),
-        FieldType::DateTime => filter_map.date_filter.get(filter_id).and_then(|filter| {
-            Some(
-                field_rev
-                    .get_type_option::<DateTypeOptionPB>(field_rev.ty)?
-                    .apply_filter(type_cell_data, filter)
-                    .ok(),
-            )
-        }),
-        FieldType::SingleSelect => filter_map.select_option_filter.get(filter_id).and_then(|filter| {
-            Some(
-                field_rev
-                    .get_type_option::<SingleSelectTypeOptionPB>(field_rev.ty)?
-                    .apply_filter(type_cell_data, filter)
-                    .ok(),
-            )
-        }),
-        FieldType::MultiSelect => filter_map.select_option_filter.get(filter_id).and_then(|filter| {
-            Some(
-                field_rev
-                    .get_type_option::<MultiSelectTypeOptionPB>(field_rev.ty)?
-                    .apply_filter(type_cell_data, filter)
-                    .ok(),
-            )
-        }),
-        FieldType::Checkbox => filter_map.checkbox_filter.get(filter_id).and_then(|filter| {
-            Some(
-                field_rev
-                    .get_type_option::<CheckboxTypeOptionPB>(field_rev.ty)?
-                    .apply_filter(type_cell_data, filter)
-                    .ok(),
-            )
-        }),
-        FieldType::URL => filter_map.url_filter.get(filter_id).and_then(|filter| {
-            Some(
-                field_rev
-                    .get_type_option::<URLTypeOptionPB>(field_rev.ty)?
-                    .apply_filter(type_cell_data, filter)
-                    .ok(),
-            )
-        }),
-        FieldType::Checklist => filter_map.checklist_filter.get(filter_id).and_then(|filter| {
-            Some(
-                field_rev
-                    .get_type_option::<ChecklistTypeOptionPB>(field_rev.ty)?
-                    .apply_filter(type_cell_data, filter)
-                    .ok(),
-            )
-        }),
-    }?;
-    tracing::Span::current().record(
-        "cell_content",
-        &format!("{} => {:?}", cloned_type_cell_data, is_visible.unwrap()).as_str(),
-    );
-    is_visible
+
+    let handler = TypeOptionCellExt::new(
+        field_rev.as_ref(),
+        Some(cell_data_cache.clone()),
+        Some(cell_filter_cache.clone()),
+    )
+    .get_type_option_cell_data_handler(&filter_type.field_type)?;
+
+    let is_visible = handler.handle_cell_filter(filter_type, field_rev.as_ref(), type_cell_data);
+    Some(is_visible)
 }
 
 #[derive(Serialize, Deserialize, Clone, Debug)]

+ 0 - 2
frontend/rust-lib/flowy-grid/src/services/filter/mod.rs

@@ -1,9 +1,7 @@
-mod cache;
 mod controller;
 mod entities;
 mod task;
 
-pub(crate) use cache::*;
 pub use controller::*;
 pub use entities::*;
 pub(crate) use task::*;

+ 20 - 1
frontend/rust-lib/flowy-grid/src/services/filter/task.rs

@@ -1,6 +1,7 @@
-use crate::services::filter::FilterController;
+use crate::services::filter::{FilterController, FilterType};
 use flowy_task::{TaskContent, TaskHandler};
 use lib_infra::future::BoxResultFuture;
+use std::collections::HashMap;
 use std::sync::Arc;
 use tokio::sync::RwLock;
 
@@ -38,3 +39,21 @@ impl TaskHandler for FilterTaskHandler {
         })
     }
 }
+/// Refresh the filter according to the field id.
+#[derive(Default)]
+pub(crate) struct FilterResult {
+    pub(crate) visible_by_filter_id: HashMap<FilterType, bool>,
+}
+
+impl FilterResult {
+    pub(crate) fn is_visible(&self) -> bool {
+        let mut is_visible = true;
+        for visible in self.visible_by_filter_id.values() {
+            if !is_visible {
+                break;
+            }
+            is_visible = *visible;
+        }
+        is_visible
+    }
+}

+ 43 - 8
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -3,7 +3,10 @@ use crate::entities::CellPathParams;
 use crate::entities::*;
 use crate::manager::GridUser;
 use crate::services::block_manager::GridBlockManager;
-use crate::services::cell::{apply_cell_data_changeset, decode_type_cell_data, CellProtobufBlob};
+use crate::services::cell::{
+    apply_cell_data_changeset, decode_type_cell_data, stringify_cell_data, AnyTypeCache, AtomicCellDataCache,
+    CellProtobufBlob, TypeCellData,
+};
 use crate::services::field::{
     default_type_option_builder_from_type, transform_type_option, type_option_builder_from_bytes, FieldBuilder,
 };
@@ -39,6 +42,7 @@ pub struct GridRevisionEditor {
     view_manager: Arc<GridViewManager>,
     rev_manager: Arc<RevisionManager<Arc<ConnectionPool>>>,
     block_manager: Arc<GridBlockManager>,
+    cell_data_cache: AtomicCellDataCache,
 }
 
 impl Drop for GridRevisionEditor {
@@ -60,6 +64,7 @@ impl GridRevisionEditor {
         let grid_pad = rev_manager.initialize::<GridRevisionSerde>(Some(cloud)).await?;
         let rev_manager = Arc::new(rev_manager);
         let grid_pad = Arc::new(RwLock::new(grid_pad));
+        let cell_data_cache = AnyTypeCache::<u64>::new();
 
         // Block manager
         let (block_event_tx, block_event_rx) = broadcast::channel(100);
@@ -72,8 +77,16 @@ impl GridRevisionEditor {
         });
 
         // View manager
-        let view_manager =
-            Arc::new(GridViewManager::new(grid_id.to_owned(), user.clone(), delegate, block_event_rx).await?);
+        let view_manager = Arc::new(
+            GridViewManager::new(
+                grid_id.to_owned(),
+                user.clone(),
+                delegate,
+                cell_data_cache.clone(),
+                block_event_rx,
+            )
+            .await?,
+        );
 
         let editor = Arc::new(Self {
             grid_id: grid_id.to_owned(),
@@ -82,6 +95,7 @@ impl GridRevisionEditor {
             rev_manager,
             block_manager,
             view_manager,
+            cell_data_cache,
         });
 
         Ok(editor)
@@ -430,6 +444,23 @@ impl GridRevisionEditor {
         Some(CellPB::new(&params.field_id, field_type, cell_bytes.to_vec()))
     }
 
+    pub async fn get_cell_display_str(&self, params: &CellPathParams) -> String {
+        let display_str = || async {
+            let field_rev = self.get_field_rev(&params.field_id).await?;
+            let field_type: FieldType = field_rev.ty.into();
+            let cell_rev = self.get_cell_rev(&params.row_id, &params.field_id).await.ok()??;
+            let type_cell_data: TypeCellData = cell_rev.try_into().ok()?;
+            Some(stringify_cell_data(
+                type_cell_data.cell_str,
+                &field_type,
+                &field_type,
+                &field_rev,
+            ))
+        };
+
+        display_str().await.unwrap_or("".to_string())
+    }
+
     pub async fn get_cell_bytes(&self, params: &CellPathParams) -> Option<CellProtobufBlob> {
         let (_, cell_data) = self.decode_cell_data_from(params).await?;
         Some(cell_data)
@@ -439,7 +470,11 @@ impl GridRevisionEditor {
         let field_rev = self.get_field_rev(&params.field_id).await?;
         let (_, row_rev) = self.block_manager.get_row_rev(&params.row_id).await.ok()??;
         let cell_rev = row_rev.cells.get(&params.field_id)?.clone();
-        Some(decode_type_cell_data(cell_rev.type_cell_data, &field_rev))
+        Some(decode_type_cell_data(
+            cell_rev.type_cell_data,
+            &field_rev,
+            Some(self.cell_data_cache.clone()),
+        ))
     }
 
     pub async fn get_cell_rev(&self, row_id: &str, field_id: &str) -> FlowyResult<Option<CellRevision>> {
@@ -470,7 +505,7 @@ impl GridRevisionEditor {
                 tracing::trace!("field changeset: id:{} / value:{:?}", &field_id, content);
                 let cell_rev = self.get_cell_rev(&row_id, &field_id).await?;
                 // Update the changeset.data property with the return value.
-                content = apply_cell_data_changeset(content, cell_rev, field_rev)?;
+                content = apply_cell_data_changeset(content, cell_rev, field_rev, Some(self.cell_data_cache.clone()))?;
                 let cell_changeset = CellChangesetPB {
                     grid_id,
                     row_id: row_id.clone(),
@@ -588,9 +623,9 @@ impl GridRevisionEditor {
         Ok(())
     }
 
-    pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult<()> {
-        let _ = self.view_manager.create_or_update_sort(params).await?;
-        Ok(())
+    pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult<SortRevision> {
+        let sort_rev = self.view_manager.create_or_update_sort(params).await?;
+        Ok(sort_rev)
     }
 
     pub async fn move_row(&self, params: MoveRowParams) -> FlowyResult<()> {

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

@@ -184,7 +184,7 @@ where
 
             if let Some(cell_rev) = cell_rev {
                 let mut grouped_rows: Vec<GroupedRow> = vec![];
-                let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data, field_rev).1;
+                let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data, field_rev, None).1;
                 let cell_data = cell_bytes.parser::<P>()?;
                 for group in self.group_ctx.groups() {
                     if self.can_group(&group.filter_content, &cell_data) {
@@ -224,7 +224,7 @@ where
         field_rev: &FieldRevision,
     ) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
         if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
-            let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data.clone(), field_rev).1;
+            let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data.clone(), field_rev, None).1;
             let cell_data = cell_bytes.parser::<P>()?;
             let mut changesets = self.add_or_remove_row_in_groups_if_match(row_rev, &cell_data);
 
@@ -247,7 +247,7 @@ where
     ) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
         // if the cell_rev is none, then the row must in the default group.
         if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
-            let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data.clone(), field_rev).1;
+            let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data.clone(), field_rev, None).1;
             let cell_data = cell_bytes.parser::<P>()?;
             if !cell_data.is_empty() {
                 tracing::error!("did_delete_delete_row {:?}", cell_rev.type_cell_data);
@@ -280,7 +280,7 @@ where
         };
 
         if let Some(cell_rev) = cell_rev {
-            let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data, context.field_rev).1;
+            let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data, context.field_rev, None).1;
             let cell_data = cell_bytes.parser::<P>()?;
             Ok(self.move_row(&cell_data, context))
         } else {

+ 60 - 45
frontend/rust-lib/flowy-grid/src/services/sort/controller.rs

@@ -3,50 +3,50 @@
 use crate::entities::FieldType;
 #[allow(unused_attributes)]
 use crate::entities::SortChangesetNotificationPB;
-
 use crate::services::sort::{SortChangeset, SortType};
-
 use flowy_task::TaskDispatcher;
 use grid_rev_model::{CellRevision, FieldRevision, RowRevision, SortCondition, SortRevision};
 use lib_infra::future::Fut;
 
+use crate::services::cell::{AtomicCellDataCache, TypeCellData};
+use crate::services::field::{default_order, TypeOptionCellExt};
+use rayon::prelude::ParallelSliceMut;
 use std::cmp::Ordering;
-use std::collections::HashMap;
+
 use std::sync::Arc;
 use tokio::sync::RwLock;
 
 pub trait SortDelegate: Send + Sync {
-    fn get_sort_rev(&self, sort_type: SortType) -> Fut<Vec<Arc<SortRevision>>>;
+    fn get_sort_rev(&self, sort_type: SortType) -> Fut<Option<Arc<SortRevision>>>;
     fn get_field_rev(&self, field_id: &str) -> Fut<Option<Arc<FieldRevision>>>;
     fn get_field_revs(&self, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<FieldRevision>>>;
 }
 
 pub struct SortController {
-    #[allow(dead_code)]
-    view_id: String,
-    #[allow(dead_code)]
     handler_id: String,
-    #[allow(dead_code)]
     delegate: Box<dyn SortDelegate>,
     task_scheduler: Arc<RwLock<TaskDispatcher>>,
-    #[allow(dead_code)]
-    sorts: Vec<SortRevision>,
-    #[allow(dead_code)]
-    row_orders: HashMap<String, usize>,
+    sorts: Vec<Arc<SortRevision>>,
+    cell_data_cache: AtomicCellDataCache,
 }
 
 impl SortController {
-    pub fn new<T>(view_id: &str, handler_id: &str, delegate: T, task_scheduler: Arc<RwLock<TaskDispatcher>>) -> Self
+    pub fn new<T>(
+        _view_id: &str,
+        handler_id: &str,
+        delegate: T,
+        task_scheduler: Arc<RwLock<TaskDispatcher>>,
+        cell_data_cache: AtomicCellDataCache,
+    ) -> Self
     where
         T: SortDelegate + 'static,
     {
         Self {
-            view_id: view_id.to_string(),
             handler_id: handler_id.to_string(),
             delegate: Box::new(delegate),
             task_scheduler,
             sorts: vec![],
-            row_orders: HashMap::new(),
+            cell_data_cache,
         }
     }
 
@@ -58,35 +58,53 @@ impl SortController {
             .await;
     }
 
-    pub fn sort_rows(&self, _rows: &mut Vec<Arc<RowRevision>>) {
-        // rows.par_sort_by(|left, right| cmp_row(left, right, &self.sorts));
+    pub async fn sort_rows(&self, rows: &mut Vec<Arc<RowRevision>>) {
+        let field_revs = self.delegate.get_field_revs(None).await;
+        rows.par_sort_by(|left, right| cmp_row(left, right, &self.sorts, &field_revs, &self.cell_data_cache));
     }
 
-    pub async fn did_receive_changes(&mut self, _changeset: SortChangeset) -> Option<SortChangesetNotificationPB> {
+    #[tracing::instrument(level = "trace", skip(self))]
+    pub async fn did_receive_changes(&mut self, changeset: SortChangeset) -> Option<SortChangesetNotificationPB> {
+        if let Some(insert_sort) = changeset.insert_sort {
+            if let Some(sort) = self.delegate.get_sort_rev(insert_sort).await {
+                self.sorts.push(sort);
+            }
+        }
+
+        if let Some(delete_sort_type) = changeset.delete_sort {
+            if let Some(index) = self.sorts.iter().position(|sort| sort.id == delete_sort_type.sort_id) {
+                self.sorts.remove(index);
+            }
+        }
+
+        if let Some(_update_sort) = changeset.update_sort {
+            //
+        }
+
         None
     }
 }
 
-#[allow(dead_code)]
 fn cmp_row(
     left: &Arc<RowRevision>,
     right: &Arc<RowRevision>,
-    sorts: &[SortRevision],
+    sorts: &[Arc<SortRevision>],
     field_revs: &[Arc<FieldRevision>],
+    cell_data_cache: &AtomicCellDataCache,
 ) -> Ordering {
-    let mut order = Ordering::Equal;
+    let mut order = default_order();
     for sort in sorts.iter() {
         let cmp_order = match (left.cells.get(&sort.field_id), right.cells.get(&sort.field_id)) {
             (Some(left_cell), Some(right_cell)) => {
                 let field_type: FieldType = sort.field_type.into();
                 match field_revs.iter().find(|field_rev| field_rev.id == sort.field_id) {
-                    None => Ordering::Equal,
-                    Some(field_rev) => cmp_cell(left_cell, right_cell, field_rev, field_type),
+                    None => default_order(),
+                    Some(field_rev) => cmp_cell(left_cell, right_cell, field_rev, field_type, cell_data_cache),
                 }
             }
             (Some(_), None) => Ordering::Greater,
             (None, Some(_)) => Ordering::Less,
-            _ => Ordering::Equal,
+            _ => default_order(),
         };
 
         if cmp_order.is_ne() {
@@ -101,29 +119,26 @@ fn cmp_row(
     order
 }
 
-#[allow(dead_code)]
 fn cmp_cell(
-    _left: &CellRevision,
-    _right: &CellRevision,
-    _field_rev: &Arc<FieldRevision>,
+    left_cell: &CellRevision,
+    right_cell: &CellRevision,
+    field_rev: &Arc<FieldRevision>,
     field_type: FieldType,
+    cell_data_cache: &AtomicCellDataCache,
 ) -> Ordering {
-    let cal_order = || {
-        let order = match &field_type {
-            // FieldType::RichText => {
-            //     let left_cell = TypeCellData::try_from(left).ok()?.into();
-            //     let right_cell = TypeCellData::try_from(right).ok()?.into();
-            //     field_rev
-            //         .get_type_option::<RichTextTypeOptionPB>(field_rev.ty)?
-            //         .apply_cmp(&left_cell, &right_cell)
-            // }
-            // FieldType::Number => field_rev
-            //     .get_type_option::<NumberTypeOptionPB>(field_rev.ty)?
-            //     .apply_cmp(&left_cell, &right_cell),
-            _ => Ordering::Equal,
-        };
-        Option::<Ordering>::Some(order)
-    };
+    match TypeOptionCellExt::new_with_cell_data_cache(field_rev.as_ref(), Some(cell_data_cache.clone()))
+        .get_type_option_cell_data_handler(&field_type)
+    {
+        None => Ordering::Less,
+        Some(handler) => {
+            let cal_order = || {
+                let left_cell_str = TypeCellData::try_from(left_cell).ok()?.into_inner();
+                let right_cell_str = TypeCellData::try_from(right_cell).ok()?.into_inner();
+                let order = handler.handle_cell_compare(&left_cell_str, &right_cell_str, field_rev.as_ref());
+                Option::<Ordering>::Some(order)
+            };
 
-    cal_order().unwrap_or(Ordering::Equal)
+            cal_order().unwrap_or_else(default_order)
+        }
+    }
 }

+ 19 - 4
frontend/rust-lib/flowy-grid/src/services/sort/entities.rs

@@ -1,4 +1,4 @@
-use crate::entities::{AlterSortParams, FieldType};
+use crate::entities::{AlterSortParams, DeleteSortParams, FieldType};
 use grid_rev_model::{FieldRevision, FieldTypeRevision};
 use std::sync::Arc;
 
@@ -37,7 +37,7 @@ impl std::convert::From<&Arc<FieldRevision>> for SortType {
 pub struct SortChangeset {
     pub(crate) insert_sort: Option<SortType>,
     pub(crate) update_sort: Option<SortType>,
-    pub(crate) delete_sort: Option<SortType>,
+    pub(crate) delete_sort: Option<DeletedSortType>,
 }
 
 impl SortChangeset {
@@ -57,11 +57,26 @@ impl SortChangeset {
         }
     }
 
-    pub fn from_delete(sort: SortType) -> Self {
+    pub fn from_delete(deleted_sort: DeletedSortType) -> Self {
         Self {
             insert_sort: None,
             update_sort: None,
-            delete_sort: Some(sort),
+            delete_sort: Some(deleted_sort),
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct DeletedSortType {
+    pub sort_type: SortType,
+    pub sort_id: String,
+}
+
+impl std::convert::From<DeleteSortParams> for DeletedSortType {
+    fn from(params: DeleteSortParams) -> Self {
+        Self {
+            sort_type: params.sort_type,
+            sort_id: params.sort_id,
         }
     }
 }

+ 29 - 11
frontend/rust-lib/flowy-grid/src/services/view_editor/editor.rs

@@ -1,13 +1,14 @@
 use crate::dart_notification::{send_dart_notification, GridDartNotification};
 use crate::entities::*;
 use crate::services::block_manager::GridBlockEvent;
+use crate::services::cell::AtomicCellDataCache;
 use crate::services::filter::{FilterChangeset, FilterController, FilterTaskHandler, FilterType, UpdatedFilterType};
 use crate::services::group::{
     default_group_configuration, find_group_field, make_group_controller, Group, GroupConfigurationReader,
     GroupController, MoveGroupRowContext,
 };
 use crate::services::row::GridBlockRowRevision;
-use crate::services::sort::{SortChangeset, SortController, SortTaskHandler, SortType};
+use crate::services::sort::{DeletedSortType, SortChangeset, SortController, SortTaskHandler, SortType};
 use crate::services::view_editor::changed_notifier::GridViewChangedNotifier;
 use crate::services::view_editor::trait_impl::*;
 use crate::services::view_editor::GridViewChangedReceiverRunner;
@@ -78,6 +79,7 @@ impl GridViewRevisionEditor {
         token: &str,
         view_id: String,
         delegate: Arc<dyn GridViewEditorDelegate>,
+        cell_data_cache: AtomicCellDataCache,
         mut rev_manager: RevisionManager<Arc<ConnectionPool>>,
     ) -> FlowyResult<Self> {
         let (notifier, _) = broadcast::channel(100);
@@ -110,12 +112,24 @@ impl GridViewRevisionEditor {
         )
         .await?;
 
-        let sort_controller = make_sort_controller(&view_id, delegate.clone(), view_rev_pad.clone()).await;
+        let sort_controller = make_sort_controller(
+            &view_id,
+            delegate.clone(),
+            view_rev_pad.clone(),
+            cell_data_cache.clone(),
+        )
+        .await;
 
         let user_id = user_id.to_owned();
         let group_controller = Arc::new(RwLock::new(group_controller));
-        let filter_controller =
-            make_filter_controller(&view_id, delegate.clone(), notifier.clone(), view_rev_pad.clone()).await;
+        let filter_controller = make_filter_controller(
+            &view_id,
+            delegate.clone(),
+            notifier.clone(),
+            cell_data_cache,
+            view_rev_pad.clone(),
+        )
+        .await;
         Ok(Self {
             pad: view_rev_pad,
             user_id,
@@ -167,7 +181,7 @@ impl GridViewRevisionEditor {
     }
 
     pub async fn sort_rows(&self, rows: &mut Vec<Arc<RowRevision>>) {
-        self.sort_controller.read().await.sort_rows(rows)
+        self.sort_controller.read().await.sort_rows(rows).await
     }
 
     pub async fn filter_rows(&self, _block_id: &str, mut rows: Vec<Arc<RowRevision>>) -> Vec<Arc<RowRevision>> {
@@ -374,7 +388,7 @@ impl GridViewRevisionEditor {
         .await
     }
 
-    pub async fn insert_view_sort(&self, params: AlterSortParams) -> FlowyResult<()> {
+    pub async fn insert_view_sort(&self, params: AlterSortParams) -> FlowyResult<SortRevision> {
         let sort_type = SortType::from(&params);
         let is_exist = params.sort_id.is_some();
         let sort_id = match params.sort_id {
@@ -392,7 +406,7 @@ impl GridViewRevisionEditor {
         let mut sort_controller = self.sort_controller.write().await;
         let changeset = if is_exist {
             self.modify(|pad| {
-                let changeset = pad.update_sort(&params.field_id, sort_rev)?;
+                let changeset = pad.update_sort(&params.field_id, sort_rev.clone())?;
                 Ok(changeset)
             })
             .await?;
@@ -401,7 +415,7 @@ impl GridViewRevisionEditor {
                 .await
         } else {
             self.modify(|pad| {
-                let changeset = pad.insert_sort(&params.field_id, sort_rev)?;
+                let changeset = pad.insert_sort(&params.field_id, sort_rev.clone())?;
                 Ok(changeset)
             })
             .await?;
@@ -413,18 +427,18 @@ impl GridViewRevisionEditor {
         if let Some(changeset) = changeset {
             self.notify_did_update_sort(changeset).await;
         }
-        Ok(())
+        Ok(sort_rev)
     }
 
     pub async fn delete_view_sort(&self, params: DeleteSortParams) -> FlowyResult<()> {
-        let sort_type = params.sort_type;
         let changeset = self
             .sort_controller
             .write()
             .await
-            .did_receive_changes(SortChangeset::from_delete(sort_type.clone()))
+            .did_receive_changes(SortChangeset::from_delete(DeletedSortType::from(params.clone())))
             .await;
 
+        let sort_type = params.sort_type;
         let _ = self
             .modify(|pad| {
                 let changeset = pad.delete_sort(&params.sort_id, &sort_type.field_id, sort_type.field_type)?;
@@ -710,6 +724,7 @@ async fn make_filter_controller(
     view_id: &str,
     delegate: Arc<dyn GridViewEditorDelegate>,
     notifier: GridViewChangedNotifier,
+    cell_data_cache: AtomicCellDataCache,
     pad: Arc<RwLock<GridViewRevisionPad>>,
 ) -> Arc<RwLock<FilterController>> {
     let field_revs = delegate.get_field_revs(None).await;
@@ -726,6 +741,7 @@ async fn make_filter_controller(
         filter_delegate,
         task_scheduler.clone(),
         filter_revs,
+        cell_data_cache,
         notifier,
     )
     .await;
@@ -741,6 +757,7 @@ async fn make_sort_controller(
     view_id: &str,
     delegate: Arc<dyn GridViewEditorDelegate>,
     pad: Arc<RwLock<GridViewRevisionPad>>,
+    cell_data_cache: AtomicCellDataCache,
 ) -> Arc<RwLock<SortController>> {
     let handler_id = gen_handler_id();
     let sort_delegate = GridViewSortDelegateImpl {
@@ -753,6 +770,7 @@ async fn make_sort_controller(
         &handler_id,
         sort_delegate,
         task_scheduler.clone(),
+        cell_data_cache,
     )));
     task_scheduler
         .write()

+ 15 - 3
frontend/rust-lib/flowy-grid/src/services/view_editor/editor_manager.rs

@@ -4,6 +4,7 @@ use crate::entities::{
 };
 use crate::manager::GridUser;
 use crate::services::block_manager::GridBlockEvent;
+use crate::services::cell::AtomicCellDataCache;
 use crate::services::filter::FilterType;
 use crate::services::persistence::rev_sqlite::{
     SQLiteGridRevisionSnapshotPersistence, SQLiteGridViewRevisionPersistence,
@@ -14,7 +15,7 @@ use crate::services::view_editor::{GridViewEditorDelegate, GridViewRevisionEdito
 use flowy_database::ConnectionPool;
 use flowy_error::FlowyResult;
 use flowy_revision::{RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration};
-use grid_rev_model::{FieldRevision, FilterRevision, RowChangeset, RowRevision};
+use grid_rev_model::{FieldRevision, FilterRevision, RowChangeset, RowRevision, SortRevision};
 use lib_infra::future::Fut;
 use lib_infra::ref_map::RefCountHashMap;
 use std::borrow::Cow;
@@ -26,6 +27,7 @@ pub struct GridViewManager {
     user: Arc<dyn GridUser>,
     delegate: Arc<dyn GridViewEditorDelegate>,
     view_editors: Arc<RwLock<RefCountHashMap<Arc<GridViewRevisionEditor>>>>,
+    cell_data_cache: AtomicCellDataCache,
 }
 
 impl GridViewManager {
@@ -33,6 +35,7 @@ impl GridViewManager {
         grid_id: String,
         user: Arc<dyn GridUser>,
         delegate: Arc<dyn GridViewEditorDelegate>,
+        cell_data_cache: AtomicCellDataCache,
         block_event_rx: broadcast::Receiver<GridBlockEvent>,
     ) -> FlowyResult<Self> {
         let view_editors = Arc::new(RwLock::new(RefCountHashMap::default()));
@@ -41,6 +44,7 @@ impl GridViewManager {
             grid_id,
             user,
             delegate,
+            cell_data_cache,
             view_editors,
         })
     }
@@ -145,7 +149,7 @@ impl GridViewManager {
         view_editor.delete_view_filter(params).await
     }
 
-    pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult<()> {
+    pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult<SortRevision> {
         let view_editor = self.get_view_editor(&params.view_id).await?;
         view_editor.insert_view_sort(params).await
     }
@@ -250,7 +254,15 @@ impl GridViewManager {
         let token = self.user.token()?;
         let view_id = view_id.to_owned();
 
-        GridViewRevisionEditor::new(&user_id, &token, view_id, self.delegate.clone(), rev_manager).await
+        GridViewRevisionEditor::new(
+            &user_id,
+            &token,
+            view_id,
+            self.delegate.clone(),
+            self.cell_data_cache.clone(),
+            rev_manager,
+        )
+        .await
     }
 }
 

+ 9 - 2
frontend/rust-lib/flowy-grid/src/services/view_editor/trait_impl.rs

@@ -172,11 +172,18 @@ pub(crate) struct GridViewSortDelegateImpl {
 }
 
 impl SortDelegate for GridViewSortDelegateImpl {
-    fn get_sort_rev(&self, sort_type: SortType) -> Fut<Vec<Arc<SortRevision>>> {
+    fn get_sort_rev(&self, sort_type: SortType) -> Fut<Option<Arc<SortRevision>>> {
         let pad = self.view_revision_pad.clone();
         to_fut(async move {
             let field_type_rev: FieldTypeRevision = sort_type.field_type.into();
-            pad.read().await.get_sorts(&sort_type.field_id, &field_type_rev)
+            let mut sorts = pad.read().await.get_sorts(&sort_type.field_id, &field_type_rev);
+            if sorts.is_empty() {
+                None
+            } else {
+                // Currently, one sort_type should have one sort.
+                debug_assert_eq!(sorts.len(), 1);
+                sorts.pop()
+            }
         })
     }
 

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

@@ -3,6 +3,7 @@ use std::sync::Arc;
 
 use flowy_grid::services::field::{
     ChecklistTypeOptionPB, DateCellChangeset, MultiSelectTypeOptionPB, SelectOptionPB, SingleSelectTypeOptionPB,
+    URLCellData,
 };
 use flowy_grid::services::row::RowRevisionBuilder;
 use grid_rev_model::{FieldRevision, RowRevision};
@@ -59,7 +60,8 @@ impl<'a> GridRowTestBuilder<'a> {
 
     pub fn insert_url_cell(&mut self, data: &str) -> String {
         let url_field = self.field_rev_with_type(&FieldType::URL);
-        self.inner_builder.insert_text_cell(&url_field.id, data.to_string());
+        let url_data = URLCellData::new(data).to_json().unwrap();
+        self.inner_builder.insert_text_cell(&url_field.id, url_data);
         url_field.id.clone()
     }
 

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

@@ -53,7 +53,7 @@ async fn grid_filter_multi_select_is_test2() {
             condition: SelectOptionConditionPB::OptionIs,
             option_ids: vec![options.remove(1).id],
         },
-        AssertNumberOfVisibleRows { expected: 3 },
+        AssertNumberOfVisibleRows { expected: 2 },
     ];
     test.run_scripts(scripts).await;
 }

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

@@ -269,7 +269,7 @@ fn make_test_grid() -> BuildGridContext {
                         FieldType::Number => row_builder.insert_number_cell("2"),
                         FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
                         FieldType::MultiSelect => row_builder
-                            .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
+                            .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(1)]),
                         FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
                         _ => "".to_owned(),
                     };

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

@@ -1,2 +1,2 @@
-// mod script;
-// mod text_sort_test;
+mod script;
+mod sort_test;

+ 32 - 8
frontend/rust-lib/flowy-grid/tests/grid/sort_test/script.rs

@@ -1,20 +1,32 @@
 use crate::grid::grid_editor::GridEditorTest;
-use flowy_grid::entities::{AlterSortParams, DeleteSortParams};
+use flowy_grid::entities::{AlterSortParams, CellPathParams, DeleteSortParams};
+use grid_rev_model::SortRevision;
 
 pub enum SortScript {
-    InsertSort { params: AlterSortParams },
-    DeleteSort { params: DeleteSortParams },
-    AssertTextOrder { orders: Vec<String> },
+    InsertSort {
+        params: AlterSortParams,
+    },
+    DeleteSort {
+        params: DeleteSortParams,
+    },
+    AssertTextOrder {
+        field_id: String,
+        orders: Vec<&'static str>,
+    },
 }
 
 pub struct GridSortTest {
     inner: GridEditorTest,
+    pub current_sort_rev: Option<SortRevision>,
 }
 
 impl GridSortTest {
     pub async fn new() -> Self {
         let editor_test = GridEditorTest::new_table().await;
-        Self { inner: editor_test }
+        Self {
+            inner: editor_test,
+            current_sort_rev: None,
+        }
     }
     pub async fn run_scripts(&mut self, scripts: Vec<SortScript>) {
         for script in scripts {
@@ -25,14 +37,26 @@ impl GridSortTest {
     pub async fn run_script(&mut self, script: SortScript) {
         match script {
             SortScript::InsertSort { params } => {
-                let _ = self.editor.create_or_update_sort(params).await.unwrap();
+                let sort_rev = self.editor.create_or_update_sort(params).await.unwrap();
+                self.current_sort_rev = Some(sort_rev);
             }
             SortScript::DeleteSort { params } => {
                 //
                 self.editor.delete_sort(params).await.unwrap();
             }
-            SortScript::AssertTextOrder { orders: _ } => {
-                //
+            SortScript::AssertTextOrder { field_id, orders } => {
+                let mut cells = vec![];
+                let rows = self.editor.get_grid(&self.grid_id).await.unwrap().rows;
+                for row in rows {
+                    let params = CellPathParams {
+                        view_id: self.grid_id.clone(),
+                        field_id: field_id.clone(),
+                        row_id: row.id,
+                    };
+                    let cell = self.editor.get_cell_display_str(&params).await;
+                    cells.push(cell);
+                }
+                assert_eq!(cells, orders)
             }
         }
     }

+ 279 - 0
frontend/rust-lib/flowy-grid/tests/grid/sort_test/sort_test.rs

@@ -0,0 +1,279 @@
+use crate::grid::sort_test::script::{GridSortTest, SortScript::*};
+use flowy_grid::entities::{AlterSortParams, DeleteSortParams, FieldType};
+use flowy_grid::services::sort::SortType;
+use grid_rev_model::SortCondition;
+
+#[tokio::test]
+async fn sort_text_by_ascending_test() {
+    let mut test = GridSortTest::new().await;
+    let text_field = test.get_first_field_rev(FieldType::RichText);
+    let view_id = test.grid_id.clone();
+    let scripts = vec![
+        AssertTextOrder {
+            field_id: text_field.id.clone(),
+            orders: vec!["A", "", "C", "DA", "AE"],
+        },
+        InsertSort {
+            params: AlterSortParams {
+                view_id,
+                field_id: text_field.id.clone(),
+                sort_id: None,
+                field_type: FieldType::RichText.into(),
+                condition: SortCondition::Ascending.into(),
+            },
+        },
+        AssertTextOrder {
+            field_id: text_field.id.clone(),
+            orders: vec!["", "A", "AE", "C", "DA"],
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn sort_text_by_ascending_and_delete_sort_test() {
+    let mut test = GridSortTest::new().await;
+    let text_field = test.get_first_field_rev(FieldType::RichText).clone();
+    let view_id = test.grid_id.clone();
+    let scripts = vec![InsertSort {
+        params: AlterSortParams {
+            view_id: view_id.clone(),
+            field_id: text_field.id.clone(),
+            sort_id: None,
+            field_type: FieldType::RichText.into(),
+            condition: SortCondition::Ascending.into(),
+        },
+    }];
+    test.run_scripts(scripts).await;
+    let sort_rev = test.current_sort_rev.as_ref().unwrap();
+    let scripts = vec![
+        DeleteSort {
+            params: DeleteSortParams {
+                view_id,
+                sort_type: SortType::from(&text_field),
+                sort_id: sort_rev.id.clone(),
+            },
+        },
+        AssertTextOrder {
+            field_id: text_field.id.clone(),
+            orders: vec!["A", "", "C", "DA", "AE"],
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn sort_text_by_descending_test() {
+    let mut test = GridSortTest::new().await;
+    let text_field = test.get_first_field_rev(FieldType::RichText);
+    let view_id = test.grid_id.clone();
+    let scripts = vec![
+        AssertTextOrder {
+            field_id: text_field.id.clone(),
+            orders: vec!["A", "", "C", "DA", "AE"],
+        },
+        InsertSort {
+            params: AlterSortParams {
+                view_id,
+                field_id: text_field.id.clone(),
+                sort_id: None,
+                field_type: FieldType::RichText.into(),
+                condition: SortCondition::Descending.into(),
+            },
+        },
+        AssertTextOrder {
+            field_id: text_field.id.clone(),
+            orders: vec!["DA", "C", "AE", "A", ""],
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn sort_checkbox_by_ascending_test() {
+    let mut test = GridSortTest::new().await;
+    let checkbox_field = test.get_first_field_rev(FieldType::Checkbox);
+    let view_id = test.grid_id.clone();
+    let scripts = vec![
+        AssertTextOrder {
+            field_id: checkbox_field.id.clone(),
+            orders: vec!["Yes", "Yes", "No", "No", "No"],
+        },
+        InsertSort {
+            params: AlterSortParams {
+                view_id,
+                field_id: checkbox_field.id.clone(),
+                sort_id: None,
+                field_type: FieldType::Checkbox.into(),
+                condition: SortCondition::Ascending.into(),
+            },
+        },
+        // AssertTextOrder {
+        //     field_id: checkbox_field.id.clone(),
+        //     orders: vec!["No", "No", "No", "Yes", "Yes"],
+        // },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn sort_checkbox_by_descending_test() {
+    let mut test = GridSortTest::new().await;
+    let checkbox_field = test.get_first_field_rev(FieldType::Checkbox);
+    let view_id = test.grid_id.clone();
+    let scripts = vec![
+        AssertTextOrder {
+            field_id: checkbox_field.id.clone(),
+            orders: vec!["Yes", "Yes", "No", "No", "No"],
+        },
+        InsertSort {
+            params: AlterSortParams {
+                view_id,
+                field_id: checkbox_field.id.clone(),
+                sort_id: None,
+                field_type: FieldType::Checkbox.into(),
+                condition: SortCondition::Descending.into(),
+            },
+        },
+        AssertTextOrder {
+            field_id: checkbox_field.id.clone(),
+            orders: vec!["Yes", "Yes", "No", "No", "No"],
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn sort_date_by_ascending_test() {
+    let mut test = GridSortTest::new().await;
+    let date_field = test.get_first_field_rev(FieldType::DateTime);
+    let view_id = test.grid_id.clone();
+    let scripts = vec![
+        AssertTextOrder {
+            field_id: date_field.id.clone(),
+            orders: vec!["2022/03/14", "2022/03/14", "2022/03/14", "2022/11/17", "2022/11/13"],
+        },
+        InsertSort {
+            params: AlterSortParams {
+                view_id,
+                field_id: date_field.id.clone(),
+                sort_id: None,
+                field_type: FieldType::DateTime.into(),
+                condition: SortCondition::Ascending.into(),
+            },
+        },
+        AssertTextOrder {
+            field_id: date_field.id.clone(),
+            orders: vec!["2022/03/14", "2022/03/14", "2022/03/14", "2022/11/13", "2022/11/17"],
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn sort_date_by_descending_test() {
+    let mut test = GridSortTest::new().await;
+    let date_field = test.get_first_field_rev(FieldType::DateTime);
+    let view_id = test.grid_id.clone();
+    let scripts = vec![
+        AssertTextOrder {
+            field_id: date_field.id.clone(),
+            orders: vec!["2022/03/14", "2022/03/14", "2022/03/14", "2022/11/17", "2022/11/13"],
+        },
+        InsertSort {
+            params: AlterSortParams {
+                view_id,
+                field_id: date_field.id.clone(),
+                sort_id: None,
+                field_type: FieldType::DateTime.into(),
+                condition: SortCondition::Descending.into(),
+            },
+        },
+        AssertTextOrder {
+            field_id: date_field.id.clone(),
+            orders: vec!["2022/11/17", "2022/11/13", "2022/03/14", "2022/03/14", "2022/03/14"],
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn sort_number_by_descending_test() {
+    let mut test = GridSortTest::new().await;
+    let number_field = test.get_first_field_rev(FieldType::Number);
+    let view_id = test.grid_id.clone();
+    let scripts = vec![
+        AssertTextOrder {
+            field_id: number_field.id.clone(),
+            orders: vec!["$1", "$2", "$3", "$4", ""],
+        },
+        InsertSort {
+            params: AlterSortParams {
+                view_id,
+                field_id: number_field.id.clone(),
+                sort_id: None,
+                field_type: FieldType::Number.into(),
+                condition: SortCondition::Descending.into(),
+            },
+        },
+        AssertTextOrder {
+            field_id: number_field.id.clone(),
+            orders: vec!["$4", "$3", "$2", "$1", ""],
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn sort_single_select_by_descending_test() {
+    let mut test = GridSortTest::new().await;
+    let single_select = test.get_first_field_rev(FieldType::SingleSelect);
+    let view_id = test.grid_id.clone();
+    let scripts = vec![
+        AssertTextOrder {
+            field_id: single_select.id.clone(),
+            orders: vec!["", "", "Completed", "Completed", "Planned"],
+        },
+        InsertSort {
+            params: AlterSortParams {
+                view_id,
+                field_id: single_select.id.clone(),
+                sort_id: None,
+                field_type: FieldType::SingleSelect.into(),
+                condition: SortCondition::Descending.into(),
+            },
+        },
+        AssertTextOrder {
+            field_id: single_select.id.clone(),
+            orders: vec!["Planned", "Completed", "Completed", "", ""],
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn sort_multi_select_by_ascending_test() {
+    let mut test = GridSortTest::new().await;
+    let multi_select = test.get_first_field_rev(FieldType::MultiSelect);
+    let view_id = test.grid_id.clone();
+    let scripts = vec![
+        AssertTextOrder {
+            field_id: multi_select.id.clone(),
+            orders: vec!["Google,Facebook", "Google,Twitter", "Facebook", "", ""],
+        },
+        InsertSort {
+            params: AlterSortParams {
+                view_id,
+                field_id: multi_select.id.clone(),
+                sort_id: None,
+                field_type: FieldType::MultiSelect.into(),
+                condition: SortCondition::Ascending.into(),
+            },
+        },
+        AssertTextOrder {
+            field_id: multi_select.id.clone(),
+            orders: vec!["", "", "Facebook", "Google,Facebook", "Google,Twitter"],
+        },
+    ];
+    test.run_scripts(scripts).await;
+}

+ 0 - 0
frontend/rust-lib/flowy-grid/tests/grid/sort_test/text_sort_test.rs


+ 6 - 0
shared-lib/grid-rev-model/src/sort_rev.rs

@@ -32,3 +32,9 @@ impl std::default::Default for SortCondition {
         Self::Ascending
     }
 }
+
+impl std::convert::From<SortCondition> for u8 {
+    fn from(condition: SortCondition) -> Self {
+        condition as u8
+    }
+}