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 <nathan@appflowy.io>
This commit is contained in:
Nathan.fooo 2022-12-24 23:19:11 +08:00 committed by GitHub
parent 6a465ac3e7
commit 5a30f46b85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 1628 additions and 849 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -41,7 +41,7 @@ impl TryInto<CreateSelectOptionParams> for CreateSelectOptionPayloadPB {
#[derive(Debug, Clone, Default, ProtoBuf)] #[derive(Debug, Clone, Default, ProtoBuf)]
pub struct CellPathPB { pub struct CellPathPB {
#[pb(index = 1)] #[pb(index = 1)]
pub grid_id: String, pub view_id: String,
#[pb(index = 2)] #[pb(index = 2)]
pub field_id: String, pub field_id: String,
@ -50,6 +50,8 @@ pub struct CellPathPB {
pub row_id: String, 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 struct CellPathParams {
pub view_id: String, pub view_id: String,
pub field_id: String, pub field_id: String,
@ -60,7 +62,7 @@ impl TryInto<CellPathParams> for CellPathPB {
type Error = ErrorCode; type Error = ErrorCode;
fn try_into(self) -> Result<CellPathParams, Self::Error> { fn try_into(self) -> Result<CellPathParams, Self::Error> {
let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?; let grid_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
Ok(CellPathParams { Ok(CellPathParams {
@ -70,15 +72,19 @@ impl TryInto<CellPathParams> for CellPathPB {
}) })
} }
} }
/// Represents as the data of the cell.
#[derive(Debug, Default, ProtoBuf)] #[derive(Debug, Default, ProtoBuf)]
pub struct CellPB { pub struct CellPB {
#[pb(index = 1)] #[pb(index = 1)]
pub field_id: String, 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)] #[pb(index = 2)]
pub data: Vec<u8>, pub data: Vec<u8>,
/// the field_type will be None if the field with field_id is not found
#[pb(index = 3, one_of)] #[pb(index = 3, one_of)]
pub field_type: Option<FieldType>, pub field_type: Option<FieldType>,
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -155,7 +155,7 @@ mod tests {
let encoded_data = type_option.apply_changeset(changeset, None).unwrap(); let encoded_data = type_option.apply_changeset(changeset, None).unwrap();
assert_eq!( 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(), expected_str.to_owned(),
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::entities::FieldType; use crate::entities::FieldType;
use crate::services::cell::{CellDataChangeset, CellDataDecoder}; use crate::services::cell::CellDataChangeset;
use crate::services::field::FieldBuilder; use crate::services::field::FieldBuilder;
use crate::services::field::{URLCellData, URLTypeOptionPB}; use crate::services::field::URLTypeOptionPB;
use grid_rev_model::FieldRevision; 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 /// 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 type_option = URLTypeOptionPB::default();
let field_type = FieldType::URL; let field_type = FieldType::URL;
let field_rev = FieldBuilder::from_field_type(&field_type).build(); let field_rev = FieldBuilder::from_field_type(&field_type).build();
assert_url(&type_option, "123", "123", "", &field_type, &field_rev); assert_url(&type_option, "123", "123", "", &field_rev);
assert_url(&type_option, "", "", "", &field_type, &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 /// 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",
"AppFlowy website - https://www.appflowy.io", "AppFlowy website - https://www.appflowy.io",
"https://www.appflowy.io/", "https://www.appflowy.io/",
&field_type,
&field_rev, &field_rev,
); );
@ -39,7 +38,6 @@ mod tests {
"AppFlowy website appflowy.io", "AppFlowy website appflowy.io",
"AppFlowy website appflowy.io", "AppFlowy website appflowy.io",
"https://appflowy.io", "https://appflowy.io",
&field_type,
&field_rev, &field_rev,
); );
} }
@ -55,7 +53,6 @@ mod tests {
"AppFlowy website - https://www.appflowy.io welcome!", "AppFlowy website - https://www.appflowy.io welcome!",
"AppFlowy website - https://www.appflowy.io welcome!", "AppFlowy website - https://www.appflowy.io welcome!",
"https://www.appflowy.io/", "https://www.appflowy.io/",
&field_type,
&field_rev, &field_rev,
); );
@ -64,7 +61,6 @@ mod tests {
"AppFlowy website appflowy.io welcome!", "AppFlowy website appflowy.io welcome!",
"AppFlowy website appflowy.io welcome!", "AppFlowy website appflowy.io welcome!",
"https://appflowy.io", "https://appflowy.io",
&field_type,
&field_rev, &field_rev,
); );
} }
@ -80,7 +76,6 @@ mod tests {
"AppFlowy website - https://www.appflowy.io!", "AppFlowy website - https://www.appflowy.io!",
"AppFlowy website - https://www.appflowy.io!", "AppFlowy website - https://www.appflowy.io!",
"https://www.appflowy.io/", "https://www.appflowy.io/",
&field_type,
&field_rev, &field_rev,
); );
@ -89,7 +84,6 @@ mod tests {
"AppFlowy website appflowy.io!", "AppFlowy website appflowy.io!",
"AppFlowy website appflowy.io!", "AppFlowy website appflowy.io!",
"https://appflowy.io", "https://appflowy.io",
&field_type,
&field_rev, &field_rev,
); );
} }
@ -105,7 +99,6 @@ mod tests {
"test - https://tester.testgroup.appflowy.io", "test - https://tester.testgroup.appflowy.io",
"test - https://tester.testgroup.appflowy.io", "test - https://tester.testgroup.appflowy.io",
"https://tester.testgroup.appflowy.io/", "https://tester.testgroup.appflowy.io/",
&field_type,
&field_rev, &field_rev,
); );
@ -114,7 +107,6 @@ mod tests {
"test tester.testgroup.appflowy.io", "test tester.testgroup.appflowy.io",
"test tester.testgroup.appflowy.io", "test tester.testgroup.appflowy.io",
"https://tester.testgroup.appflowy.io", "https://tester.testgroup.appflowy.io",
&field_type,
&field_rev, &field_rev,
); );
} }
@ -130,7 +122,6 @@ mod tests {
"appflowy - https://appflowy.com", "appflowy - https://appflowy.com",
"appflowy - https://appflowy.com", "appflowy - https://appflowy.com",
"https://appflowy.com/", "https://appflowy.com/",
&field_type,
&field_rev, &field_rev,
); );
@ -139,7 +130,6 @@ mod tests {
"appflowy - https://appflowy.top", "appflowy - https://appflowy.top",
"appflowy - https://appflowy.top", "appflowy - https://appflowy.top",
"https://appflowy.top/", "https://appflowy.top/",
&field_type,
&field_rev, &field_rev,
); );
@ -148,7 +138,6 @@ mod tests {
"appflowy - https://appflowy.net", "appflowy - https://appflowy.net",
"appflowy - https://appflowy.net", "appflowy - https://appflowy.net",
"https://appflowy.net/", "https://appflowy.net/",
&field_type,
&field_rev, &field_rev,
); );
@ -157,7 +146,6 @@ mod tests {
"appflowy - https://appflowy.edu", "appflowy - https://appflowy.edu",
"appflowy - https://appflowy.edu", "appflowy - https://appflowy.edu",
"https://appflowy.edu/", "https://appflowy.edu/",
&field_type,
&field_rev, &field_rev,
); );
} }
@ -167,23 +155,10 @@ mod tests {
input_str: &str, input_str: &str,
expected_str: &str, expected_str: &str,
expected_url: &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 = 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);
assert_eq!(expected_str.to_owned(), decode_cell_data.content); assert_eq!(expected_str.to_owned(), decode_cell_data.content);
assert_eq!(expected_url.to_owned(), decode_cell_data.url); 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()
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -172,11 +172,18 @@ pub(crate) struct GridViewSortDelegateImpl {
} }
impl SortDelegate for GridViewSortDelegateImpl { impl SortDelegate for GridViewSortDelegateImpl {
fn get_sort_rev(&self, sort_type: SortType) -> Fut<Vec<Arc<SortRevision>>> { fn get_sort_rev(&self, sort_type: SortType) -> Fut<Option<Arc<SortRevision>>> {
let pad = self.view_revision_pad.clone(); let pad = self.view_revision_pad.clone();
to_fut(async move { to_fut(async move {
let field_type_rev: FieldTypeRevision = sort_type.field_type.into(); 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()
}
}) })
} }

View File

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

View File

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

View File

@ -269,7 +269,7 @@ fn make_test_grid() -> BuildGridContext {
FieldType::Number => row_builder.insert_number_cell("2"), FieldType::Number => row_builder.insert_number_cell("2"),
FieldType::DateTime => row_builder.insert_date_cell("1647251762"), FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
FieldType::MultiSelect => row_builder 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"), FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
_ => "".to_owned(), _ => "".to_owned(),
}; };

View File

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

View File

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

View File

@ -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;
}

View File

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