From 5a30f46b85f5bf0cb2ae508f97b223a1e401ec80 Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Sat, 24 Dec 2022 23:19:11 +0800 Subject: [PATCH] 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 --- .../cell_service/cell_data_persistence.dart | 2 +- .../cell/cell_service/cell_service.dart | 2 +- .../cell/select_option_service.dart | 6 +- .../widgets/emoji_picker/src/emoji_lists.dart | 4 +- .../lib/src/emoji_picker/src/emoji_lists.dart | 4 +- frontend/rust-lib/Cargo.lock | 1 + frontend/rust-lib/flowy-grid/Cargo.toml | 1 + .../flowy-grid/src/entities/cell_entities.rs | 12 +- .../filter_entities/checkbox_filter.rs | 13 + .../filter_entities/checklist_filter.rs | 13 + .../entities/filter_entities/date_filter.rs | 21 + .../entities/filter_entities/number_filter.rs | 13 + .../filter_entities/select_option_filter.rs | 14 + .../entities/filter_entities/text_filter.rs | 13 + .../flowy-grid/src/entities/sort_entities.rs | 2 +- .../src/services/cell/cell_data_cache.rs | 121 +++++ .../src/services/cell/cell_operation.rs | 56 +-- .../flowy-grid/src/services/cell/mod.rs | 2 + .../checkbox_type_option/checkbox_filter.rs | 18 +- .../checkbox_type_option.rs | 45 +- .../checkbox_type_option_entities.rs | 2 +- .../date_type_option/date_filter.rs | 20 +- .../date_type_option/date_tests.rs | 2 +- .../date_type_option/date_type_option.rs | 45 +- .../date_type_option_entities.rs | 11 +- .../src/services/field/type_options/mod.rs | 2 + .../number_type_option/number_filter.rs | 23 +- .../number_type_option/number_type_option.rs | 46 +- .../checklist_type_option.rs | 40 +- .../multi_select_type_option.rs | 89 ++-- .../selection_type_option/select_filter.rs | 42 +- .../select_type_option.rs | 2 +- .../single_select_type_option.rs | 67 ++- .../text_type_option/text_filter.rs | 18 - .../text_type_option/text_tests.rs | 16 +- .../text_type_option/text_type_option.rs | 45 +- .../field/type_options/type_option.rs | 215 +-------- .../field/type_options/type_option_cell.rs | 420 ++++++++++++++++++ .../field/type_options/url_type_option/mod.rs | 1 - .../url_type_option/url_filter.rs | 18 - .../type_options/url_type_option/url_tests.rs | 37 +- .../url_type_option/url_type_option.rs | 41 +- .../url_type_option_entities.rs | 2 +- .../flowy-grid/src/services/filter/cache.rs | 117 ----- .../src/services/filter/controller.rs | 177 +++----- .../flowy-grid/src/services/filter/mod.rs | 2 - .../flowy-grid/src/services/filter/task.rs | 21 +- .../flowy-grid/src/services/grid_editor.rs | 51 ++- .../src/services/group/controller.rs | 8 +- .../src/services/sort/controller.rs | 105 +++-- .../flowy-grid/src/services/sort/entities.rs | 23 +- .../src/services/view_editor/editor.rs | 40 +- .../services/view_editor/editor_manager.rs | 18 +- .../src/services/view_editor/trait_impl.rs | 11 +- .../flowy-grid/tests/grid/block_test/util.rs | 4 +- .../filter_test/select_option_filter_test.rs | 2 +- .../flowy-grid/tests/grid/grid_editor.rs | 2 +- .../flowy-grid/tests/grid/sort_test/mod.rs | 4 +- .../flowy-grid/tests/grid/sort_test/script.rs | 40 +- .../tests/grid/sort_test/sort_test.rs | 279 ++++++++++++ .../tests/grid/sort_test/text_sort_test.rs | 0 shared-lib/grid-rev-model/src/sort_rev.rs | 6 + 62 files changed, 1628 insertions(+), 849 deletions(-) create mode 100644 frontend/rust-lib/flowy-grid/src/services/cell/cell_data_cache.rs create mode 100644 frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option_cell.rs delete mode 100644 frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_filter.rs delete mode 100644 frontend/rust-lib/flowy-grid/src/services/filter/cache.rs create mode 100644 frontend/rust-lib/flowy-grid/tests/grid/sort_test/sort_test.rs delete mode 100644 frontend/rust-lib/flowy-grid/tests/grid/sort_test/text_sort_test.rs diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_persistence.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_persistence.dart index 253744a28a..17edb59824 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_persistence.dart +++ b/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; } diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart index f4f7f2e633..ef8d91c5d0 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart +++ b/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(); diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart index 5f8639967a..038d6fa792 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart +++ b/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> 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; } diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_lists.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_lists.dart index d012b0e7ca..6cb3681206 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_lists.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_lists.dart @@ -2706,7 +2706,7 @@ final Map 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 flags = Map.fromIterables([ 'Flag: Falkland Islands', 'Flag: Micronesia', 'Flag: Faroe Islands', - 'Flag: France', + 'Flag: france', 'Flag: Gabon', 'Flag: United Kingdom', 'Flag: Grenada', diff --git a/frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/emoji_picker/src/emoji_lists.dart b/frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/emoji_picker/src/emoji_lists.dart index d012b0e7ca..6cb3681206 100644 --- a/frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/emoji_picker/src/emoji_lists.dart +++ b/frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/emoji_picker/src/emoji_lists.dart @@ -2706,7 +2706,7 @@ final Map 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 flags = Map.fromIterables([ 'Flag: Falkland Islands', 'Flag: Micronesia', 'Flag: Faroe Islands', - 'Flag: France', + 'Flag: france', 'Flag: Gabon', 'Flag: United Kingdom', 'Flag: Grenada', diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index ce799c4221..c8578a72f6 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -1021,6 +1021,7 @@ dependencies = [ "lib-infra", "lib-ot", "nanoid", + "parking_lot 0.12.1", "protobuf", "rayon", "regex", diff --git a/frontend/rust-lib/flowy-grid/Cargo.toml b/frontend/rust-lib/flowy-grid/Cargo.toml index f67c88384b..50bd00cf22 100644 --- a/frontend/rust-lib/flowy-grid/Cargo.toml +++ b/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" } diff --git a/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs index ac757f436a..38caa1a63a 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs @@ -41,7 +41,7 @@ impl TryInto 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 for CellPathPB { type Error = ErrorCode; fn try_into(self) -> Result { - 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 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, + /// the field_type will be None if the field with field_id is not found #[pb(index = 3, one_of)] pub field_type: Option, } diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs index 81d659f4e6..b7a30ce1af 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs +++ b/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 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 { diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checklist_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checklist_filter.rs index bbd2480836..91b902eb46 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checklist_filter.rs +++ b/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 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 { diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs index b70b1b0944..cdf40b9f8a 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs +++ b/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 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); diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs index 900059ccab..b8ad9a0da4 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs +++ b/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 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 { diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs index 9b182f9378..75ddcb34a7 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs +++ b/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 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 { diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs index f7a5770f2c..079efa426a 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs +++ b/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 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 { diff --git a/frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs index 5e84201acc..ee74e0d2c9 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs @@ -130,7 +130,7 @@ impl TryInto for DeleteSortPayloadPB { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct DeleteSortParams { pub view_id: String, pub sort_type: SortType, diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/cell_data_cache.rs b/frontend/rust-lib/flowy-grid/src/services/cell/cell_data_cache.rs new file mode 100644 index 0000000000..8f94d193dd --- /dev/null +++ b/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>>; +pub type AtomicCellFilterCache = Arc>>; + +#[derive(Default, Debug)] +pub struct AnyTypeCache(HashMap); + +impl AnyTypeCache +where + TypeValueKey: Clone + Hash + Eq, +{ + pub fn new() -> Arc>> { + Arc::new(RwLock::new(AnyTypeCache(HashMap::default()))) + } + + pub fn insert(&mut self, key: &TypeValueKey, val: T) -> Option + 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>(&mut self, key: K) -> Option + // where + // T: 'static + Send + Sync, + // { + // self.0.remove(key.as_ref()).and_then(downcast_owned) + // } + + pub fn get(&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(&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(type_value: TypeValue) -> Option { + type_value.boxed.downcast().ok().map(|boxed| *boxed) +} + +#[derive(Debug)] +struct TypeValue { + boxed: Box, + #[allow(dead_code)] + ty: &'static str, +} + +impl TypeValue { + pub fn new(value: T) -> Self + where + T: Send + Sync + 'static, + { + Self { + boxed: Box::new(value), + ty: type_name::(), + } + } +} + +impl std::ops::Deref for TypeValue { + type Target = Box; + + 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()); +// } +// } diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs index 3a64650bdb..06340f1b42 100644 --- a/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs +++ b/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: &::CellFilterConfiguration, - ) -> FlowyResult; -} - -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: ::CellChangeset, type_cell_data: Option, - ) -> FlowyResult; + ) -> FlowyResult<::CellData>; } /// changeset: It will be deserialized into specific data base on the FieldType. @@ -70,6 +54,7 @@ pub fn apply_cell_data_changeset>( changeset: C, cell_rev: Option, field_rev: T, + cell_data_cache: Option, ) -> Result { let field_rev = field_rev.as_ref(); let changeset = changeset.to_string(); @@ -80,9 +65,11 @@ pub fn apply_cell_data_changeset>( 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>( pub fn decode_type_cell_data + Debug>( data: T, field_rev: &FieldRevision, + cell_data_cache: Option, ) -> (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, ) -> FlowyResult { - 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, 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, 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) } diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/mod.rs b/frontend/rust-lib/flowy-grid/src/services/cell/mod.rs index 0df4561559..fecc08a024 100644 --- a/frontend/rust-lib/flowy-grid/src/services/cell/mod.rs +++ b/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::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs index 8a79491324..e98454de81 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs +++ b/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: &::CellFilterConfiguration, - ) -> FlowyResult { - 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}; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs index d05a26d777..1ca6ef8002 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs +++ b/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: ::CellData) -> ::CellProtobufType { cell_data @@ -89,8 +87,37 @@ impl CellDataChangeset for CheckboxTypeOptionPB { &self, changeset: ::CellChangeset, _type_cell_data: Option, - ) -> FlowyResult { - let cell_data = CheckboxCellData::from_str(&changeset)?; - Ok(cell_data.to_string()) + ) -> FlowyResult<::CellData> { + let checkbox_cell_data = CheckboxCellData::from_str(&changeset)?; + Ok(checkbox_cell_data) + } +} + +impl TypeOptionCellDataFilter for CheckboxTypeOptionPB { + fn apply_filter( + &self, + filter: &::CellFilter, + field_type: &FieldType, + cell_data: &::CellData, + ) -> bool { + if !field_type.is_checkbox() { + return true; + } + filter.is_visible(cell_data) + } +} + +impl TypeOptionCellDataCompare for CheckboxTypeOptionPB { + fn apply_cmp( + &self, + cell_data: &::CellData, + other_cell_data: &::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(), + } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs index c1678d994e..04c48823c0 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs +++ b/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 { diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_filter.rs index aadb941d4a..da617eb096 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_filter.rs +++ b/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>>(&self, cell_timestamp: T) -> bool { @@ -60,21 +57,6 @@ impl DateFilterPB { } } -impl CellFilterable for DateTypeOptionPB { - fn apply_filter( - &self, - type_cell_data: TypeCellData, - filter: &::CellFilterConfiguration, - ) -> FlowyResult { - 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)] diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_tests.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_tests.rs index a7443cb8c2..ca20c811b5 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_tests.rs +++ b/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(), ); } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs index 443521a471..25dd9c64bc 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs +++ b/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: ::CellChangeset, _type_cell_data: Option, - ) -> FlowyResult { + ) -> FlowyResult<::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: &::CellFilter, + field_type: &FieldType, + cell_data: &::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: &::CellData, + other_cell_data: &::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(), + } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs index aca5f79fdd..12e42b1237 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs +++ b/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); impl std::convert::From 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, diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs index e293a9b084..822ffab987 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs +++ b/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::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_filter.rs index 5206960150..fecc381fb7 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_filter.rs +++ b/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: &::CellFilterConfiguration, - ) -> FlowyResult { - 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}; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs index 1b73277885..7fcae02b66 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs +++ b/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: ::CellChangeset, _type_cell_data: Option, - ) -> FlowyResult { + ) -> FlowyResult<::CellData> { let data = changeset.trim().to_string(); let _ = self.format_cell_data(&data)?; - Ok(data) - } -} -impl CellComparable for NumberTypeOptionPB { - type CellData = NumberCellData; - - fn apply_cmp(&self, _cell_data: &Self::CellData, _other_cell_data: &Self::CellData) -> Ordering { - Ordering::Equal + Ok(StrCellData(data)) } } +impl TypeOptionCellDataFilter for NumberTypeOptionPB { + fn apply_filter( + &self, + filter: &::CellFilter, + field_type: &FieldType, + cell_data: &::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: &::CellData, + other_cell_data: &::CellData, + ) -> Ordering { + cell_data.0.cmp(&other_cell_data.0) + } +} impl std::default::Default for NumberTypeOptionPB { fn default() -> Self { let format = NumberFormat::default(); diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/checklist_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/checklist_type_option.rs index 2ef4d71bf7..38ad7236a8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/checklist_type_option.rs +++ b/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: ::CellChangeset, type_cell_data: Option, - ) -> FlowyResult { + ) -> FlowyResult<::CellData> { let insert_option_ids = changeset .insert_option_ids .into_iter() @@ -70,7 +68,7 @@ impl CellDataChangeset for ChecklistTypeOptionPB { .collect::>(); 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: &::CellFilter, + field_type: &FieldType, + cell_data: &::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: &::CellData, + other_cell_data: &::CellData, + ) -> Ordering { + cell_data.len().cmp(&other_cell_data.len()) + } +} #[derive(Default)] pub struct ChecklistTypeOptionBuilder(ChecklistTypeOptionPB); diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs index bff5c2ca7e..45f40d2ba4 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs +++ b/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: ::CellChangeset, type_cell_data: Option, - ) -> FlowyResult { + ) -> FlowyResult<::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::>(); - 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: &::CellFilter, + field_type: &FieldType, + cell_data: &::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: &::CellData, + other_cell_data: &::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()); } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_filter.rs index a7c51773b4..528c4178b8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_filter.rs +++ b/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 { - 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 { - 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 { - 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)] diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_type_option.rs index b5a97b0310..06dea283ec 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_type_option.rs +++ b/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) -> 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); impl SelectOptionIds { diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs index 052ca63fb3..b4999a53bc 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs +++ b/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: ::CellChangeset, _type_cell_data: Option, - ) -> FlowyResult { + ) -> FlowyResult<::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: &::CellFilter, + field_type: &FieldType, + cell_data: &::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: &::CellData, + other_cell_data: &::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()); } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_filter.rs index 143fb04bec..f336418c21 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_filter.rs +++ b/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>(&self, cell_data: T) -> bool { @@ -20,21 +17,6 @@ impl TextFilterPB { } } -impl CellFilterable for RichTextTypeOptionPB { - fn apply_filter( - &self, - type_cell_data: TypeCellData, - filter: &::CellFilterConfiguration, - ) -> FlowyResult { - 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)] diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_tests.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_tests.rs index 8616e3c851..a4138589ed 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_tests.rs +++ b/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) ); } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs index 58857f702d..25d9d9bff2 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs +++ b/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: ::CellData) -> ::CellProtobufType { cell_data @@ -92,23 +89,41 @@ impl CellDataChangeset for RichTextTypeOptionPB { &self, changeset: ::CellChangeset, _type_cell_data: Option, - ) -> FlowyResult { + ) -> FlowyResult<::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: &::CellFilter, + field_type: &FieldType, + cell_data: &::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: &::CellData, + other_cell_data: &::CellData, + ) -> Ordering { + cell_data.0.cmp(&other_cell_data.0) + } +} + +#[derive(Clone)] pub struct TextCellData(pub String); impl AsRef 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; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option.rs index 04bfcd8df2..8d6128b91a 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option.rs +++ b/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 + 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<::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 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; - - fn handle_cell_changeset( - &self, - cell_changeset: String, - old_type_cell_data: Option, - ) -> FlowyResult; - - /// 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 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 { - 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, - ) -> FlowyResult { - let changeset = ::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 ::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: &::CellFilter, field_type: &FieldType, - ) -> Option> { - match field_type { - FieldType::RichText => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| Box::new(type_option) as Box), - FieldType::Number => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| Box::new(type_option) as Box), - FieldType::DateTime => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| Box::new(type_option) as Box), - FieldType::SingleSelect => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| Box::new(type_option) as Box), - FieldType::MultiSelect => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| Box::new(type_option) as Box), - FieldType::Checkbox => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| Box::new(type_option) as Box), - FieldType::URL => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| Box::new(type_option) as Box), - FieldType::Checklist => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| Box::new(type_option) as Box), - } - } + cell_data: &::CellData, + ) -> bool; } -pub fn transform_type_option( - type_option_data: &str, - new_field_type: &FieldType, - old_type_option_data: Option, - 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 { - match field_type { - FieldType::RichText => { - Box::new(RichTextTypeOptionPB::from_json_str(type_option_data)) as Box - } - FieldType::Number => { - Box::new(NumberTypeOptionPB::from_json_str(type_option_data)) as Box - } - FieldType::DateTime => { - Box::new(DateTypeOptionPB::from_json_str(type_option_data)) as Box - } - FieldType::SingleSelect => { - Box::new(SingleSelectTypeOptionPB::from_json_str(type_option_data)) as Box - } - FieldType::MultiSelect => { - Box::new(MultiSelectTypeOptionPB::from_json_str(type_option_data)) as Box - } - FieldType::Checkbox => { - Box::new(CheckboxTypeOptionPB::from_json_str(type_option_data)) as Box - } - FieldType::URL => { - Box::new(URLTypeOptionPB::from_json_str(type_option_data)) as Box - } - FieldType::Checklist => { - Box::new(ChecklistTypeOptionPB::from_json_str(type_option_data)) as Box - } - } +pub trait TypeOptionCellDataCompare: TypeOption { + fn apply_cmp( + &self, + cell_data: &::CellData, + other_cell_data: &::CellData, + ) -> Ordering; } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option_cell.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option_cell.rs new file mode 100644 index 0000000000..a33808d6a3 --- /dev/null +++ b/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; + + fn handle_cell_changeset( + &self, + cell_changeset: String, + old_type_cell_data: Option, + field_rev: &FieldRevision, + ) -> FlowyResult; + + 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 for CellDataCacheKey { + fn as_ref(&self) -> &u64 { + &self.0 + } +} + +struct TypeOptionCellDataHandlerImpl { + inner: T, + cell_data_cache: Option, + cell_filter_cache: Option, +} + +impl TypeOptionCellDataHandlerImpl +where + T: TypeOption + + CellDataDecoder + + CellDataChangeset + + TypeOptionCellData + + TypeOptionTransform + + TypeOptionCellDataFilter + + TypeOptionCellDataCompare + + 'static, +{ + pub fn new_with_boxed( + inner: T, + cell_filter_cache: Option, + cell_data_cache: Option, + ) -> Box { + Box::new(Self { + inner, + cell_data_cache, + cell_filter_cache, + }) as Box + } +} + +impl TypeOptionCellDataHandlerImpl +where + T: TypeOption + CellDataDecoder, +{ + fn get_decoded_cell_data( + &self, + cell_str: String, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> FlowyResult<::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: ::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 std::ops::Deref for TypeOptionCellDataHandlerImpl { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl TypeOption for TypeOptionCellDataHandlerImpl +where + T: TypeOption, +{ + type CellData = T::CellData; + type CellChangeset = T::CellChangeset; + type CellProtobufType = T::CellProtobufType; + type CellFilter = T::CellFilter; +} + +impl TypeOptionCellDataHandler for TypeOptionCellDataHandlerImpl +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 { + 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, + field_rev: &FieldRevision, + ) -> FlowyResult { + let changeset = ::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::<::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 ::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, + cell_filter_cache: Option, +} + +impl<'a> TypeOptionCellExt<'a> { + pub fn new_with_cell_data_cache( + field_rev: &'a FieldRevision, + cell_data_cache: Option, + ) -> Self { + Self { + field_rev, + cell_data_cache, + cell_filter_cache: None, + } + } + + pub fn new( + field_rev: &'a FieldRevision, + cell_data_cache: Option, + cell_filter_cache: Option, + ) -> 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> { + match field_type { + FieldType::RichText => self + .field_rev + .get_type_option::(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::(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::(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::(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::(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::(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::(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::(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, + 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 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 { + match field_type { + FieldType::RichText => { + Box::new(RichTextTypeOptionPB::from_json_str(type_option_data)) as Box + } + FieldType::Number => { + Box::new(NumberTypeOptionPB::from_json_str(type_option_data)) as Box + } + FieldType::DateTime => { + Box::new(DateTypeOptionPB::from_json_str(type_option_data)) as Box + } + FieldType::SingleSelect => { + Box::new(SingleSelectTypeOptionPB::from_json_str(type_option_data)) as Box + } + FieldType::MultiSelect => { + Box::new(MultiSelectTypeOptionPB::from_json_str(type_option_data)) as Box + } + FieldType::Checkbox => { + Box::new(CheckboxTypeOptionPB::from_json_str(type_option_data)) as Box + } + FieldType::URL => { + Box::new(URLTypeOptionPB::from_json_str(type_option_data)) as Box + } + FieldType::Checklist => { + Box::new(ChecklistTypeOptionPB::from_json_str(type_option_data)) as Box + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs index bbe195c628..8f6cb884df 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs +++ b/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; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_filter.rs deleted file mode 100644 index 4932f6a649..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_filter.rs +++ /dev/null @@ -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: &::CellFilterConfiguration, - ) -> FlowyResult { - 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)) - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_tests.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_tests.rs index b934d3d9dc..1816ef1308 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_tests.rs +++ b/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() - } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs index 650114624a..6c17327281 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs +++ b/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: ::CellData) -> ::CellProtobufType { cell_data.into() @@ -83,19 +81,42 @@ impl CellDataChangeset for URLTypeOptionPB { &self, changeset: ::CellChangeset, _type_cell_data: Option, - ) -> FlowyResult { + ) -> FlowyResult<::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, - } - .to_json() + }) } } +impl TypeOptionCellDataFilter for URLTypeOptionPB { + fn apply_filter( + &self, + filter: &::CellFilter, + field_type: &FieldType, + cell_data: &::CellData, + ) -> bool { + if !field_type.is_url() { + return true; + } + + filter.is_visible(&cell_data) + } +} + +impl TypeOptionCellDataCompare for URLTypeOptionPB { + fn apply_cmp( + &self, + cell_data: &::CellData, + other_cell_data: &::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) { diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option_entities.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option_entities.rs index c21162a88c..9f1d3eec0d 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option_entities.rs +++ b/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 { + pub fn to_json(&self) -> FlowyResult { serde_json::to_string(self).map_err(internal_error) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/cache.rs b/frontend/rust-lib/flowy-grid/src/services/filter/cache.rs deleted file mode 100644 index caffc0d777..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/filter/cache.rs +++ /dev/null @@ -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, - pub(crate) url_filter: HashMap, - pub(crate) number_filter: HashMap, - pub(crate) date_filter: HashMap, - pub(crate) select_option_filter: HashMap, - pub(crate) checkbox_filter: HashMap, - pub(crate) checklist_filter: HashMap, -} - -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, -} - -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 - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/controller.rs b/frontend/rust-lib/flowy-grid/src/services/filter/controller.rs index e0a30ab4f3..73a37dd36e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/controller.rs +++ b/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)>>; } +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, - filter_map: FilterMap, result_by_row_id: HashMap, + cell_data_cache: AtomicCellDataCache, + cell_filter_cache: AtomicCellFilterCache, task_scheduler: Arc>, notifier: GridViewChangedNotifier, } @@ -41,6 +48,7 @@ impl FilterController { delegate: T, task_scheduler: Arc>, filter_revs: Vec>, + 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::::new(), task_scheduler, notifier, }; @@ -75,16 +84,17 @@ impl FilterController { } pub async fn filter_row_revs(&mut self, row_revs: &mut Vec>) { - 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, - filter_map: &FilterMap, result_by_row_id: &mut HashMap, field_rev_by_field_id: &HashMap>, + 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, - filter_map: &FilterMap, cell_rev: Option<&CellRevision>, + cell_data_cache: &AtomicCellDataCache, + cell_filter_cache: &AtomicCellFilterCache, ) -> Option { 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::(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::(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::(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::(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::(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::(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::(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::(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)] diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs b/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs index 214bcbb8b7..72bfa3a925 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs +++ b/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::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/task.rs b/frontend/rust-lib/flowy-grid/src/services/filter/task.rs index f43694a267..b903156ad3 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/task.rs +++ b/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, +} + +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 + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index a036168dd3..91a3e2b924 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/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, rev_manager: Arc>>, block_manager: Arc, + cell_data_cache: AtomicCellDataCache, } impl Drop for GridRevisionEditor { @@ -60,6 +64,7 @@ impl GridRevisionEditor { let grid_pad = rev_manager.initialize::(Some(cloud)).await?; let rev_manager = Arc::new(rev_manager); let grid_pad = Arc::new(RwLock::new(grid_pad)); + let cell_data_cache = AnyTypeCache::::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(¶ms.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(¶ms.field_id).await?; + let field_type: FieldType = field_rev.ty.into(); + let cell_rev = self.get_cell_rev(¶ms.row_id, ¶ms.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 { 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(¶ms.field_id).await?; let (_, row_rev) = self.block_manager.get_row_rev(¶ms.row_id).await.ok()??; let cell_rev = row_rev.cells.get(¶ms.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> { @@ -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 { + let sort_rev = self.view_manager.create_or_update_sort(params).await?; + Ok(sort_rev) } pub async fn move_row(&self, params: MoveRowParams) -> FlowyResult<()> { diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller.rs index 4ad1d1d432..76e7ca962e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller.rs +++ b/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 = 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::

()?; 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> { 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::

()?; let mut changesets = self.add_or_remove_row_in_groups_if_match(row_rev, &cell_data); @@ -247,7 +247,7 @@ where ) -> FlowyResult> { // 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::

()?; 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::

()?; Ok(self.move_row(&cell_data, context)) } else { diff --git a/frontend/rust-lib/flowy-grid/src/services/sort/controller.rs b/frontend/rust-lib/flowy-grid/src/services/sort/controller.rs index 25e07b012f..cb97da53eb 100644 --- a/frontend/rust-lib/flowy-grid/src/services/sort/controller.rs +++ b/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>>; + fn get_sort_rev(&self, sort_type: SortType) -> Fut>>; fn get_field_rev(&self, field_id: &str) -> Fut>>; fn get_field_revs(&self, field_ids: Option>) -> Fut>>; } pub struct SortController { - #[allow(dead_code)] - view_id: String, - #[allow(dead_code)] handler_id: String, - #[allow(dead_code)] delegate: Box, task_scheduler: Arc>, - #[allow(dead_code)] - sorts: Vec, - #[allow(dead_code)] - row_orders: HashMap, + sorts: Vec>, + cell_data_cache: AtomicCellDataCache, } impl SortController { - pub fn new(view_id: &str, handler_id: &str, delegate: T, task_scheduler: Arc>) -> Self + pub fn new( + _view_id: &str, + handler_id: &str, + delegate: T, + task_scheduler: Arc>, + 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>) { - // rows.par_sort_by(|left, right| cmp_row(left, right, &self.sorts)); + pub async fn sort_rows(&self, rows: &mut Vec>) { + 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 { + #[tracing::instrument(level = "trace", skip(self))] + pub async fn did_receive_changes(&mut self, changeset: SortChangeset) -> Option { + 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, right: &Arc, - sorts: &[SortRevision], + sorts: &[Arc], field_revs: &[Arc], + 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, + left_cell: &CellRevision, + right_cell: &CellRevision, + field_rev: &Arc, 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::(field_rev.ty)? - // .apply_cmp(&left_cell, &right_cell) - // } - // FieldType::Number => field_rev - // .get_type_option::(field_rev.ty)? - // .apply_cmp(&left_cell, &right_cell), - _ => Ordering::Equal, - }; - Option::::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::::Some(order) + }; - cal_order().unwrap_or(Ordering::Equal) + cal_order().unwrap_or_else(default_order) + } + } } diff --git a/frontend/rust-lib/flowy-grid/src/services/sort/entities.rs b/frontend/rust-lib/flowy-grid/src/services/sort/entities.rs index f64160a9e6..905bc594da 100644 --- a/frontend/rust-lib/flowy-grid/src/services/sort/entities.rs +++ b/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> for SortType { pub struct SortChangeset { pub(crate) insert_sort: Option, pub(crate) update_sort: Option, - pub(crate) delete_sort: Option, + pub(crate) delete_sort: Option, } 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 for DeletedSortType { + fn from(params: DeleteSortParams) -> Self { + Self { + sort_type: params.sort_type, + sort_id: params.sort_id, } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/view_editor/editor.rs b/frontend/rust-lib/flowy-grid/src/services/view_editor/editor.rs index 2768bcd004..11ff83242f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/view_editor/editor.rs +++ b/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, + cell_data_cache: AtomicCellDataCache, mut rev_manager: RevisionManager>, ) -> FlowyResult { 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>) { - 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>) -> Vec> { @@ -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 { let sort_type = SortType::from(¶ms); 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(¶ms.field_id, sort_rev)?; + let changeset = pad.update_sort(¶ms.field_id, sort_rev.clone())?; Ok(changeset) }) .await?; @@ -401,7 +415,7 @@ impl GridViewRevisionEditor { .await } else { self.modify(|pad| { - let changeset = pad.insert_sort(¶ms.field_id, sort_rev)?; + let changeset = pad.insert_sort(¶ms.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(¶ms.sort_id, &sort_type.field_id, sort_type.field_type)?; @@ -710,6 +724,7 @@ async fn make_filter_controller( view_id: &str, delegate: Arc, notifier: GridViewChangedNotifier, + cell_data_cache: AtomicCellDataCache, pad: Arc>, ) -> Arc> { 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, pad: Arc>, + cell_data_cache: AtomicCellDataCache, ) -> Arc> { 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() diff --git a/frontend/rust-lib/flowy-grid/src/services/view_editor/editor_manager.rs b/frontend/rust-lib/flowy-grid/src/services/view_editor/editor_manager.rs index 3160e03bd1..586bcd0feb 100644 --- a/frontend/rust-lib/flowy-grid/src/services/view_editor/editor_manager.rs +++ b/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, delegate: Arc, view_editors: Arc>>>, + cell_data_cache: AtomicCellDataCache, } impl GridViewManager { @@ -33,6 +35,7 @@ impl GridViewManager { grid_id: String, user: Arc, delegate: Arc, + cell_data_cache: AtomicCellDataCache, block_event_rx: broadcast::Receiver, ) -> FlowyResult { 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 { let view_editor = self.get_view_editor(¶ms.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 } } diff --git a/frontend/rust-lib/flowy-grid/src/services/view_editor/trait_impl.rs b/frontend/rust-lib/flowy-grid/src/services/view_editor/trait_impl.rs index 8d90414b15..5764857106 100644 --- a/frontend/rust-lib/flowy-grid/src/services/view_editor/trait_impl.rs +++ b/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>> { + fn get_sort_rev(&self, sort_type: SortType) -> Fut>> { 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() + } }) } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs index 7f3e43275b..82e8d5513f 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs +++ b/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() } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/select_option_filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/select_option_filter_test.rs index 53c7962ece..1c73afa820 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/select_option_filter_test.rs +++ b/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; } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs index 280f6163c0..9ad9d201b2 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs +++ b/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(), }; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/sort_test/mod.rs b/frontend/rust-lib/flowy-grid/tests/grid/sort_test/mod.rs index 4636e892d8..20ed425231 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/sort_test/mod.rs +++ b/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; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/sort_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/sort_test/script.rs index ab866995e5..a60eeb544b 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/sort_test/script.rs +++ b/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 }, + InsertSort { + params: AlterSortParams, + }, + DeleteSort { + params: DeleteSortParams, + }, + AssertTextOrder { + field_id: String, + orders: Vec<&'static str>, + }, } pub struct GridSortTest { inner: GridEditorTest, + pub current_sort_rev: Option, } 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) { 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(¶ms).await; + cells.push(cell); + } + assert_eq!(cells, orders) } } } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/sort_test/sort_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/sort_test/sort_test.rs new file mode 100644 index 0000000000..7097c864ea --- /dev/null +++ b/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; +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/sort_test/text_sort_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/sort_test/text_sort_test.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/shared-lib/grid-rev-model/src/sort_rev.rs b/shared-lib/grid-rev-model/src/sort_rev.rs index b8175846e9..81f6a2f82e 100644 --- a/shared-lib/grid-rev-model/src/sort_rev.rs +++ b/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 for u8 { + fn from(condition: SortCondition) -> Self { + condition as u8 + } +}