mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
parent
6a465ac3e7
commit
5a30f46b85
@ -63,7 +63,7 @@ class DateCellDataPersistence
|
||||
|
||||
CellPathPB _makeCellPath(GridCellIdentifier cellId) {
|
||||
return CellPathPB.create()
|
||||
..gridId = cellId.gridId
|
||||
..viewId = cellId.gridId
|
||||
..fieldId = cellId.fieldId
|
||||
..rowId = cellId.rowId;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class CellService {
|
||||
required GridCellIdentifier cellId,
|
||||
}) {
|
||||
final payload = CellPathPB.create()
|
||||
..gridId = cellId.gridId
|
||||
..viewId = cellId.gridId
|
||||
..fieldId = cellId.fieldId
|
||||
..rowId = cellId.rowId;
|
||||
return GridEventGetCell(payload).send();
|
||||
|
@ -23,7 +23,7 @@ class SelectOptionFFIService {
|
||||
return result.fold(
|
||||
(option) {
|
||||
final cellIdentifier = CellPathPB.create()
|
||||
..gridId = gridId
|
||||
..viewId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId;
|
||||
final payload = SelectOptionChangesetPB.create()
|
||||
@ -62,7 +62,7 @@ class SelectOptionFFIService {
|
||||
|
||||
Future<Either<SelectOptionCellDataPB, FlowyError>> getOptionContext() {
|
||||
final payload = CellPathPB.create()
|
||||
..gridId = gridId
|
||||
..viewId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId;
|
||||
|
||||
@ -87,7 +87,7 @@ class SelectOptionFFIService {
|
||||
|
||||
CellPathPB _cellIdentifier() {
|
||||
return CellPathPB.create()
|
||||
..gridId = gridId
|
||||
..viewId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId;
|
||||
}
|
||||
|
@ -2706,7 +2706,7 @@ final Map<String, String> flags = Map.fromIterables([
|
||||
'Flag: Armenia',
|
||||
'Flag: Angola',
|
||||
'Flag: Antarctica',
|
||||
'Flag: Argentina',
|
||||
'Flag: argentina',
|
||||
'Flag: American Samoa',
|
||||
'Flag: Austria',
|
||||
'Flag: Australia',
|
||||
@ -2775,7 +2775,7 @@ final Map<String, String> flags = Map.fromIterables([
|
||||
'Flag: Falkland Islands',
|
||||
'Flag: Micronesia',
|
||||
'Flag: Faroe Islands',
|
||||
'Flag: France',
|
||||
'Flag: france',
|
||||
'Flag: Gabon',
|
||||
'Flag: United Kingdom',
|
||||
'Flag: Grenada',
|
||||
|
@ -2706,7 +2706,7 @@ final Map<String, String> flags = Map.fromIterables([
|
||||
'Flag: Armenia',
|
||||
'Flag: Angola',
|
||||
'Flag: Antarctica',
|
||||
'Flag: Argentina',
|
||||
'Flag: argentina',
|
||||
'Flag: American Samoa',
|
||||
'Flag: Austria',
|
||||
'Flag: Australia',
|
||||
@ -2775,7 +2775,7 @@ final Map<String, String> flags = Map.fromIterables([
|
||||
'Flag: Falkland Islands',
|
||||
'Flag: Micronesia',
|
||||
'Flag: Faroe Islands',
|
||||
'Flag: France',
|
||||
'Flag: france',
|
||||
'Flag: Gabon',
|
||||
'Flag: United Kingdom',
|
||||
'Flag: Grenada',
|
||||
|
1
frontend/rust-lib/Cargo.lock
generated
1
frontend/rust-lib/Cargo.lock
generated
@ -1021,6 +1021,7 @@ dependencies = [
|
||||
"lib-infra",
|
||||
"lib-ot",
|
||||
"nanoid",
|
||||
"parking_lot 0.12.1",
|
||||
"protobuf",
|
||||
"rayon",
|
||||
"regex",
|
||||
|
@ -45,6 +45,7 @@ futures = "0.3.15"
|
||||
atomic_refcell = "0.1.8"
|
||||
crossbeam-utils = "0.8.7"
|
||||
async-stream = "0.3.2"
|
||||
parking_lot = "0.12.1"
|
||||
|
||||
[dev-dependencies]
|
||||
flowy-test = { path = "../flowy-test" }
|
||||
|
@ -41,7 +41,7 @@ impl TryInto<CreateSelectOptionParams> for CreateSelectOptionPayloadPB {
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct CellPathPB {
|
||||
#[pb(index = 1)]
|
||||
pub grid_id: String,
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
@ -50,6 +50,8 @@ pub struct CellPathPB {
|
||||
pub row_id: String,
|
||||
}
|
||||
|
||||
/// Represents as the cell identifier. It's used to locate the cell in corresponding
|
||||
/// view's row with the field id.
|
||||
pub struct CellPathParams {
|
||||
pub view_id: String,
|
||||
pub field_id: String,
|
||||
@ -60,7 +62,7 @@ impl TryInto<CellPathParams> for CellPathPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<CellPathParams, Self::Error> {
|
||||
let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
|
||||
let grid_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
|
||||
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
|
||||
let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
||||
Ok(CellPathParams {
|
||||
@ -70,15 +72,19 @@ impl TryInto<CellPathParams> for CellPathPB {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents as the data of the cell.
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct CellPB {
|
||||
#[pb(index = 1)]
|
||||
pub field_id: String,
|
||||
|
||||
// The data was encoded in field_type's data type
|
||||
/// Encoded the data using the helper struct `CellProtobufBlob`.
|
||||
/// Check out the `CellProtobufBlob` for more information.
|
||||
#[pb(index = 2)]
|
||||
pub data: Vec<u8>,
|
||||
|
||||
/// the field_type will be None if the field with field_id is not found
|
||||
#[pb(index = 3, one_of)]
|
||||
pub field_type: Option<FieldType>,
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::services::filter::FromFilterString;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::FilterRevision;
|
||||
@ -39,6 +40,18 @@ impl std::convert::TryFrom<u8> for CheckboxFilterConditionPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromFilterString for CheckboxFilterPB {
|
||||
fn from_filter_rev(filter_rev: &FilterRevision) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
CheckboxFilterPB {
|
||||
condition: CheckboxFilterConditionPB::try_from(filter_rev.condition)
|
||||
.unwrap_or(CheckboxFilterConditionPB::IsChecked),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&FilterRevision> for CheckboxFilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
CheckboxFilterPB {
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::services::filter::FromFilterString;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::FilterRevision;
|
||||
@ -39,6 +40,18 @@ impl std::convert::TryFrom<u8> for ChecklistFilterConditionPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromFilterString for ChecklistFilterPB {
|
||||
fn from_filter_rev(filter_rev: &FilterRevision) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ChecklistFilterPB {
|
||||
condition: ChecklistFilterConditionPB::try_from(filter_rev.condition)
|
||||
.unwrap_or(ChecklistFilterConditionPB::IsIncomplete),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&FilterRevision> for ChecklistFilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
ChecklistFilterPB {
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::services::filter::FromFilterString;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::FilterRevision;
|
||||
@ -80,6 +81,26 @@ impl std::convert::TryFrom<u8> for DateFilterConditionPB {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromFilterString for DateFilterPB {
|
||||
fn from_filter_rev(filter_rev: &FilterRevision) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let condition = DateFilterConditionPB::try_from(filter_rev.condition).unwrap_or(DateFilterConditionPB::DateIs);
|
||||
let mut filter = DateFilterPB {
|
||||
condition,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Ok(content) = DateFilterContentPB::from_str(&filter_rev.content) {
|
||||
filter.start = content.start;
|
||||
filter.end = content.end;
|
||||
filter.timestamp = content.timestamp;
|
||||
};
|
||||
|
||||
filter
|
||||
}
|
||||
}
|
||||
impl std::convert::From<&FilterRevision> for DateFilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
let condition = DateFilterConditionPB::try_from(rev.condition).unwrap_or(DateFilterConditionPB::DateIs);
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::services::filter::FromFilterString;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::FilterRevision;
|
||||
@ -53,6 +54,18 @@ impl std::convert::TryFrom<u8> for NumberFilterConditionPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromFilterString for NumberFilterPB {
|
||||
fn from_filter_rev(filter_rev: &FilterRevision) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
NumberFilterPB {
|
||||
condition: NumberFilterConditionPB::try_from(filter_rev.condition)
|
||||
.unwrap_or(NumberFilterConditionPB::Equal),
|
||||
content: filter_rev.content.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::convert::From<&FilterRevision> for NumberFilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
NumberFilterPB {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::services::field::SelectOptionIds;
|
||||
use crate::services::filter::FromFilterString;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::FilterRevision;
|
||||
@ -46,6 +47,19 @@ impl std::convert::TryFrom<u8> for SelectOptionConditionPB {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromFilterString for SelectOptionFilterPB {
|
||||
fn from_filter_rev(filter_rev: &FilterRevision) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let ids = SelectOptionIds::from(filter_rev.content.clone());
|
||||
SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::try_from(filter_rev.condition)
|
||||
.unwrap_or(SelectOptionConditionPB::OptionIs),
|
||||
option_ids: ids.into_inner(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&FilterRevision> for SelectOptionFilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::services::filter::FromFilterString;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::FilterRevision;
|
||||
@ -54,6 +55,18 @@ impl std::convert::TryFrom<u8> for TextFilterConditionPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromFilterString for TextFilterPB {
|
||||
fn from_filter_rev(filter_rev: &FilterRevision) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
TextFilterPB {
|
||||
condition: TextFilterConditionPB::try_from(filter_rev.condition).unwrap_or(TextFilterConditionPB::Is),
|
||||
content: filter_rev.content.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&FilterRevision> for TextFilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
TextFilterPB {
|
||||
|
@ -130,7 +130,7 @@ impl TryInto<DeleteSortParams> for DeleteSortPayloadPB {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeleteSortParams {
|
||||
pub view_id: String,
|
||||
pub sort_type: SortType,
|
||||
|
@ -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());
|
||||
// }
|
||||
// }
|
@ -1,26 +1,10 @@
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::{CellProtobufBlob, TypeCellData};
|
||||
use crate::services::cell::{AtomicCellDataCache, CellProtobufBlob, TypeCellData};
|
||||
use crate::services::field::*;
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use grid_rev_model::{CellRevision, FieldRevision};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// This trait is used when doing filter/search on the grid.
|
||||
pub trait CellFilterable: TypeOptionConfiguration {
|
||||
/// Return true if type_cell_data match the filter condition.
|
||||
fn apply_filter(
|
||||
&self,
|
||||
type_cell_data: TypeCellData,
|
||||
filter: &<Self as TypeOptionConfiguration>::CellFilterConfiguration,
|
||||
) -> FlowyResult<bool>;
|
||||
}
|
||||
|
||||
pub trait CellComparable {
|
||||
type CellData;
|
||||
fn apply_cmp(&self, cell_data: &Self::CellData, other_cell_data: &Self::CellData) -> Ordering;
|
||||
}
|
||||
|
||||
/// Decode the opaque cell data into readable format content
|
||||
pub trait CellDataDecoder: TypeOption {
|
||||
///
|
||||
@ -57,7 +41,7 @@ pub trait CellDataChangeset: TypeOption {
|
||||
&self,
|
||||
changeset: <Self as TypeOption>::CellChangeset,
|
||||
type_cell_data: Option<TypeCellData>,
|
||||
) -> FlowyResult<String>;
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData>;
|
||||
}
|
||||
|
||||
/// changeset: It will be deserialized into specific data base on the FieldType.
|
||||
@ -70,6 +54,7 @@ pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
|
||||
changeset: C,
|
||||
cell_rev: Option<CellRevision>,
|
||||
field_rev: T,
|
||||
cell_data_cache: Option<AtomicCellDataCache>,
|
||||
) -> Result<String, FlowyError> {
|
||||
let field_rev = field_rev.as_ref();
|
||||
let changeset = changeset.to_string();
|
||||
@ -80,9 +65,11 @@ pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
|
||||
Err(_) => None,
|
||||
});
|
||||
|
||||
let cell_data = match FieldRevisionExt::new(field_rev).get_type_option_cell_data_handler(&field_type) {
|
||||
let cell_data = match TypeOptionCellExt::new_with_cell_data_cache(field_rev, cell_data_cache)
|
||||
.get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
None => "".to_string(),
|
||||
Some(handler) => handler.handle_cell_changeset(changeset, type_cell_data)?,
|
||||
Some(handler) => handler.handle_cell_changeset(changeset, type_cell_data, field_rev)?,
|
||||
};
|
||||
Ok(TypeCellData::new(cell_data, field_type).to_json())
|
||||
}
|
||||
@ -90,12 +77,13 @@ pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
|
||||
pub fn decode_type_cell_data<T: TryInto<TypeCellData, Error = FlowyError> + Debug>(
|
||||
data: T,
|
||||
field_rev: &FieldRevision,
|
||||
cell_data_cache: Option<AtomicCellDataCache>,
|
||||
) -> (FieldType, CellProtobufBlob) {
|
||||
let to_field_type = field_rev.ty.into();
|
||||
match data.try_into() {
|
||||
Ok(type_cell_data) => {
|
||||
let TypeCellData { cell_str, field_type } = type_cell_data;
|
||||
match try_decode_cell_str(cell_str, &field_type, &to_field_type, field_rev) {
|
||||
match try_decode_cell_str(cell_str, &field_type, &to_field_type, field_rev, cell_data_cache) {
|
||||
Ok(cell_bytes) => (field_type, cell_bytes),
|
||||
Err(e) => {
|
||||
tracing::error!("Decode cell data failed, {:?}", e);
|
||||
@ -134,8 +122,11 @@ pub fn try_decode_cell_str(
|
||||
from_field_type: &FieldType,
|
||||
to_field_type: &FieldType,
|
||||
field_rev: &FieldRevision,
|
||||
cell_data_cache: Option<AtomicCellDataCache>,
|
||||
) -> FlowyResult<CellProtobufBlob> {
|
||||
match FieldRevisionExt::new(field_rev).get_type_option_cell_data_handler(to_field_type) {
|
||||
match TypeOptionCellExt::new_with_cell_data_cache(field_rev, cell_data_cache)
|
||||
.get_type_option_cell_data_handler(to_field_type)
|
||||
{
|
||||
None => Ok(CellProtobufBlob::default()),
|
||||
Some(handler) => handler.handle_cell_str(cell_str, from_field_type, field_rev),
|
||||
}
|
||||
@ -146,29 +137,30 @@ pub fn try_decode_cell_str(
|
||||
/// empty string. For example, The string of the Multi-Select cell will be a list of the option's name
|
||||
/// separated by a comma.
|
||||
pub fn stringify_cell_data(
|
||||
cell_data: String,
|
||||
cell_str: String,
|
||||
from_field_type: &FieldType,
|
||||
to_field_type: &FieldType,
|
||||
field_rev: &FieldRevision,
|
||||
) -> String {
|
||||
match FieldRevisionExt::new(field_rev).get_type_option_cell_data_handler(to_field_type) {
|
||||
match TypeOptionCellExt::new_with_cell_data_cache(field_rev, None).get_type_option_cell_data_handler(to_field_type)
|
||||
{
|
||||
None => "".to_string(),
|
||||
Some(handler) => handler.stringify_cell_str(cell_data, from_field_type, field_rev),
|
||||
Some(handler) => handler.stringify_cell_str(cell_str, from_field_type, field_rev),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_text_cell(s: String, field_rev: &FieldRevision) -> CellRevision {
|
||||
let data = apply_cell_data_changeset(s, None, field_rev).unwrap();
|
||||
let data = apply_cell_data_changeset(s, None, field_rev, None).unwrap();
|
||||
CellRevision::new(data)
|
||||
}
|
||||
|
||||
pub fn insert_number_cell(num: i64, field_rev: &FieldRevision) -> CellRevision {
|
||||
let data = apply_cell_data_changeset(num, None, field_rev).unwrap();
|
||||
let data = apply_cell_data_changeset(num, None, field_rev, None).unwrap();
|
||||
CellRevision::new(data)
|
||||
}
|
||||
|
||||
pub fn insert_url_cell(url: String, field_rev: &FieldRevision) -> CellRevision {
|
||||
let data = apply_cell_data_changeset(url, None, field_rev).unwrap();
|
||||
let data = apply_cell_data_changeset(url, None, field_rev, None).unwrap();
|
||||
CellRevision::new(data)
|
||||
}
|
||||
|
||||
@ -178,7 +170,7 @@ pub fn insert_checkbox_cell(is_check: bool, field_rev: &FieldRevision) -> CellRe
|
||||
} else {
|
||||
UNCHECK.to_string()
|
||||
};
|
||||
let data = apply_cell_data_changeset(s, None, field_rev).unwrap();
|
||||
let data = apply_cell_data_changeset(s, None, field_rev, None).unwrap();
|
||||
CellRevision::new(data)
|
||||
}
|
||||
|
||||
@ -189,19 +181,19 @@ pub fn insert_date_cell(timestamp: i64, field_rev: &FieldRevision) -> CellRevisi
|
||||
is_utc: true,
|
||||
})
|
||||
.unwrap();
|
||||
let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap();
|
||||
let data = apply_cell_data_changeset(cell_data, None, field_rev, None).unwrap();
|
||||
CellRevision::new(data)
|
||||
}
|
||||
|
||||
pub fn insert_select_option_cell(option_ids: Vec<String>, field_rev: &FieldRevision) -> CellRevision {
|
||||
let cell_data = SelectOptionCellChangeset::from_insert_options(option_ids).to_str();
|
||||
let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap();
|
||||
let data = apply_cell_data_changeset(cell_data, None, field_rev, None).unwrap();
|
||||
CellRevision::new(data)
|
||||
}
|
||||
|
||||
pub fn delete_select_option_cell(option_ids: Vec<String>, field_rev: &FieldRevision) -> CellRevision {
|
||||
let cell_data = SelectOptionCellChangeset::from_delete_options(option_ids).to_str();
|
||||
let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap();
|
||||
let data = apply_cell_data_changeset(cell_data, None, field_rev, None).unwrap();
|
||||
CellRevision::new(data)
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
mod cell_data_cache;
|
||||
mod cell_operation;
|
||||
mod type_cell_data;
|
||||
|
||||
pub use cell_data_cache::*;
|
||||
pub use cell_operation::*;
|
||||
pub use type_cell_data::*;
|
||||
|
@ -1,7 +1,5 @@
|
||||
use crate::entities::{CheckboxFilterConditionPB, CheckboxFilterPB};
|
||||
use crate::services::cell::{CellFilterable, TypeCellData};
|
||||
use crate::services::field::{CheckboxCellData, CheckboxTypeOptionPB, TypeOptionCellData, TypeOptionConfiguration};
|
||||
use flowy_error::FlowyResult;
|
||||
use crate::services::field::CheckboxCellData;
|
||||
|
||||
impl CheckboxFilterPB {
|
||||
pub fn is_visible(&self, cell_data: &CheckboxCellData) -> bool {
|
||||
@ -13,20 +11,6 @@ impl CheckboxFilterPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl CellFilterable for CheckboxTypeOptionPB {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
type_cell_data: TypeCellData,
|
||||
filter: &<Self as TypeOptionConfiguration>::CellFilterConfiguration,
|
||||
) -> FlowyResult<bool> {
|
||||
if !type_cell_data.is_checkbox() {
|
||||
return Ok(true);
|
||||
}
|
||||
let checkbox_cell_data = self.decode_type_option_cell_str(type_cell_data.cell_str)?;
|
||||
Ok(filter.is_visible(&checkbox_cell_data))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::entities::{CheckboxFilterConditionPB, CheckboxFilterPB};
|
||||
|
@ -2,14 +2,15 @@ use crate::entities::{CheckboxFilterPB, FieldType};
|
||||
use crate::impl_type_option;
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder, FromCellString, TypeCellData};
|
||||
use crate::services::field::{
|
||||
BoxTypeOptionBuilder, CheckboxCellData, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
|
||||
TypeOptionTransform,
|
||||
default_order, BoxTypeOptionBuilder, CheckboxCellData, TypeOption, TypeOptionBuilder, TypeOptionCellData,
|
||||
TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::FlowyResult;
|
||||
use grid_rev_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Default)]
|
||||
@ -45,14 +46,11 @@ impl TypeOption for CheckboxTypeOptionPB {
|
||||
type CellData = CheckboxCellData;
|
||||
type CellChangeset = CheckboxCellChangeset;
|
||||
type CellProtobufType = CheckboxCellData;
|
||||
type CellFilter = CheckboxFilterPB;
|
||||
}
|
||||
|
||||
impl TypeOptionTransform for CheckboxTypeOptionPB {}
|
||||
|
||||
impl TypeOptionConfiguration for CheckboxTypeOptionPB {
|
||||
type CellFilterConfiguration = CheckboxFilterPB;
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for CheckboxTypeOptionPB {
|
||||
fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType {
|
||||
cell_data
|
||||
@ -89,8 +87,37 @@ impl CellDataChangeset for CheckboxTypeOptionPB {
|
||||
&self,
|
||||
changeset: <Self as TypeOption>::CellChangeset,
|
||||
_type_cell_data: Option<TypeCellData>,
|
||||
) -> FlowyResult<String> {
|
||||
let cell_data = CheckboxCellData::from_str(&changeset)?;
|
||||
Ok(cell_data.to_string())
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
let checkbox_cell_data = CheckboxCellData::from_str(&changeset)?;
|
||||
Ok(checkbox_cell_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataFilter for CheckboxTypeOptionPB {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
filter: &<Self as TypeOption>::CellFilter,
|
||||
field_type: &FieldType,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> bool {
|
||||
if !field_type.is_checkbox() {
|
||||
return true;
|
||||
}
|
||||
filter.is_visible(cell_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataCompare for CheckboxTypeOptionPB {
|
||||
fn apply_cmp(
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> Ordering {
|
||||
match (cell_data.is_check(), other_cell_data.is_check()) {
|
||||
(true, true) => Ordering::Equal,
|
||||
(true, false) => Ordering::Greater,
|
||||
(false, true) => Ordering::Less,
|
||||
(false, false) => default_order(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use std::str::FromStr;
|
||||
pub const CHECK: &str = "Yes";
|
||||
pub const UNCHECK: &str = "No";
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct CheckboxCellData(String);
|
||||
|
||||
impl CheckboxCellData {
|
||||
|
@ -1,9 +1,6 @@
|
||||
use crate::entities::{DateFilterConditionPB, DateFilterPB};
|
||||
use crate::services::cell::{CellFilterable, TypeCellData};
|
||||
use crate::services::field::{DateTypeOptionPB, TypeOptionCellData, TypeOptionConfiguration};
|
||||
use chrono::NaiveDateTime;
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
use chrono::NaiveDateTime;
|
||||
|
||||
impl DateFilterPB {
|
||||
pub fn is_visible<T: Into<Option<i64>>>(&self, cell_timestamp: T) -> bool {
|
||||
@ -60,21 +57,6 @@ impl DateFilterPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl CellFilterable for DateTypeOptionPB {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
type_cell_data: TypeCellData,
|
||||
filter: &<Self as TypeOptionConfiguration>::CellFilterConfiguration,
|
||||
) -> FlowyResult<bool> {
|
||||
if !type_cell_data.is_date() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let date_cell_data = self.decode_type_option_cell_str(type_cell_data.cell_str)?;
|
||||
Ok(filter.is_visible(date_cell_data))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::all)]
|
||||
|
@ -155,7 +155,7 @@ mod tests {
|
||||
let encoded_data = type_option.apply_changeset(changeset, None).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
decode_cell_data(encoded_data, type_option, field_rev),
|
||||
decode_cell_data(encoded_data.to_string(), type_option, field_rev),
|
||||
expected_str.to_owned(),
|
||||
);
|
||||
}
|
||||
|
@ -2,8 +2,9 @@ use crate::entities::{DateFilterPB, FieldType};
|
||||
use crate::impl_type_option;
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder, FromCellString, TypeCellData};
|
||||
use crate::services::field::{
|
||||
BoxTypeOptionBuilder, DateCellChangeset, DateCellData, DateCellDataPB, DateFormat, TimeFormat, TypeOption,
|
||||
TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration, TypeOptionTransform,
|
||||
default_order, BoxTypeOptionBuilder, DateCellChangeset, DateCellData, DateCellDataPB, DateFormat, TimeFormat,
|
||||
TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
TypeOptionTransform,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use chrono::format::strftime::StrftimeItems;
|
||||
@ -12,6 +13,7 @@ use flowy_derive::ProtoBuf;
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use grid_rev_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
// Date
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
|
||||
@ -31,10 +33,7 @@ impl TypeOption for DateTypeOptionPB {
|
||||
type CellData = DateCellData;
|
||||
type CellChangeset = DateCellChangeset;
|
||||
type CellProtobufType = DateCellDataPB;
|
||||
}
|
||||
|
||||
impl TypeOptionConfiguration for DateTypeOptionPB {
|
||||
type CellFilterConfiguration = DateFilterPB;
|
||||
type CellFilter = DateFilterPB;
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for DateTypeOptionPB {
|
||||
@ -158,7 +157,7 @@ impl CellDataChangeset for DateTypeOptionPB {
|
||||
&self,
|
||||
changeset: <Self as TypeOption>::CellChangeset,
|
||||
_type_cell_data: Option<TypeCellData>,
|
||||
) -> FlowyResult<String> {
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
let cell_data = match changeset.date_timestamp() {
|
||||
None => 0,
|
||||
Some(date_timestamp) => match (self.include_time, changeset.time) {
|
||||
@ -173,7 +172,37 @@ impl CellDataChangeset for DateTypeOptionPB {
|
||||
},
|
||||
};
|
||||
|
||||
Ok(cell_data.to_string())
|
||||
Ok(DateCellData(Some(cell_data)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataFilter for DateTypeOptionPB {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
filter: &<Self as TypeOption>::CellFilter,
|
||||
field_type: &FieldType,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> bool {
|
||||
if !field_type.is_date() {
|
||||
return true;
|
||||
}
|
||||
|
||||
filter.is_visible(cell_data.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataCompare for DateTypeOptionPB {
|
||||
fn apply_cmp(
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> Ordering {
|
||||
match (cell_data.0, other_cell_data.0) {
|
||||
(Some(left), Some(right)) => left.cmp(&right),
|
||||
(Some(_), None) => Ordering::Greater,
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
(None, None) => default_order(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ impl ToString for DateCellChangeset {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct DateCellData(pub Option<i64>);
|
||||
|
||||
impl std::convert::From<DateCellData> for i64 {
|
||||
@ -93,6 +93,15 @@ impl FromCellString for DateCellData {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for DateCellData {
|
||||
fn to_string(&self) -> String {
|
||||
match self.0 {
|
||||
None => "".to_string(),
|
||||
Some(val) => val.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
|
||||
pub enum DateFormat {
|
||||
Local = 0,
|
||||
|
@ -4,6 +4,7 @@ pub mod number_type_option;
|
||||
pub mod selection_type_option;
|
||||
pub mod text_type_option;
|
||||
mod type_option;
|
||||
mod type_option_cell;
|
||||
pub mod url_type_option;
|
||||
|
||||
pub use checkbox_type_option::*;
|
||||
@ -12,4 +13,5 @@ pub use number_type_option::*;
|
||||
pub use selection_type_option::*;
|
||||
pub use text_type_option::*;
|
||||
pub use type_option::*;
|
||||
pub use type_option_cell::*;
|
||||
pub use url_type_option::*;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::entities::{NumberFilterConditionPB, NumberFilterPB};
|
||||
use crate::services::cell::{CellFilterable, TypeCellData};
|
||||
use crate::services::field::{NumberCellData, NumberTypeOptionPB, TypeOptionConfiguration};
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
use crate::services::field::NumberCellData;
|
||||
|
||||
use rust_decimal::prelude::Zero;
|
||||
use rust_decimal::Decimal;
|
||||
use std::str::FromStr;
|
||||
@ -37,23 +37,6 @@ impl NumberFilterPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl CellFilterable for NumberTypeOptionPB {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
type_cell_data: TypeCellData,
|
||||
filter: &<Self as TypeOptionConfiguration>::CellFilterConfiguration,
|
||||
) -> FlowyResult<bool> {
|
||||
if !type_cell_data.is_number() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let cell_data = type_cell_data.cell_str;
|
||||
let num_cell_data = self.format_cell_data(&cell_data)?;
|
||||
|
||||
Ok(filter.is_visible(&num_cell_data))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::entities::{NumberFilterConditionPB, NumberFilterPB};
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::entities::{FieldType, NumberFilterPB};
|
||||
use crate::impl_type_option;
|
||||
use crate::services::cell::{CellComparable, CellDataChangeset, CellDataDecoder, TypeCellData};
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder, TypeCellData};
|
||||
use crate::services::field::type_options::number_type_option::format::*;
|
||||
use crate::services::field::{
|
||||
BoxTypeOptionBuilder, NumberCellData, StrCellData, TypeOption, TypeOptionBuilder, TypeOptionCellData,
|
||||
TypeOptionConfiguration, TypeOptionTransform,
|
||||
TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
@ -77,10 +77,7 @@ impl TypeOption for NumberTypeOptionPB {
|
||||
type CellData = StrCellData;
|
||||
type CellChangeset = NumberCellChangeset;
|
||||
type CellProtobufType = StrCellData;
|
||||
}
|
||||
|
||||
impl TypeOptionConfiguration for NumberTypeOptionPB {
|
||||
type CellFilterConfiguration = NumberFilterPB;
|
||||
type CellFilter = NumberFilterPB;
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for NumberTypeOptionPB {
|
||||
@ -158,20 +155,39 @@ impl CellDataChangeset for NumberTypeOptionPB {
|
||||
&self,
|
||||
changeset: <Self as TypeOption>::CellChangeset,
|
||||
_type_cell_data: Option<TypeCellData>,
|
||||
) -> FlowyResult<String> {
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
let data = changeset.trim().to_string();
|
||||
let _ = self.format_cell_data(&data)?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
impl CellComparable for NumberTypeOptionPB {
|
||||
type CellData = NumberCellData;
|
||||
|
||||
fn apply_cmp(&self, _cell_data: &Self::CellData, _other_cell_data: &Self::CellData) -> Ordering {
|
||||
Ordering::Equal
|
||||
Ok(StrCellData(data))
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataFilter for NumberTypeOptionPB {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
filter: &<Self as TypeOption>::CellFilter,
|
||||
field_type: &FieldType,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> bool {
|
||||
if !field_type.is_number() {
|
||||
return true;
|
||||
}
|
||||
match self.format_cell_data(cell_data) {
|
||||
Ok(cell_data) => filter.is_visible(&cell_data),
|
||||
Err(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataCompare for NumberTypeOptionPB {
|
||||
fn apply_cmp(
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> Ordering {
|
||||
cell_data.0.cmp(&other_cell_data.0)
|
||||
}
|
||||
}
|
||||
impl std::default::Default for NumberTypeOptionPB {
|
||||
fn default() -> Self {
|
||||
let format = NumberFormat::default();
|
||||
|
@ -1,16 +1,17 @@
|
||||
use crate::entities::{ChecklistFilterPB, FieldType};
|
||||
use crate::impl_type_option;
|
||||
use crate::services::cell::{CellDataChangeset, FromCellString, TypeCellData};
|
||||
|
||||
use crate::services::field::{
|
||||
BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, SelectOptionPB,
|
||||
SelectTypeOptionSharedAction, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
|
||||
SelectTypeOptionSharedAction, SelectedSelectOptions, TypeOption, TypeOptionBuilder, TypeOptionCellData,
|
||||
TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::FlowyResult;
|
||||
use grid_rev_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
// Multiple select
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
|
||||
@ -27,10 +28,7 @@ impl TypeOption for ChecklistTypeOptionPB {
|
||||
type CellData = SelectOptionIds;
|
||||
type CellChangeset = SelectOptionCellChangeset;
|
||||
type CellProtobufType = SelectOptionCellDataPB;
|
||||
}
|
||||
|
||||
impl TypeOptionConfiguration for ChecklistTypeOptionPB {
|
||||
type CellFilterConfiguration = ChecklistFilterPB;
|
||||
type CellFilter = ChecklistFilterPB;
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for ChecklistTypeOptionPB {
|
||||
@ -62,7 +60,7 @@ impl CellDataChangeset for ChecklistTypeOptionPB {
|
||||
&self,
|
||||
changeset: <Self as TypeOption>::CellChangeset,
|
||||
type_cell_data: Option<TypeCellData>,
|
||||
) -> FlowyResult<String> {
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
let insert_option_ids = changeset
|
||||
.insert_option_ids
|
||||
.into_iter()
|
||||
@ -70,7 +68,7 @@ impl CellDataChangeset for ChecklistTypeOptionPB {
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
match type_cell_data {
|
||||
None => Ok(SelectOptionIds::from(insert_option_ids).to_string()),
|
||||
None => Ok(SelectOptionIds::from(insert_option_ids)),
|
||||
Some(type_cell_data) => {
|
||||
let mut select_ids: SelectOptionIds = type_cell_data.cell_str.into();
|
||||
for insert_option_id in insert_option_ids {
|
||||
@ -83,11 +81,35 @@ impl CellDataChangeset for ChecklistTypeOptionPB {
|
||||
select_ids.retain(|id| id != &delete_option_id);
|
||||
}
|
||||
|
||||
Ok(select_ids.to_string())
|
||||
Ok(select_ids)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TypeOptionCellDataFilter for ChecklistTypeOptionPB {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
filter: &<Self as TypeOption>::CellFilter,
|
||||
field_type: &FieldType,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> bool {
|
||||
if !field_type.is_check_list() {
|
||||
return true;
|
||||
}
|
||||
let selected_options = SelectedSelectOptions::from(self.get_selected_options(cell_data.clone()));
|
||||
filter.is_visible(&self.options, &selected_options)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataCompare for ChecklistTypeOptionPB {
|
||||
fn apply_cmp(
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> Ordering {
|
||||
cell_data.len().cmp(&other_cell_data.len())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ChecklistTypeOptionBuilder(ChecklistTypeOptionPB);
|
||||
|
@ -1,10 +1,12 @@
|
||||
use crate::entities::{FieldType, SelectOptionFilterPB};
|
||||
use crate::impl_type_option;
|
||||
use crate::services::cell::{CellDataChangeset, FromCellString, TypeCellData};
|
||||
use std::cmp::{min, Ordering};
|
||||
|
||||
use crate::services::field::{
|
||||
BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, SelectOptionPB,
|
||||
SelectTypeOptionSharedAction, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
|
||||
default_order, BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds,
|
||||
SelectOptionPB, SelectTypeOptionSharedAction, SelectedSelectOptions, TypeOption, TypeOptionBuilder,
|
||||
TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
@ -27,10 +29,7 @@ impl TypeOption for MultiSelectTypeOptionPB {
|
||||
type CellData = SelectOptionIds;
|
||||
type CellChangeset = SelectOptionCellChangeset;
|
||||
type CellProtobufType = SelectOptionCellDataPB;
|
||||
}
|
||||
|
||||
impl TypeOptionConfiguration for MultiSelectTypeOptionPB {
|
||||
type CellFilterConfiguration = SelectOptionFilterPB;
|
||||
type CellFilter = SelectOptionFilterPB;
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for MultiSelectTypeOptionPB {
|
||||
@ -62,18 +61,15 @@ impl CellDataChangeset for MultiSelectTypeOptionPB {
|
||||
&self,
|
||||
changeset: <Self as TypeOption>::CellChangeset,
|
||||
type_cell_data: Option<TypeCellData>,
|
||||
) -> FlowyResult<String> {
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
let insert_option_ids = changeset
|
||||
.insert_option_ids
|
||||
.into_iter()
|
||||
.filter(|insert_option_id| self.options.iter().any(|option| &option.id == insert_option_id))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let new_cell_data: String;
|
||||
match type_cell_data {
|
||||
None => {
|
||||
new_cell_data = SelectOptionIds::from(insert_option_ids).to_string();
|
||||
}
|
||||
None => Ok(SelectOptionIds::from(insert_option_ids)),
|
||||
Some(type_cell_data) => {
|
||||
let mut select_ids: SelectOptionIds = type_cell_data.cell_str.into();
|
||||
for insert_option_id in insert_option_ids {
|
||||
@ -86,15 +82,56 @@ impl CellDataChangeset for MultiSelectTypeOptionPB {
|
||||
select_ids.retain(|id| id != &delete_option_id);
|
||||
}
|
||||
|
||||
new_cell_data = select_ids.to_string();
|
||||
tracing::trace!("Multi-select cell data: {}", &new_cell_data);
|
||||
tracing::trace!("Multi-select cell data: {}", select_ids.to_string());
|
||||
Ok(select_ids)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(new_cell_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataFilter for MultiSelectTypeOptionPB {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
filter: &<Self as TypeOption>::CellFilter,
|
||||
field_type: &FieldType,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> bool {
|
||||
if !field_type.is_multi_select() {
|
||||
return true;
|
||||
}
|
||||
let selected_options = SelectedSelectOptions::from(self.get_selected_options(cell_data.clone()));
|
||||
filter.is_visible(&selected_options, FieldType::MultiSelect)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataCompare for MultiSelectTypeOptionPB {
|
||||
fn apply_cmp(
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> Ordering {
|
||||
for i in 0..min(cell_data.len(), other_cell_data.len()) {
|
||||
let order = match (
|
||||
cell_data
|
||||
.get(i)
|
||||
.and_then(|id| self.options.iter().find(|option| &option.id == id)),
|
||||
other_cell_data
|
||||
.get(i)
|
||||
.and_then(|id| self.options.iter().find(|option| &option.id == id)),
|
||||
) {
|
||||
(Some(left), Some(right)) => left.name.cmp(&right.name),
|
||||
(Some(_), None) => Ordering::Greater,
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
(None, None) => default_order(),
|
||||
};
|
||||
|
||||
if order.is_ne() {
|
||||
return order;
|
||||
}
|
||||
}
|
||||
default_order()
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct MultiSelectTypeOptionBuilder(MultiSelectTypeOptionPB);
|
||||
impl_into_box_type_option_builder!(MultiSelectTypeOptionBuilder);
|
||||
@ -173,7 +210,7 @@ mod tests {
|
||||
let type_option = MultiSelectTypeOptionPB::from(&field_rev);
|
||||
let option_ids = vec![google.id, facebook.id];
|
||||
let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone());
|
||||
let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap().into();
|
||||
let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap();
|
||||
|
||||
assert_eq!(&*select_option_ids, &option_ids);
|
||||
}
|
||||
@ -192,12 +229,12 @@ mod tests {
|
||||
|
||||
// insert
|
||||
let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone());
|
||||
let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap().into();
|
||||
let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert_eq!(&*select_option_ids, &option_ids);
|
||||
|
||||
// delete
|
||||
let changeset = SelectOptionCellChangeset::from_delete_options(option_ids);
|
||||
let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap().into();
|
||||
let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert!(select_option_ids.is_empty());
|
||||
}
|
||||
|
||||
@ -213,8 +250,8 @@ mod tests {
|
||||
|
||||
let type_option = MultiSelectTypeOptionPB::from(&field_rev);
|
||||
let changeset = SelectOptionCellChangeset::from_insert_option_id(&google.id);
|
||||
let cell_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert_eq!(cell_option_ids, google.id);
|
||||
let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert_eq!(select_option_ids.to_string(), google.id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -228,8 +265,8 @@ mod tests {
|
||||
|
||||
let type_option = MultiSelectTypeOptionPB::from(&field_rev);
|
||||
let changeset = SelectOptionCellChangeset::from_insert_option_id(&google.id);
|
||||
let cell_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert!(cell_option_ids.is_empty());
|
||||
let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert!(select_option_ids.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -246,11 +283,11 @@ mod tests {
|
||||
|
||||
// empty option id string
|
||||
let changeset = SelectOptionCellChangeset::from_insert_option_id("");
|
||||
let cell_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert_eq!(cell_option_ids, "");
|
||||
let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert_eq!(select_option_ids.to_string(), "");
|
||||
|
||||
let changeset = SelectOptionCellChangeset::from_insert_option_id("123,456");
|
||||
let cell_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert_eq!(cell_option_ids, "");
|
||||
let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert!(select_option_ids.is_empty());
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,7 @@
|
||||
#![allow(clippy::needless_collect)]
|
||||
|
||||
use crate::entities::{ChecklistFilterPB, FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
|
||||
use crate::services::cell::{CellFilterable, TypeCellData};
|
||||
use crate::services::field::{
|
||||
ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB, TypeOptionCellData,
|
||||
};
|
||||
use crate::services::field::{SelectTypeOptionSharedAction, SelectedSelectOptions};
|
||||
use flowy_error::FlowyResult;
|
||||
use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
|
||||
use crate::services::field::SelectedSelectOptions;
|
||||
|
||||
impl SelectOptionFilterPB {
|
||||
pub fn is_visible(&self, selected_options: &SelectedSelectOptions, field_type: FieldType) -> bool {
|
||||
@ -80,39 +75,6 @@ impl SelectOptionFilterPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl CellFilterable for MultiSelectTypeOptionPB {
|
||||
fn apply_filter(&self, type_cell_data: TypeCellData, filter: &SelectOptionFilterPB) -> FlowyResult<bool> {
|
||||
if !type_cell_data.is_multi_select() {
|
||||
return Ok(true);
|
||||
}
|
||||
let ids = self.decode_type_option_cell_str(type_cell_data.cell_str)?;
|
||||
let selected_options = SelectedSelectOptions::from(self.get_selected_options(ids));
|
||||
Ok(filter.is_visible(&selected_options, FieldType::MultiSelect))
|
||||
}
|
||||
}
|
||||
|
||||
impl CellFilterable for SingleSelectTypeOptionPB {
|
||||
fn apply_filter(&self, type_cell_data: TypeCellData, filter: &SelectOptionFilterPB) -> FlowyResult<bool> {
|
||||
if !type_cell_data.is_single_select() {
|
||||
return Ok(true);
|
||||
}
|
||||
let ids = self.decode_type_option_cell_str(type_cell_data.cell_str)?;
|
||||
let selected_options = SelectedSelectOptions::from(self.get_selected_options(ids));
|
||||
Ok(filter.is_visible(&selected_options, FieldType::SingleSelect))
|
||||
}
|
||||
}
|
||||
|
||||
impl CellFilterable for ChecklistTypeOptionPB {
|
||||
fn apply_filter(&self, type_cell_data: TypeCellData, filter: &ChecklistFilterPB) -> FlowyResult<bool> {
|
||||
if !type_cell_data.is_checklist() {
|
||||
return Ok(true);
|
||||
}
|
||||
let ids = self.decode_type_option_cell_str(type_cell_data.cell_str)?;
|
||||
let selected_options = SelectedSelectOptions::from(self.get_selected_options(ids));
|
||||
Ok(filter.is_visible(&self.options, &selected_options))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::all)]
|
||||
|
@ -252,7 +252,7 @@ pub fn new_select_option_color(options: &Vec<SelectOptionPB>) -> SelectOptionCol
|
||||
/// Calls [to_string] will return a string consists list of ids,
|
||||
/// placing a commas separator between each
|
||||
///
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct SelectOptionIds(Vec<String>);
|
||||
|
||||
impl SelectOptionIds {
|
||||
|
@ -1,10 +1,11 @@
|
||||
use crate::entities::{FieldType, SelectOptionFilterPB};
|
||||
use crate::impl_type_option;
|
||||
use crate::services::cell::{CellDataChangeset, FromCellString, TypeCellData};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::services::field::{
|
||||
BoxTypeOptionBuilder, SelectOptionCellDataPB, TypeOption, TypeOptionBuilder, TypeOptionCellData,
|
||||
TypeOptionConfiguration,
|
||||
default_order, BoxTypeOptionBuilder, SelectOptionCellDataPB, SelectedSelectOptions, TypeOption, TypeOptionBuilder,
|
||||
TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
};
|
||||
use crate::services::field::{
|
||||
SelectOptionCellChangeset, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction,
|
||||
@ -30,10 +31,7 @@ impl TypeOption for SingleSelectTypeOptionPB {
|
||||
type CellData = SelectOptionIds;
|
||||
type CellChangeset = SelectOptionCellChangeset;
|
||||
type CellProtobufType = SelectOptionCellDataPB;
|
||||
}
|
||||
|
||||
impl TypeOptionConfiguration for SingleSelectTypeOptionPB {
|
||||
type CellFilterConfiguration = SelectOptionFilterPB;
|
||||
type CellFilter = SelectOptionFilterPB;
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for SingleSelectTypeOptionPB {
|
||||
@ -65,7 +63,7 @@ impl CellDataChangeset for SingleSelectTypeOptionPB {
|
||||
&self,
|
||||
changeset: <Self as TypeOption>::CellChangeset,
|
||||
_type_cell_data: Option<TypeCellData>,
|
||||
) -> FlowyResult<String> {
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
let mut insert_option_ids = changeset
|
||||
.insert_option_ids
|
||||
.into_iter()
|
||||
@ -76,15 +74,51 @@ impl CellDataChangeset for SingleSelectTypeOptionPB {
|
||||
// Sometimes, the insert_option_ids may contain list of option ids. For example,
|
||||
// copy/paste a ids string.
|
||||
if insert_option_ids.is_empty() {
|
||||
Ok("".to_string())
|
||||
Ok(SelectOptionIds::from(insert_option_ids))
|
||||
} else {
|
||||
// Just take the first select option
|
||||
let _ = insert_option_ids.drain(1..);
|
||||
Ok(insert_option_ids.pop().unwrap())
|
||||
Ok(SelectOptionIds::from(insert_option_ids))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataFilter for SingleSelectTypeOptionPB {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
filter: &<Self as TypeOption>::CellFilter,
|
||||
field_type: &FieldType,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> bool {
|
||||
if !field_type.is_single_select() {
|
||||
return true;
|
||||
}
|
||||
let selected_options = SelectedSelectOptions::from(self.get_selected_options(cell_data.clone()));
|
||||
filter.is_visible(&selected_options, FieldType::SingleSelect)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataCompare for SingleSelectTypeOptionPB {
|
||||
fn apply_cmp(
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> Ordering {
|
||||
match (
|
||||
cell_data
|
||||
.first()
|
||||
.and_then(|id| self.options.iter().find(|option| &option.id == id)),
|
||||
other_cell_data
|
||||
.first()
|
||||
.and_then(|id| self.options.iter().find(|option| &option.id == id)),
|
||||
) {
|
||||
(Some(left), Some(right)) => left.name.cmp(&right.name),
|
||||
(Some(_), None) => Ordering::Greater,
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
(None, None) => default_order(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct SingleSelectTypeOptionBuilder(SingleSelectTypeOptionPB);
|
||||
impl_into_box_type_option_builder!(SingleSelectTypeOptionBuilder);
|
||||
@ -161,8 +195,7 @@ mod tests {
|
||||
let type_option = SingleSelectTypeOptionPB::from(&field_rev);
|
||||
let option_ids = vec![google.id.clone(), facebook.id];
|
||||
let changeset = SelectOptionCellChangeset::from_insert_options(option_ids);
|
||||
let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap().into();
|
||||
|
||||
let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert_eq!(&*select_option_ids, &vec![google.id]);
|
||||
}
|
||||
|
||||
@ -180,12 +213,12 @@ mod tests {
|
||||
|
||||
// insert
|
||||
let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone());
|
||||
let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap().into();
|
||||
let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert_eq!(&*select_option_ids, &vec![google.id]);
|
||||
|
||||
// delete
|
||||
let changeset = SelectOptionCellChangeset::from_delete_options(option_ids);
|
||||
let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap().into();
|
||||
let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert!(select_option_ids.is_empty());
|
||||
}
|
||||
|
||||
@ -198,9 +231,9 @@ mod tests {
|
||||
|
||||
let option_ids = vec![google.id];
|
||||
let changeset = SelectOptionCellChangeset::from_insert_options(option_ids);
|
||||
let cell_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
|
||||
assert!(cell_option_ids.is_empty());
|
||||
assert!(select_option_ids.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -210,7 +243,7 @@ mod tests {
|
||||
let type_option = SingleSelectTypeOptionPB::from(&field_rev);
|
||||
|
||||
let changeset = SelectOptionCellChangeset::from_insert_option_id("");
|
||||
let cell_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert_eq!(cell_option_ids, "");
|
||||
let select_option_ids = type_option.apply_changeset(changeset, None).unwrap();
|
||||
assert!(select_option_ids.is_empty());
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,4 @@
|
||||
use crate::entities::{TextFilterConditionPB, TextFilterPB};
|
||||
use crate::services::cell::{CellFilterable, TypeCellData};
|
||||
use crate::services::field::{RichTextTypeOptionPB, TypeOptionCellData, TypeOptionConfiguration};
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
impl TextFilterPB {
|
||||
pub fn is_visible<T: AsRef<str>>(&self, cell_data: T) -> bool {
|
||||
@ -20,21 +17,6 @@ impl TextFilterPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl CellFilterable for RichTextTypeOptionPB {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
type_cell_data: TypeCellData,
|
||||
filter: &<Self as TypeOptionConfiguration>::CellFilterConfiguration,
|
||||
) -> FlowyResult<bool> {
|
||||
if !type_cell_data.is_text() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let text_cell_data = self.decode_type_option_cell_str(type_cell_data.cell_str)?;
|
||||
Ok(filter.is_visible(text_cell_data))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::all)]
|
||||
|
@ -52,28 +52,28 @@ mod tests {
|
||||
let text_type_option = RichTextTypeOptionPB::default();
|
||||
let field_type = FieldType::MultiSelect;
|
||||
|
||||
let France = SelectOptionPB::new("France");
|
||||
let france_optionId = France.id.clone();
|
||||
let france = SelectOptionPB::new("france");
|
||||
let france_option_id = france.id.clone();
|
||||
|
||||
let Argentina = SelectOptionPB::new("Argentina");
|
||||
let argentina_optionId = Argentina.id.clone();
|
||||
let argentina = SelectOptionPB::new("argentina");
|
||||
let argentina_option_id = argentina.id.clone();
|
||||
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.add_option(France.clone())
|
||||
.add_option(Argentina.clone());
|
||||
.add_option(france.clone())
|
||||
.add_option(argentina.clone());
|
||||
|
||||
let field_rev = FieldBuilder::new(multi_select).build();
|
||||
|
||||
assert_eq!(
|
||||
text_type_option
|
||||
.decode_cell_str(
|
||||
format!("{},{}", france_optionId, argentina_optionId),
|
||||
format!("{},{}", france_option_id, argentina_option_id),
|
||||
&field_type,
|
||||
&field_rev
|
||||
)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
format!("{},{}", France.name, Argentina.name)
|
||||
format!("{},{}", france.name, argentina.name)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
use crate::entities::{FieldType, TextFilterPB};
|
||||
use crate::impl_type_option;
|
||||
use crate::services::cell::{
|
||||
stringify_cell_data, CellComparable, CellDataChangeset, CellDataDecoder, CellProtobufBlobParser, DecodedCellData,
|
||||
FromCellString, TypeCellData,
|
||||
stringify_cell_data, CellDataChangeset, CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellString,
|
||||
TypeCellData,
|
||||
};
|
||||
use crate::services::field::{
|
||||
BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
|
||||
TypeOptionTransform,
|
||||
BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare,
|
||||
TypeOptionCellDataFilter, TypeOptionTransform,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
@ -45,14 +45,11 @@ impl TypeOption for RichTextTypeOptionPB {
|
||||
type CellData = StrCellData;
|
||||
type CellChangeset = String;
|
||||
type CellProtobufType = StrCellData;
|
||||
type CellFilter = TextFilterPB;
|
||||
}
|
||||
|
||||
impl TypeOptionTransform for RichTextTypeOptionPB {}
|
||||
|
||||
impl TypeOptionConfiguration for RichTextTypeOptionPB {
|
||||
type CellFilterConfiguration = TextFilterPB;
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for RichTextTypeOptionPB {
|
||||
fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType {
|
||||
cell_data
|
||||
@ -92,23 +89,41 @@ impl CellDataChangeset for RichTextTypeOptionPB {
|
||||
&self,
|
||||
changeset: <Self as TypeOption>::CellChangeset,
|
||||
_type_cell_data: Option<TypeCellData>,
|
||||
) -> FlowyResult<String> {
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
if changeset.len() > 10000 {
|
||||
Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000"))
|
||||
} else {
|
||||
Ok(changeset)
|
||||
Ok(StrCellData(changeset))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CellComparable for RichTextTypeOptionPB {
|
||||
type CellData = String;
|
||||
impl TypeOptionCellDataFilter for RichTextTypeOptionPB {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
filter: &<Self as TypeOption>::CellFilter,
|
||||
field_type: &FieldType,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> bool {
|
||||
if !field_type.is_text() {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn apply_cmp(&self, cell_data: &Self::CellData, other_cell_data: &Self::CellData) -> Ordering {
|
||||
cell_data.cmp(other_cell_data)
|
||||
filter.is_visible(cell_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataCompare for RichTextTypeOptionPB {
|
||||
fn apply_cmp(
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> Ordering {
|
||||
cell_data.0.cmp(&other_cell_data.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TextCellData(pub String);
|
||||
impl AsRef<str> for TextCellData {
|
||||
fn as_ref(&self) -> &str {
|
||||
@ -158,7 +173,7 @@ impl CellProtobufBlobParser for TextCellDataParser {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct StrCellData(pub String);
|
||||
impl std::ops::Deref for StrCellData {
|
||||
type Target = String;
|
||||
|
@ -1,15 +1,12 @@
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::{
|
||||
CellDataChangeset, CellDataDecoder, CellProtobufBlob, FromCellChangeset, FromCellString, TypeCellData,
|
||||
};
|
||||
use crate::services::field::{
|
||||
CheckboxTypeOptionPB, ChecklistTypeOptionPB, DateTypeOptionPB, MultiSelectTypeOptionPB, NumberTypeOptionPB,
|
||||
RichTextTypeOptionPB, SingleSelectTypeOptionPB, URLTypeOptionPB,
|
||||
};
|
||||
use crate::services::cell::{CellDataDecoder, FromCellChangeset, FromCellString};
|
||||
|
||||
use crate::services::filter::FromFilterString;
|
||||
use bytes::Bytes;
|
||||
use flowy_error::FlowyResult;
|
||||
use grid_rev_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
|
||||
use grid_rev_model::FieldRevision;
|
||||
use protobuf::ProtobufError;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait TypeOption {
|
||||
@ -23,7 +20,7 @@ pub trait TypeOption {
|
||||
///
|
||||
/// Uses `StrCellData` for any `TypeOption` if their cell data is pure `String`.
|
||||
///
|
||||
type CellData: FromCellString + Default;
|
||||
type CellData: FromCellString + ToString + Default + Send + Sync + Clone + 'static;
|
||||
|
||||
/// Represents as the corresponding field type cell changeset.
|
||||
/// The changeset must implements the `FromCellChangeset` trait. The `CellChangeset` is implemented
|
||||
@ -39,6 +36,9 @@ pub trait TypeOption {
|
||||
/// FieldType::URL => URLCellDataPB
|
||||
///
|
||||
type CellProtobufType: TryInto<Bytes, Error = ProtobufError> + Debug;
|
||||
|
||||
/// Represents as the filter configuration for this type option.
|
||||
type CellFilter: FromFilterString + Send + Sync + 'static;
|
||||
}
|
||||
|
||||
pub trait TypeOptionCellData: TypeOption {
|
||||
@ -55,10 +55,6 @@ pub trait TypeOptionCellData: TypeOption {
|
||||
fn decode_type_option_cell_str(&self, cell_str: String) -> FlowyResult<<Self as TypeOption>::CellData>;
|
||||
}
|
||||
|
||||
pub trait TypeOptionConfiguration {
|
||||
type CellFilterConfiguration;
|
||||
}
|
||||
|
||||
pub trait TypeOptionTransform: TypeOption {
|
||||
/// Returns true if the current `TypeOption` provides custom type option transformation
|
||||
fn transformable(&self) -> bool {
|
||||
@ -97,189 +93,24 @@ pub trait TypeOptionTransform: TypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper trait that used to erase the `Self` of `TypeOption` trait to make it become a Object-safe trait.
|
||||
pub trait TypeOptionTransformHandler {
|
||||
fn transform(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String);
|
||||
|
||||
fn json_str(&self) -> String;
|
||||
}
|
||||
|
||||
impl<T> TypeOptionTransformHandler for T
|
||||
where
|
||||
T: TypeOptionTransform + TypeOptionDataSerializer,
|
||||
{
|
||||
fn transform(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String) {
|
||||
if self.transformable() {
|
||||
self.transform_type_option(old_type_option_field_type, old_type_option_data)
|
||||
}
|
||||
}
|
||||
|
||||
fn json_str(&self) -> String {
|
||||
self.json_str()
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper trait that used to erase the `Self` of `TypeOption` trait to make it become a Object-safe trait
|
||||
/// Only object-safe traits can be made into trait objects.
|
||||
/// > Object-safe traits are traits with methods that follow these two rules:
|
||||
/// 1.the return type is not Self.
|
||||
/// 2.there are no generic types parameters.
|
||||
///
|
||||
pub trait TypeOptionCellDataHandler {
|
||||
fn handle_cell_str(
|
||||
&self,
|
||||
cell_str: String,
|
||||
decoded_field_type: &FieldType,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<CellProtobufBlob>;
|
||||
|
||||
fn handle_cell_changeset(
|
||||
&self,
|
||||
cell_changeset: String,
|
||||
old_type_cell_data: Option<TypeCellData>,
|
||||
) -> FlowyResult<String>;
|
||||
|
||||
/// Decode the cell_str to corresponding cell data, and then return the display string of the
|
||||
/// cell data.
|
||||
fn stringify_cell_str(&self, cell_str: String, field_type: &FieldType, field_rev: &FieldRevision) -> String;
|
||||
}
|
||||
|
||||
impl<T> TypeOptionCellDataHandler for T
|
||||
where
|
||||
T: TypeOption + CellDataDecoder + CellDataChangeset + TypeOptionCellData + TypeOptionTransform,
|
||||
{
|
||||
fn handle_cell_str(
|
||||
&self,
|
||||
cell_str: String,
|
||||
decoded_field_type: &FieldType,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<CellProtobufBlob> {
|
||||
let cell_data = if self.transformable() {
|
||||
match self.transform_type_option_cell_str(&cell_str, decoded_field_type, field_rev) {
|
||||
None => self.decode_cell_str(cell_str, decoded_field_type, field_rev)?,
|
||||
Some(cell_data) => cell_data,
|
||||
}
|
||||
} else {
|
||||
self.decode_cell_str(cell_str, decoded_field_type, field_rev)?
|
||||
};
|
||||
CellProtobufBlob::from(self.convert_to_protobuf(cell_data))
|
||||
}
|
||||
|
||||
fn handle_cell_changeset(
|
||||
&self,
|
||||
cell_changeset: String,
|
||||
old_type_cell_data: Option<TypeCellData>,
|
||||
) -> FlowyResult<String> {
|
||||
let changeset = <Self as TypeOption>::CellChangeset::from_changeset(cell_changeset)?;
|
||||
let cell_data = self.apply_changeset(changeset, old_type_cell_data)?;
|
||||
Ok(cell_data)
|
||||
}
|
||||
|
||||
fn stringify_cell_str(&self, cell_str: String, field_type: &FieldType, field_rev: &FieldRevision) -> String {
|
||||
if self.transformable() {
|
||||
let cell_data = self.transform_type_option_cell_str(&cell_str, field_type, field_rev);
|
||||
if let Some(cell_data) = cell_data {
|
||||
return self.decode_cell_data_to_str(cell_data);
|
||||
}
|
||||
}
|
||||
match <Self as TypeOption>::CellData::from_cell_str(&cell_str) {
|
||||
Ok(cell_data) => self.decode_cell_data_to_str(cell_data),
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FieldRevisionExt<'a> {
|
||||
field_rev: &'a FieldRevision,
|
||||
}
|
||||
|
||||
impl<'a> FieldRevisionExt<'a> {
|
||||
pub fn new(field_rev: &'a FieldRevision) -> Self {
|
||||
Self { field_rev }
|
||||
}
|
||||
|
||||
pub fn get_type_option_cell_data_handler(
|
||||
pub trait TypeOptionCellDataFilter: TypeOption + CellDataDecoder {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
filter: &<Self as TypeOption>::CellFilter,
|
||||
field_type: &FieldType,
|
||||
) -> Option<Box<dyn TypeOptionCellDataHandler>> {
|
||||
match field_type {
|
||||
FieldType::RichText => self
|
||||
.field_rev
|
||||
.get_type_option::<RichTextTypeOptionPB>(field_type.into())
|
||||
.map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
|
||||
FieldType::Number => self
|
||||
.field_rev
|
||||
.get_type_option::<NumberTypeOptionPB>(field_type.into())
|
||||
.map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
|
||||
FieldType::DateTime => self
|
||||
.field_rev
|
||||
.get_type_option::<DateTypeOptionPB>(field_type.into())
|
||||
.map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
|
||||
FieldType::SingleSelect => self
|
||||
.field_rev
|
||||
.get_type_option::<SingleSelectTypeOptionPB>(field_type.into())
|
||||
.map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
|
||||
FieldType::MultiSelect => self
|
||||
.field_rev
|
||||
.get_type_option::<MultiSelectTypeOptionPB>(field_type.into())
|
||||
.map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
|
||||
FieldType::Checkbox => self
|
||||
.field_rev
|
||||
.get_type_option::<CheckboxTypeOptionPB>(field_type.into())
|
||||
.map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
|
||||
FieldType::URL => self
|
||||
.field_rev
|
||||
.get_type_option::<URLTypeOptionPB>(field_type.into())
|
||||
.map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
|
||||
FieldType::Checklist => self
|
||||
.field_rev
|
||||
.get_type_option::<ChecklistTypeOptionPB>(field_type.into())
|
||||
.map(|type_option| Box::new(type_option) as Box<dyn TypeOptionCellDataHandler>),
|
||||
}
|
||||
}
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
pub fn transform_type_option(
|
||||
type_option_data: &str,
|
||||
new_field_type: &FieldType,
|
||||
old_type_option_data: Option<String>,
|
||||
old_field_type: FieldType,
|
||||
) -> String {
|
||||
let mut transform_handler = get_type_option_transform_handler(type_option_data, new_field_type);
|
||||
if let Some(old_type_option_data) = old_type_option_data {
|
||||
transform_handler.transform(old_field_type, old_type_option_data);
|
||||
}
|
||||
transform_handler.json_str()
|
||||
#[inline(always)]
|
||||
pub fn default_order() -> Ordering {
|
||||
Ordering::Equal
|
||||
}
|
||||
|
||||
pub fn get_type_option_transform_handler(
|
||||
type_option_data: &str,
|
||||
field_type: &FieldType,
|
||||
) -> Box<dyn TypeOptionTransformHandler> {
|
||||
match field_type {
|
||||
FieldType::RichText => {
|
||||
Box::new(RichTextTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
|
||||
}
|
||||
FieldType::Number => {
|
||||
Box::new(NumberTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
Box::new(DateTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
|
||||
}
|
||||
FieldType::SingleSelect => {
|
||||
Box::new(SingleSelectTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
Box::new(MultiSelectTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
Box::new(CheckboxTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
|
||||
}
|
||||
FieldType::URL => {
|
||||
Box::new(URLTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
|
||||
}
|
||||
FieldType::Checklist => {
|
||||
Box::new(ChecklistTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
|
||||
}
|
||||
}
|
||||
pub trait TypeOptionCellDataCompare: TypeOption {
|
||||
fn apply_cmp(
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> Ordering;
|
||||
}
|
||||
|
@ -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>
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
mod url_filter;
|
||||
mod url_tests;
|
||||
mod url_type_option;
|
||||
mod url_type_option_entities;
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
|
||||
use crate::services::cell::CellDataChangeset;
|
||||
|
||||
use crate::services::field::FieldBuilder;
|
||||
use crate::services::field::{URLCellData, URLTypeOptionPB};
|
||||
use crate::services::field::URLTypeOptionPB;
|
||||
use grid_rev_model::FieldRevision;
|
||||
|
||||
/// The expected_str will equal to the input string, but the expected_url will be empty if there's no
|
||||
@ -14,8 +14,8 @@ mod tests {
|
||||
let type_option = URLTypeOptionPB::default();
|
||||
let field_type = FieldType::URL;
|
||||
let field_rev = FieldBuilder::from_field_type(&field_type).build();
|
||||
assert_url(&type_option, "123", "123", "", &field_type, &field_rev);
|
||||
assert_url(&type_option, "", "", "", &field_type, &field_rev);
|
||||
assert_url(&type_option, "123", "123", "", &field_rev);
|
||||
assert_url(&type_option, "", "", "", &field_rev);
|
||||
}
|
||||
|
||||
/// The expected_str will equal to the input string, but the expected_url will not be empty
|
||||
@ -30,7 +30,6 @@ mod tests {
|
||||
"AppFlowy website - https://www.appflowy.io",
|
||||
"AppFlowy website - https://www.appflowy.io",
|
||||
"https://www.appflowy.io/",
|
||||
&field_type,
|
||||
&field_rev,
|
||||
);
|
||||
|
||||
@ -39,7 +38,6 @@ mod tests {
|
||||
"AppFlowy website appflowy.io",
|
||||
"AppFlowy website appflowy.io",
|
||||
"https://appflowy.io",
|
||||
&field_type,
|
||||
&field_rev,
|
||||
);
|
||||
}
|
||||
@ -55,7 +53,6 @@ mod tests {
|
||||
"AppFlowy website - https://www.appflowy.io welcome!",
|
||||
"AppFlowy website - https://www.appflowy.io welcome!",
|
||||
"https://www.appflowy.io/",
|
||||
&field_type,
|
||||
&field_rev,
|
||||
);
|
||||
|
||||
@ -64,7 +61,6 @@ mod tests {
|
||||
"AppFlowy website appflowy.io welcome!",
|
||||
"AppFlowy website appflowy.io welcome!",
|
||||
"https://appflowy.io",
|
||||
&field_type,
|
||||
&field_rev,
|
||||
);
|
||||
}
|
||||
@ -80,7 +76,6 @@ mod tests {
|
||||
"AppFlowy website - https://www.appflowy.io!",
|
||||
"AppFlowy website - https://www.appflowy.io!",
|
||||
"https://www.appflowy.io/",
|
||||
&field_type,
|
||||
&field_rev,
|
||||
);
|
||||
|
||||
@ -89,7 +84,6 @@ mod tests {
|
||||
"AppFlowy website appflowy.io!",
|
||||
"AppFlowy website appflowy.io!",
|
||||
"https://appflowy.io",
|
||||
&field_type,
|
||||
&field_rev,
|
||||
);
|
||||
}
|
||||
@ -105,7 +99,6 @@ mod tests {
|
||||
"test - https://tester.testgroup.appflowy.io",
|
||||
"test - https://tester.testgroup.appflowy.io",
|
||||
"https://tester.testgroup.appflowy.io/",
|
||||
&field_type,
|
||||
&field_rev,
|
||||
);
|
||||
|
||||
@ -114,7 +107,6 @@ mod tests {
|
||||
"test tester.testgroup.appflowy.io",
|
||||
"test tester.testgroup.appflowy.io",
|
||||
"https://tester.testgroup.appflowy.io",
|
||||
&field_type,
|
||||
&field_rev,
|
||||
);
|
||||
}
|
||||
@ -130,7 +122,6 @@ mod tests {
|
||||
"appflowy - https://appflowy.com",
|
||||
"appflowy - https://appflowy.com",
|
||||
"https://appflowy.com/",
|
||||
&field_type,
|
||||
&field_rev,
|
||||
);
|
||||
|
||||
@ -139,7 +130,6 @@ mod tests {
|
||||
"appflowy - https://appflowy.top",
|
||||
"appflowy - https://appflowy.top",
|
||||
"https://appflowy.top/",
|
||||
&field_type,
|
||||
&field_rev,
|
||||
);
|
||||
|
||||
@ -148,7 +138,6 @@ mod tests {
|
||||
"appflowy - https://appflowy.net",
|
||||
"appflowy - https://appflowy.net",
|
||||
"https://appflowy.net/",
|
||||
&field_type,
|
||||
&field_rev,
|
||||
);
|
||||
|
||||
@ -157,7 +146,6 @@ mod tests {
|
||||
"appflowy - https://appflowy.edu",
|
||||
"appflowy - https://appflowy.edu",
|
||||
"https://appflowy.edu/",
|
||||
&field_type,
|
||||
&field_rev,
|
||||
);
|
||||
}
|
||||
@ -167,23 +155,10 @@ mod tests {
|
||||
input_str: &str,
|
||||
expected_str: &str,
|
||||
expected_url: &str,
|
||||
field_type: &FieldType,
|
||||
field_rev: &FieldRevision,
|
||||
_field_rev: &FieldRevision,
|
||||
) {
|
||||
let encoded_data = type_option.apply_changeset(input_str.to_owned(), None).unwrap();
|
||||
let decode_cell_data = decode_cell_data(encoded_data, type_option, field_rev, field_type);
|
||||
let decode_cell_data = type_option.apply_changeset(input_str.to_owned(), None).unwrap();
|
||||
assert_eq!(expected_str.to_owned(), decode_cell_data.content);
|
||||
assert_eq!(expected_url.to_owned(), decode_cell_data.url);
|
||||
}
|
||||
|
||||
fn decode_cell_data(
|
||||
encoded_data: String,
|
||||
type_option: &URLTypeOptionPB,
|
||||
field_rev: &FieldRevision,
|
||||
field_type: &FieldType,
|
||||
) -> URLCellData {
|
||||
type_option
|
||||
.decode_cell_str(encoded_data, field_type, field_rev)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ use crate::entities::{FieldType, TextFilterPB};
|
||||
use crate::impl_type_option;
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder, FromCellString, TypeCellData};
|
||||
use crate::services::field::{
|
||||
BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
|
||||
TypeOptionTransform, URLCellData, URLCellDataPB,
|
||||
BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare,
|
||||
TypeOptionCellDataFilter, TypeOptionTransform, URLCellData, URLCellDataPB,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use fancy_regex::Regex;
|
||||
@ -12,6 +12,7 @@ use flowy_error::FlowyResult;
|
||||
use grid_rev_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct URLTypeOptionBuilder(URLTypeOptionPB);
|
||||
@ -39,14 +40,11 @@ impl TypeOption for URLTypeOptionPB {
|
||||
type CellData = URLCellData;
|
||||
type CellChangeset = URLCellChangeset;
|
||||
type CellProtobufType = URLCellDataPB;
|
||||
type CellFilter = TextFilterPB;
|
||||
}
|
||||
|
||||
impl TypeOptionTransform for URLTypeOptionPB {}
|
||||
|
||||
impl TypeOptionConfiguration for URLTypeOptionPB {
|
||||
type CellFilterConfiguration = TextFilterPB;
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for URLTypeOptionPB {
|
||||
fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType {
|
||||
cell_data.into()
|
||||
@ -83,19 +81,42 @@ impl CellDataChangeset for URLTypeOptionPB {
|
||||
&self,
|
||||
changeset: <Self as TypeOption>::CellChangeset,
|
||||
_type_cell_data: Option<TypeCellData>,
|
||||
) -> FlowyResult<String> {
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
let mut url = "".to_string();
|
||||
if let Ok(Some(m)) = URL_REGEX.find(&changeset) {
|
||||
url = auto_append_scheme(m.as_str());
|
||||
}
|
||||
URLCellData {
|
||||
Ok(URLCellData {
|
||||
url,
|
||||
content: changeset,
|
||||
}
|
||||
.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 {
|
||||
// Only support https scheme by now
|
||||
match url::Url::parse(s) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
use crate::entities::filter_entities::*;
|
||||
use crate::entities::{FieldType, InsertedRowPB, RowPB};
|
||||
use crate::services::cell::{CellFilterable, TypeCellData};
|
||||
use crate::services::cell::{AnyTypeCache, AtomicCellDataCache, AtomicCellFilterCache, TypeCellData};
|
||||
use crate::services::field::*;
|
||||
use crate::services::filter::{FilterChangeset, FilterMap, FilterResult, FilterResultNotification, FilterType};
|
||||
use crate::services::filter::{FilterChangeset, FilterResult, FilterResultNotification, FilterType};
|
||||
use crate::services::row::GridBlockRowRevision;
|
||||
use crate::services::view_editor::{GridViewChanged, GridViewChangedNotifier};
|
||||
use flowy_error::FlowyResult;
|
||||
@ -24,12 +24,19 @@ pub trait FilterDelegate: Send + Sync + 'static {
|
||||
fn get_row_rev(&self, rows_id: &str) -> Fut<Option<(usize, Arc<RowRevision>)>>;
|
||||
}
|
||||
|
||||
pub trait FromFilterString {
|
||||
fn from_filter_rev(filter_rev: &FilterRevision) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub struct FilterController {
|
||||
view_id: String,
|
||||
handler_id: String,
|
||||
delegate: Box<dyn FilterDelegate>,
|
||||
filter_map: FilterMap,
|
||||
result_by_row_id: HashMap<RowId, FilterResult>,
|
||||
cell_data_cache: AtomicCellDataCache,
|
||||
cell_filter_cache: AtomicCellFilterCache,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
notifier: GridViewChangedNotifier,
|
||||
}
|
||||
@ -41,6 +48,7 @@ impl FilterController {
|
||||
delegate: T,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
filter_revs: Vec<Arc<FilterRevision>>,
|
||||
cell_data_cache: AtomicCellDataCache,
|
||||
notifier: GridViewChangedNotifier,
|
||||
) -> Self
|
||||
where
|
||||
@ -50,8 +58,9 @@ impl FilterController {
|
||||
view_id: view_id.to_string(),
|
||||
handler_id: handler_id.to_string(),
|
||||
delegate: Box::new(delegate),
|
||||
filter_map: FilterMap::new(),
|
||||
result_by_row_id: HashMap::default(),
|
||||
cell_data_cache,
|
||||
cell_filter_cache: AnyTypeCache::<FilterType>::new(),
|
||||
task_scheduler,
|
||||
notifier,
|
||||
};
|
||||
@ -75,16 +84,17 @@ impl FilterController {
|
||||
}
|
||||
|
||||
pub async fn filter_row_revs(&mut self, row_revs: &mut Vec<Arc<RowRevision>>) {
|
||||
if self.filter_map.is_empty() {
|
||||
if self.cell_filter_cache.read().is_empty() {
|
||||
return;
|
||||
}
|
||||
let field_rev_by_field_id = self.get_filter_revs_map().await;
|
||||
row_revs.iter().for_each(|row_rev| {
|
||||
let _ = filter_row(
|
||||
row_rev,
|
||||
&self.filter_map,
|
||||
&mut self.result_by_row_id,
|
||||
&field_rev_by_field_id,
|
||||
&self.cell_data_cache,
|
||||
&self.cell_filter_cache,
|
||||
);
|
||||
});
|
||||
|
||||
@ -121,9 +131,10 @@ impl FilterController {
|
||||
let mut notification = FilterResultNotification::new(self.view_id.clone(), row_rev.block_id.clone());
|
||||
if let Some((row_id, is_visible)) = filter_row(
|
||||
&row_rev,
|
||||
&self.filter_map,
|
||||
&mut self.result_by_row_id,
|
||||
&field_rev_by_field_id,
|
||||
&self.cell_data_cache,
|
||||
&self.cell_filter_cache,
|
||||
) {
|
||||
if is_visible {
|
||||
if let Some((index, row_rev)) = self.delegate.get_row_rev(&row_id).await {
|
||||
@ -154,9 +165,10 @@ impl FilterController {
|
||||
for (index, row_rev) in block.row_revs.iter().enumerate() {
|
||||
if let Some((row_id, is_visible)) = filter_row(
|
||||
row_rev,
|
||||
&self.filter_map,
|
||||
&mut self.result_by_row_id,
|
||||
&field_rev_by_field_id,
|
||||
&self.cell_data_cache,
|
||||
&self.cell_filter_cache,
|
||||
) {
|
||||
if is_visible {
|
||||
let row_pb = RowPB::from(row_rev.as_ref());
|
||||
@ -233,7 +245,7 @@ impl FilterController {
|
||||
if let Some(filter) = self.filter_from_filter_type(filter_type).await {
|
||||
notification = Some(FilterChangesetNotificationPB::from_delete(&self.view_id, vec![filter]));
|
||||
}
|
||||
self.filter_map.remove(filter_type);
|
||||
self.cell_filter_cache.write().remove(filter_type);
|
||||
}
|
||||
|
||||
let _ = self
|
||||
@ -258,46 +270,39 @@ impl FilterController {
|
||||
tracing::trace!("Create filter with type: {:?}", filter_type);
|
||||
match &filter_type.field_type {
|
||||
FieldType::RichText => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.text_filter
|
||||
.insert(filter_type, TextFilterPB::from(filter_rev.as_ref()));
|
||||
self.cell_filter_cache
|
||||
.write()
|
||||
.insert(&filter_type, TextFilterPB::from_filter_rev(filter_rev.as_ref()));
|
||||
}
|
||||
FieldType::Number => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.number_filter
|
||||
.insert(filter_type, NumberFilterPB::from(filter_rev.as_ref()));
|
||||
self.cell_filter_cache
|
||||
.write()
|
||||
.insert(&filter_type, NumberFilterPB::from_filter_rev(filter_rev.as_ref()));
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.date_filter
|
||||
.insert(filter_type, DateFilterPB::from(filter_rev.as_ref()));
|
||||
self.cell_filter_cache
|
||||
.write()
|
||||
.insert(&filter_type, DateFilterPB::from_filter_rev(filter_rev.as_ref()));
|
||||
}
|
||||
FieldType::SingleSelect | FieldType::MultiSelect => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.select_option_filter
|
||||
.insert(filter_type, SelectOptionFilterPB::from(filter_rev.as_ref()));
|
||||
self.cell_filter_cache
|
||||
.write()
|
||||
.insert(&filter_type, SelectOptionFilterPB::from_filter_rev(filter_rev.as_ref()));
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.checkbox_filter
|
||||
.insert(filter_type, CheckboxFilterPB::from(filter_rev.as_ref()));
|
||||
self.cell_filter_cache
|
||||
.write()
|
||||
.insert(&filter_type, CheckboxFilterPB::from_filter_rev(filter_rev.as_ref()));
|
||||
}
|
||||
FieldType::URL => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.url_filter
|
||||
.insert(filter_type, TextFilterPB::from(filter_rev.as_ref()));
|
||||
self.cell_filter_cache
|
||||
.write()
|
||||
.insert(&filter_type, TextFilterPB::from_filter_rev(filter_rev.as_ref()));
|
||||
}
|
||||
FieldType::Checklist => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.checklist_filter
|
||||
.insert(filter_type, ChecklistFilterPB::from(filter_rev.as_ref()));
|
||||
self.cell_filter_cache
|
||||
.write()
|
||||
.insert(&filter_type, ChecklistFilterPB::from_filter_rev(filter_rev.as_ref()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -309,9 +314,10 @@ impl FilterController {
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
fn filter_row(
|
||||
row_rev: &Arc<RowRevision>,
|
||||
filter_map: &FilterMap,
|
||||
result_by_row_id: &mut HashMap<RowId, FilterResult>,
|
||||
field_rev_by_field_id: &HashMap<FieldId, Arc<FieldRevision>>,
|
||||
cell_data_cache: &AtomicCellDataCache,
|
||||
cell_filter_cache: &AtomicCellFilterCache,
|
||||
) -> Option<(String, bool)> {
|
||||
// Create a filter result cache if it's not exist
|
||||
let filter_result = result_by_row_id
|
||||
@ -322,7 +328,7 @@ fn filter_row(
|
||||
// Iterate each cell of the row to check its visibility
|
||||
for (field_id, field_rev) in field_rev_by_field_id {
|
||||
let filter_type = FilterType::from(field_rev);
|
||||
if !filter_map.has_filter(&filter_type) {
|
||||
if !cell_filter_cache.read().contains(&filter_type) {
|
||||
filter_result.visible_by_filter_id.remove(&filter_type);
|
||||
continue;
|
||||
}
|
||||
@ -330,7 +336,7 @@ fn filter_row(
|
||||
let cell_rev = row_rev.cells.get(field_id);
|
||||
// if the visibility of the cell_rew is changed, which means the visibility of the
|
||||
// row is changed too.
|
||||
if let Some(is_visible) = filter_cell(&filter_type, field_rev, filter_map, cell_rev) {
|
||||
if let Some(is_visible) = filter_cell(&filter_type, field_rev, cell_rev, cell_data_cache, cell_filter_cache) {
|
||||
filter_result.visible_by_filter_id.insert(filter_type, is_visible);
|
||||
}
|
||||
}
|
||||
@ -348,93 +354,32 @@ fn filter_row(
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, fields(cell_content))]
|
||||
fn filter_cell(
|
||||
filter_id: &FilterType,
|
||||
filter_type: &FilterType,
|
||||
field_rev: &Arc<FieldRevision>,
|
||||
filter_map: &FilterMap,
|
||||
cell_rev: Option<&CellRevision>,
|
||||
cell_data_cache: &AtomicCellDataCache,
|
||||
cell_filter_cache: &AtomicCellFilterCache,
|
||||
) -> Option<bool> {
|
||||
let type_cell_data = match cell_rev {
|
||||
None => TypeCellData::from_field_type(&filter_id.field_type),
|
||||
None => TypeCellData::from_field_type(&filter_type.field_type),
|
||||
Some(cell_rev) => match TypeCellData::try_from(cell_rev) {
|
||||
Ok(cell_data) => cell_data,
|
||||
Err(err) => {
|
||||
tracing::error!("Deserialize TypeCellData failed: {}", err);
|
||||
TypeCellData::from_field_type(&filter_id.field_type)
|
||||
TypeCellData::from_field_type(&filter_type.field_type)
|
||||
}
|
||||
},
|
||||
};
|
||||
let cloned_type_cell_data = type_cell_data.cell_str.clone();
|
||||
let is_visible = match &filter_id.field_type {
|
||||
FieldType::RichText => filter_map.text_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<RichTextTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(type_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::Number => filter_map.number_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<NumberTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(type_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::DateTime => filter_map.date_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<DateTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(type_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::SingleSelect => filter_map.select_option_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<SingleSelectTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(type_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::MultiSelect => filter_map.select_option_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<MultiSelectTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(type_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::Checkbox => filter_map.checkbox_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<CheckboxTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(type_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::URL => filter_map.url_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<URLTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(type_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::Checklist => filter_map.checklist_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<ChecklistTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(type_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
}?;
|
||||
tracing::Span::current().record(
|
||||
"cell_content",
|
||||
&format!("{} => {:?}", cloned_type_cell_data, is_visible.unwrap()).as_str(),
|
||||
);
|
||||
is_visible
|
||||
|
||||
let handler = TypeOptionCellExt::new(
|
||||
field_rev.as_ref(),
|
||||
Some(cell_data_cache.clone()),
|
||||
Some(cell_filter_cache.clone()),
|
||||
)
|
||||
.get_type_option_cell_data_handler(&filter_type.field_type)?;
|
||||
|
||||
let is_visible = handler.handle_cell_filter(filter_type, field_rev.as_ref(), type_cell_data);
|
||||
Some(is_visible)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
|
@ -1,9 +1,7 @@
|
||||
mod cache;
|
||||
mod controller;
|
||||
mod entities;
|
||||
mod task;
|
||||
|
||||
pub(crate) use cache::*;
|
||||
pub use controller::*;
|
||||
pub use entities::*;
|
||||
pub(crate) use task::*;
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::services::filter::FilterController;
|
||||
use crate::services::filter::{FilterController, FilterType};
|
||||
use flowy_task::{TaskContent, TaskHandler};
|
||||
use lib_infra::future::BoxResultFuture;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
@ -38,3 +39,21 @@ impl TaskHandler for FilterTaskHandler {
|
||||
})
|
||||
}
|
||||
}
|
||||
/// Refresh the filter according to the field id.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct FilterResult {
|
||||
pub(crate) visible_by_filter_id: HashMap<FilterType, bool>,
|
||||
}
|
||||
|
||||
impl FilterResult {
|
||||
pub(crate) fn is_visible(&self) -> bool {
|
||||
let mut is_visible = true;
|
||||
for visible in self.visible_by_filter_id.values() {
|
||||
if !is_visible {
|
||||
break;
|
||||
}
|
||||
is_visible = *visible;
|
||||
}
|
||||
is_visible
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,10 @@ use crate::entities::CellPathParams;
|
||||
use crate::entities::*;
|
||||
use crate::manager::GridUser;
|
||||
use crate::services::block_manager::GridBlockManager;
|
||||
use crate::services::cell::{apply_cell_data_changeset, decode_type_cell_data, CellProtobufBlob};
|
||||
use crate::services::cell::{
|
||||
apply_cell_data_changeset, decode_type_cell_data, stringify_cell_data, AnyTypeCache, AtomicCellDataCache,
|
||||
CellProtobufBlob, TypeCellData,
|
||||
};
|
||||
use crate::services::field::{
|
||||
default_type_option_builder_from_type, transform_type_option, type_option_builder_from_bytes, FieldBuilder,
|
||||
};
|
||||
@ -39,6 +42,7 @@ pub struct GridRevisionEditor {
|
||||
view_manager: Arc<GridViewManager>,
|
||||
rev_manager: Arc<RevisionManager<Arc<ConnectionPool>>>,
|
||||
block_manager: Arc<GridBlockManager>,
|
||||
cell_data_cache: AtomicCellDataCache,
|
||||
}
|
||||
|
||||
impl Drop for GridRevisionEditor {
|
||||
@ -60,6 +64,7 @@ impl GridRevisionEditor {
|
||||
let grid_pad = rev_manager.initialize::<GridRevisionSerde>(Some(cloud)).await?;
|
||||
let rev_manager = Arc::new(rev_manager);
|
||||
let grid_pad = Arc::new(RwLock::new(grid_pad));
|
||||
let cell_data_cache = AnyTypeCache::<u64>::new();
|
||||
|
||||
// Block manager
|
||||
let (block_event_tx, block_event_rx) = broadcast::channel(100);
|
||||
@ -72,8 +77,16 @@ impl GridRevisionEditor {
|
||||
});
|
||||
|
||||
// View manager
|
||||
let view_manager =
|
||||
Arc::new(GridViewManager::new(grid_id.to_owned(), user.clone(), delegate, block_event_rx).await?);
|
||||
let view_manager = Arc::new(
|
||||
GridViewManager::new(
|
||||
grid_id.to_owned(),
|
||||
user.clone(),
|
||||
delegate,
|
||||
cell_data_cache.clone(),
|
||||
block_event_rx,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
let editor = Arc::new(Self {
|
||||
grid_id: grid_id.to_owned(),
|
||||
@ -82,6 +95,7 @@ impl GridRevisionEditor {
|
||||
rev_manager,
|
||||
block_manager,
|
||||
view_manager,
|
||||
cell_data_cache,
|
||||
});
|
||||
|
||||
Ok(editor)
|
||||
@ -430,6 +444,23 @@ impl GridRevisionEditor {
|
||||
Some(CellPB::new(¶ms.field_id, field_type, cell_bytes.to_vec()))
|
||||
}
|
||||
|
||||
pub async fn get_cell_display_str(&self, params: &CellPathParams) -> String {
|
||||
let display_str = || async {
|
||||
let field_rev = self.get_field_rev(¶ms.field_id).await?;
|
||||
let field_type: FieldType = field_rev.ty.into();
|
||||
let cell_rev = self.get_cell_rev(¶ms.row_id, ¶ms.field_id).await.ok()??;
|
||||
let type_cell_data: TypeCellData = cell_rev.try_into().ok()?;
|
||||
Some(stringify_cell_data(
|
||||
type_cell_data.cell_str,
|
||||
&field_type,
|
||||
&field_type,
|
||||
&field_rev,
|
||||
))
|
||||
};
|
||||
|
||||
display_str().await.unwrap_or("".to_string())
|
||||
}
|
||||
|
||||
pub async fn get_cell_bytes(&self, params: &CellPathParams) -> Option<CellProtobufBlob> {
|
||||
let (_, cell_data) = self.decode_cell_data_from(params).await?;
|
||||
Some(cell_data)
|
||||
@ -439,7 +470,11 @@ impl GridRevisionEditor {
|
||||
let field_rev = self.get_field_rev(¶ms.field_id).await?;
|
||||
let (_, row_rev) = self.block_manager.get_row_rev(¶ms.row_id).await.ok()??;
|
||||
let cell_rev = row_rev.cells.get(¶ms.field_id)?.clone();
|
||||
Some(decode_type_cell_data(cell_rev.type_cell_data, &field_rev))
|
||||
Some(decode_type_cell_data(
|
||||
cell_rev.type_cell_data,
|
||||
&field_rev,
|
||||
Some(self.cell_data_cache.clone()),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_cell_rev(&self, row_id: &str, field_id: &str) -> FlowyResult<Option<CellRevision>> {
|
||||
@ -470,7 +505,7 @@ impl GridRevisionEditor {
|
||||
tracing::trace!("field changeset: id:{} / value:{:?}", &field_id, content);
|
||||
let cell_rev = self.get_cell_rev(&row_id, &field_id).await?;
|
||||
// Update the changeset.data property with the return value.
|
||||
content = apply_cell_data_changeset(content, cell_rev, field_rev)?;
|
||||
content = apply_cell_data_changeset(content, cell_rev, field_rev, Some(self.cell_data_cache.clone()))?;
|
||||
let cell_changeset = CellChangesetPB {
|
||||
grid_id,
|
||||
row_id: row_id.clone(),
|
||||
@ -588,9 +623,9 @@ impl GridRevisionEditor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult<()> {
|
||||
let _ = self.view_manager.create_or_update_sort(params).await?;
|
||||
Ok(())
|
||||
pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult<SortRevision> {
|
||||
let sort_rev = self.view_manager.create_or_update_sort(params).await?;
|
||||
Ok(sort_rev)
|
||||
}
|
||||
|
||||
pub async fn move_row(&self, params: MoveRowParams) -> FlowyResult<()> {
|
||||
|
@ -184,7 +184,7 @@ where
|
||||
|
||||
if let Some(cell_rev) = cell_rev {
|
||||
let mut grouped_rows: Vec<GroupedRow> = vec![];
|
||||
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data, field_rev).1;
|
||||
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data, field_rev, None).1;
|
||||
let cell_data = cell_bytes.parser::<P>()?;
|
||||
for group in self.group_ctx.groups() {
|
||||
if self.can_group(&group.filter_content, &cell_data) {
|
||||
@ -224,7 +224,7 @@ where
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
|
||||
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
|
||||
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data.clone(), field_rev).1;
|
||||
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data.clone(), field_rev, None).1;
|
||||
let cell_data = cell_bytes.parser::<P>()?;
|
||||
let mut changesets = self.add_or_remove_row_in_groups_if_match(row_rev, &cell_data);
|
||||
|
||||
@ -247,7 +247,7 @@ where
|
||||
) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
|
||||
// if the cell_rev is none, then the row must in the default group.
|
||||
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
|
||||
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data.clone(), field_rev).1;
|
||||
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data.clone(), field_rev, None).1;
|
||||
let cell_data = cell_bytes.parser::<P>()?;
|
||||
if !cell_data.is_empty() {
|
||||
tracing::error!("did_delete_delete_row {:?}", cell_rev.type_cell_data);
|
||||
@ -280,7 +280,7 @@ where
|
||||
};
|
||||
|
||||
if let Some(cell_rev) = cell_rev {
|
||||
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data, context.field_rev).1;
|
||||
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data, context.field_rev, None).1;
|
||||
let cell_data = cell_bytes.parser::<P>()?;
|
||||
Ok(self.move_row(&cell_data, context))
|
||||
} else {
|
||||
|
@ -3,50 +3,50 @@
|
||||
use crate::entities::FieldType;
|
||||
#[allow(unused_attributes)]
|
||||
use crate::entities::SortChangesetNotificationPB;
|
||||
|
||||
use crate::services::sort::{SortChangeset, SortType};
|
||||
|
||||
use flowy_task::TaskDispatcher;
|
||||
use grid_rev_model::{CellRevision, FieldRevision, RowRevision, SortCondition, SortRevision};
|
||||
use lib_infra::future::Fut;
|
||||
|
||||
use crate::services::cell::{AtomicCellDataCache, TypeCellData};
|
||||
use crate::services::field::{default_order, TypeOptionCellExt};
|
||||
use rayon::prelude::ParallelSliceMut;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub trait SortDelegate: Send + Sync {
|
||||
fn get_sort_rev(&self, sort_type: SortType) -> Fut<Vec<Arc<SortRevision>>>;
|
||||
fn get_sort_rev(&self, sort_type: SortType) -> Fut<Option<Arc<SortRevision>>>;
|
||||
fn get_field_rev(&self, field_id: &str) -> Fut<Option<Arc<FieldRevision>>>;
|
||||
fn get_field_revs(&self, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<FieldRevision>>>;
|
||||
}
|
||||
|
||||
pub struct SortController {
|
||||
#[allow(dead_code)]
|
||||
view_id: String,
|
||||
#[allow(dead_code)]
|
||||
handler_id: String,
|
||||
#[allow(dead_code)]
|
||||
delegate: Box<dyn SortDelegate>,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
#[allow(dead_code)]
|
||||
sorts: Vec<SortRevision>,
|
||||
#[allow(dead_code)]
|
||||
row_orders: HashMap<String, usize>,
|
||||
sorts: Vec<Arc<SortRevision>>,
|
||||
cell_data_cache: AtomicCellDataCache,
|
||||
}
|
||||
|
||||
impl SortController {
|
||||
pub fn new<T>(view_id: &str, handler_id: &str, delegate: T, task_scheduler: Arc<RwLock<TaskDispatcher>>) -> Self
|
||||
pub fn new<T>(
|
||||
_view_id: &str,
|
||||
handler_id: &str,
|
||||
delegate: T,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
cell_data_cache: AtomicCellDataCache,
|
||||
) -> Self
|
||||
where
|
||||
T: SortDelegate + 'static,
|
||||
{
|
||||
Self {
|
||||
view_id: view_id.to_string(),
|
||||
handler_id: handler_id.to_string(),
|
||||
delegate: Box::new(delegate),
|
||||
task_scheduler,
|
||||
sorts: vec![],
|
||||
row_orders: HashMap::new(),
|
||||
cell_data_cache,
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,35 +58,53 @@ impl SortController {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub fn sort_rows(&self, _rows: &mut Vec<Arc<RowRevision>>) {
|
||||
// rows.par_sort_by(|left, right| cmp_row(left, right, &self.sorts));
|
||||
pub async fn sort_rows(&self, rows: &mut Vec<Arc<RowRevision>>) {
|
||||
let field_revs = self.delegate.get_field_revs(None).await;
|
||||
rows.par_sort_by(|left, right| cmp_row(left, right, &self.sorts, &field_revs, &self.cell_data_cache));
|
||||
}
|
||||
|
||||
pub async fn did_receive_changes(&mut self, _changeset: SortChangeset) -> Option<SortChangesetNotificationPB> {
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub async fn did_receive_changes(&mut self, changeset: SortChangeset) -> Option<SortChangesetNotificationPB> {
|
||||
if let Some(insert_sort) = changeset.insert_sort {
|
||||
if let Some(sort) = self.delegate.get_sort_rev(insert_sort).await {
|
||||
self.sorts.push(sort);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(delete_sort_type) = changeset.delete_sort {
|
||||
if let Some(index) = self.sorts.iter().position(|sort| sort.id == delete_sort_type.sort_id) {
|
||||
self.sorts.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(_update_sort) = changeset.update_sort {
|
||||
//
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn cmp_row(
|
||||
left: &Arc<RowRevision>,
|
||||
right: &Arc<RowRevision>,
|
||||
sorts: &[SortRevision],
|
||||
sorts: &[Arc<SortRevision>],
|
||||
field_revs: &[Arc<FieldRevision>],
|
||||
cell_data_cache: &AtomicCellDataCache,
|
||||
) -> Ordering {
|
||||
let mut order = Ordering::Equal;
|
||||
let mut order = default_order();
|
||||
for sort in sorts.iter() {
|
||||
let cmp_order = match (left.cells.get(&sort.field_id), right.cells.get(&sort.field_id)) {
|
||||
(Some(left_cell), Some(right_cell)) => {
|
||||
let field_type: FieldType = sort.field_type.into();
|
||||
match field_revs.iter().find(|field_rev| field_rev.id == sort.field_id) {
|
||||
None => Ordering::Equal,
|
||||
Some(field_rev) => cmp_cell(left_cell, right_cell, field_rev, field_type),
|
||||
None => default_order(),
|
||||
Some(field_rev) => cmp_cell(left_cell, right_cell, field_rev, field_type, cell_data_cache),
|
||||
}
|
||||
}
|
||||
(Some(_), None) => Ordering::Greater,
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
_ => Ordering::Equal,
|
||||
_ => default_order(),
|
||||
};
|
||||
|
||||
if cmp_order.is_ne() {
|
||||
@ -101,29 +119,26 @@ fn cmp_row(
|
||||
order
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn cmp_cell(
|
||||
_left: &CellRevision,
|
||||
_right: &CellRevision,
|
||||
_field_rev: &Arc<FieldRevision>,
|
||||
left_cell: &CellRevision,
|
||||
right_cell: &CellRevision,
|
||||
field_rev: &Arc<FieldRevision>,
|
||||
field_type: FieldType,
|
||||
cell_data_cache: &AtomicCellDataCache,
|
||||
) -> Ordering {
|
||||
let cal_order = || {
|
||||
let order = match &field_type {
|
||||
// FieldType::RichText => {
|
||||
// let left_cell = TypeCellData::try_from(left).ok()?.into();
|
||||
// let right_cell = TypeCellData::try_from(right).ok()?.into();
|
||||
// field_rev
|
||||
// .get_type_option::<RichTextTypeOptionPB>(field_rev.ty)?
|
||||
// .apply_cmp(&left_cell, &right_cell)
|
||||
// }
|
||||
// FieldType::Number => field_rev
|
||||
// .get_type_option::<NumberTypeOptionPB>(field_rev.ty)?
|
||||
// .apply_cmp(&left_cell, &right_cell),
|
||||
_ => Ordering::Equal,
|
||||
};
|
||||
Option::<Ordering>::Some(order)
|
||||
};
|
||||
match TypeOptionCellExt::new_with_cell_data_cache(field_rev.as_ref(), Some(cell_data_cache.clone()))
|
||||
.get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
None => Ordering::Less,
|
||||
Some(handler) => {
|
||||
let cal_order = || {
|
||||
let left_cell_str = TypeCellData::try_from(left_cell).ok()?.into_inner();
|
||||
let right_cell_str = TypeCellData::try_from(right_cell).ok()?.into_inner();
|
||||
let order = handler.handle_cell_compare(&left_cell_str, &right_cell_str, field_rev.as_ref());
|
||||
Option::<Ordering>::Some(order)
|
||||
};
|
||||
|
||||
cal_order().unwrap_or(Ordering::Equal)
|
||||
cal_order().unwrap_or_else(default_order)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::entities::{AlterSortParams, FieldType};
|
||||
use crate::entities::{AlterSortParams, DeleteSortParams, FieldType};
|
||||
use grid_rev_model::{FieldRevision, FieldTypeRevision};
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -37,7 +37,7 @@ impl std::convert::From<&Arc<FieldRevision>> for SortType {
|
||||
pub struct SortChangeset {
|
||||
pub(crate) insert_sort: Option<SortType>,
|
||||
pub(crate) update_sort: Option<SortType>,
|
||||
pub(crate) delete_sort: Option<SortType>,
|
||||
pub(crate) delete_sort: Option<DeletedSortType>,
|
||||
}
|
||||
|
||||
impl SortChangeset {
|
||||
@ -57,11 +57,26 @@ impl SortChangeset {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_delete(sort: SortType) -> Self {
|
||||
pub fn from_delete(deleted_sort: DeletedSortType) -> Self {
|
||||
Self {
|
||||
insert_sort: None,
|
||||
update_sort: None,
|
||||
delete_sort: Some(sort),
|
||||
delete_sort: Some(deleted_sort),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DeletedSortType {
|
||||
pub sort_type: SortType,
|
||||
pub sort_id: String,
|
||||
}
|
||||
|
||||
impl std::convert::From<DeleteSortParams> for DeletedSortType {
|
||||
fn from(params: DeleteSortParams) -> Self {
|
||||
Self {
|
||||
sort_type: params.sort_type,
|
||||
sort_id: params.sort_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
use crate::dart_notification::{send_dart_notification, GridDartNotification};
|
||||
use crate::entities::*;
|
||||
use crate::services::block_manager::GridBlockEvent;
|
||||
use crate::services::cell::AtomicCellDataCache;
|
||||
use crate::services::filter::{FilterChangeset, FilterController, FilterTaskHandler, FilterType, UpdatedFilterType};
|
||||
use crate::services::group::{
|
||||
default_group_configuration, find_group_field, make_group_controller, Group, GroupConfigurationReader,
|
||||
GroupController, MoveGroupRowContext,
|
||||
};
|
||||
use crate::services::row::GridBlockRowRevision;
|
||||
use crate::services::sort::{SortChangeset, SortController, SortTaskHandler, SortType};
|
||||
use crate::services::sort::{DeletedSortType, SortChangeset, SortController, SortTaskHandler, SortType};
|
||||
use crate::services::view_editor::changed_notifier::GridViewChangedNotifier;
|
||||
use crate::services::view_editor::trait_impl::*;
|
||||
use crate::services::view_editor::GridViewChangedReceiverRunner;
|
||||
@ -78,6 +79,7 @@ impl GridViewRevisionEditor {
|
||||
token: &str,
|
||||
view_id: String,
|
||||
delegate: Arc<dyn GridViewEditorDelegate>,
|
||||
cell_data_cache: AtomicCellDataCache,
|
||||
mut rev_manager: RevisionManager<Arc<ConnectionPool>>,
|
||||
) -> FlowyResult<Self> {
|
||||
let (notifier, _) = broadcast::channel(100);
|
||||
@ -110,12 +112,24 @@ impl GridViewRevisionEditor {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let sort_controller = make_sort_controller(&view_id, delegate.clone(), view_rev_pad.clone()).await;
|
||||
let sort_controller = make_sort_controller(
|
||||
&view_id,
|
||||
delegate.clone(),
|
||||
view_rev_pad.clone(),
|
||||
cell_data_cache.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let user_id = user_id.to_owned();
|
||||
let group_controller = Arc::new(RwLock::new(group_controller));
|
||||
let filter_controller =
|
||||
make_filter_controller(&view_id, delegate.clone(), notifier.clone(), view_rev_pad.clone()).await;
|
||||
let filter_controller = make_filter_controller(
|
||||
&view_id,
|
||||
delegate.clone(),
|
||||
notifier.clone(),
|
||||
cell_data_cache,
|
||||
view_rev_pad.clone(),
|
||||
)
|
||||
.await;
|
||||
Ok(Self {
|
||||
pad: view_rev_pad,
|
||||
user_id,
|
||||
@ -167,7 +181,7 @@ impl GridViewRevisionEditor {
|
||||
}
|
||||
|
||||
pub async fn sort_rows(&self, rows: &mut Vec<Arc<RowRevision>>) {
|
||||
self.sort_controller.read().await.sort_rows(rows)
|
||||
self.sort_controller.read().await.sort_rows(rows).await
|
||||
}
|
||||
|
||||
pub async fn filter_rows(&self, _block_id: &str, mut rows: Vec<Arc<RowRevision>>) -> Vec<Arc<RowRevision>> {
|
||||
@ -374,7 +388,7 @@ impl GridViewRevisionEditor {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn insert_view_sort(&self, params: AlterSortParams) -> FlowyResult<()> {
|
||||
pub async fn insert_view_sort(&self, params: AlterSortParams) -> FlowyResult<SortRevision> {
|
||||
let sort_type = SortType::from(¶ms);
|
||||
let is_exist = params.sort_id.is_some();
|
||||
let sort_id = match params.sort_id {
|
||||
@ -392,7 +406,7 @@ impl GridViewRevisionEditor {
|
||||
let mut sort_controller = self.sort_controller.write().await;
|
||||
let changeset = if is_exist {
|
||||
self.modify(|pad| {
|
||||
let changeset = pad.update_sort(¶ms.field_id, sort_rev)?;
|
||||
let changeset = pad.update_sort(¶ms.field_id, sort_rev.clone())?;
|
||||
Ok(changeset)
|
||||
})
|
||||
.await?;
|
||||
@ -401,7 +415,7 @@ impl GridViewRevisionEditor {
|
||||
.await
|
||||
} else {
|
||||
self.modify(|pad| {
|
||||
let changeset = pad.insert_sort(¶ms.field_id, sort_rev)?;
|
||||
let changeset = pad.insert_sort(¶ms.field_id, sort_rev.clone())?;
|
||||
Ok(changeset)
|
||||
})
|
||||
.await?;
|
||||
@ -413,18 +427,18 @@ impl GridViewRevisionEditor {
|
||||
if let Some(changeset) = changeset {
|
||||
self.notify_did_update_sort(changeset).await;
|
||||
}
|
||||
Ok(())
|
||||
Ok(sort_rev)
|
||||
}
|
||||
|
||||
pub async fn delete_view_sort(&self, params: DeleteSortParams) -> FlowyResult<()> {
|
||||
let sort_type = params.sort_type;
|
||||
let changeset = self
|
||||
.sort_controller
|
||||
.write()
|
||||
.await
|
||||
.did_receive_changes(SortChangeset::from_delete(sort_type.clone()))
|
||||
.did_receive_changes(SortChangeset::from_delete(DeletedSortType::from(params.clone())))
|
||||
.await;
|
||||
|
||||
let sort_type = params.sort_type;
|
||||
let _ = self
|
||||
.modify(|pad| {
|
||||
let changeset = pad.delete_sort(¶ms.sort_id, &sort_type.field_id, sort_type.field_type)?;
|
||||
@ -710,6 +724,7 @@ async fn make_filter_controller(
|
||||
view_id: &str,
|
||||
delegate: Arc<dyn GridViewEditorDelegate>,
|
||||
notifier: GridViewChangedNotifier,
|
||||
cell_data_cache: AtomicCellDataCache,
|
||||
pad: Arc<RwLock<GridViewRevisionPad>>,
|
||||
) -> Arc<RwLock<FilterController>> {
|
||||
let field_revs = delegate.get_field_revs(None).await;
|
||||
@ -726,6 +741,7 @@ async fn make_filter_controller(
|
||||
filter_delegate,
|
||||
task_scheduler.clone(),
|
||||
filter_revs,
|
||||
cell_data_cache,
|
||||
notifier,
|
||||
)
|
||||
.await;
|
||||
@ -741,6 +757,7 @@ async fn make_sort_controller(
|
||||
view_id: &str,
|
||||
delegate: Arc<dyn GridViewEditorDelegate>,
|
||||
pad: Arc<RwLock<GridViewRevisionPad>>,
|
||||
cell_data_cache: AtomicCellDataCache,
|
||||
) -> Arc<RwLock<SortController>> {
|
||||
let handler_id = gen_handler_id();
|
||||
let sort_delegate = GridViewSortDelegateImpl {
|
||||
@ -753,6 +770,7 @@ async fn make_sort_controller(
|
||||
&handler_id,
|
||||
sort_delegate,
|
||||
task_scheduler.clone(),
|
||||
cell_data_cache,
|
||||
)));
|
||||
task_scheduler
|
||||
.write()
|
||||
|
@ -4,6 +4,7 @@ use crate::entities::{
|
||||
};
|
||||
use crate::manager::GridUser;
|
||||
use crate::services::block_manager::GridBlockEvent;
|
||||
use crate::services::cell::AtomicCellDataCache;
|
||||
use crate::services::filter::FilterType;
|
||||
use crate::services::persistence::rev_sqlite::{
|
||||
SQLiteGridRevisionSnapshotPersistence, SQLiteGridViewRevisionPersistence,
|
||||
@ -14,7 +15,7 @@ use crate::services::view_editor::{GridViewEditorDelegate, GridViewRevisionEdito
|
||||
use flowy_database::ConnectionPool;
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_revision::{RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration};
|
||||
use grid_rev_model::{FieldRevision, FilterRevision, RowChangeset, RowRevision};
|
||||
use grid_rev_model::{FieldRevision, FilterRevision, RowChangeset, RowRevision, SortRevision};
|
||||
use lib_infra::future::Fut;
|
||||
use lib_infra::ref_map::RefCountHashMap;
|
||||
use std::borrow::Cow;
|
||||
@ -26,6 +27,7 @@ pub struct GridViewManager {
|
||||
user: Arc<dyn GridUser>,
|
||||
delegate: Arc<dyn GridViewEditorDelegate>,
|
||||
view_editors: Arc<RwLock<RefCountHashMap<Arc<GridViewRevisionEditor>>>>,
|
||||
cell_data_cache: AtomicCellDataCache,
|
||||
}
|
||||
|
||||
impl GridViewManager {
|
||||
@ -33,6 +35,7 @@ impl GridViewManager {
|
||||
grid_id: String,
|
||||
user: Arc<dyn GridUser>,
|
||||
delegate: Arc<dyn GridViewEditorDelegate>,
|
||||
cell_data_cache: AtomicCellDataCache,
|
||||
block_event_rx: broadcast::Receiver<GridBlockEvent>,
|
||||
) -> FlowyResult<Self> {
|
||||
let view_editors = Arc::new(RwLock::new(RefCountHashMap::default()));
|
||||
@ -41,6 +44,7 @@ impl GridViewManager {
|
||||
grid_id,
|
||||
user,
|
||||
delegate,
|
||||
cell_data_cache,
|
||||
view_editors,
|
||||
})
|
||||
}
|
||||
@ -145,7 +149,7 @@ impl GridViewManager {
|
||||
view_editor.delete_view_filter(params).await
|
||||
}
|
||||
|
||||
pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult<()> {
|
||||
pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult<SortRevision> {
|
||||
let view_editor = self.get_view_editor(¶ms.view_id).await?;
|
||||
view_editor.insert_view_sort(params).await
|
||||
}
|
||||
@ -250,7 +254,15 @@ impl GridViewManager {
|
||||
let token = self.user.token()?;
|
||||
let view_id = view_id.to_owned();
|
||||
|
||||
GridViewRevisionEditor::new(&user_id, &token, view_id, self.delegate.clone(), rev_manager).await
|
||||
GridViewRevisionEditor::new(
|
||||
&user_id,
|
||||
&token,
|
||||
view_id,
|
||||
self.delegate.clone(),
|
||||
self.cell_data_cache.clone(),
|
||||
rev_manager,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,11 +172,18 @@ pub(crate) struct GridViewSortDelegateImpl {
|
||||
}
|
||||
|
||||
impl SortDelegate for GridViewSortDelegateImpl {
|
||||
fn get_sort_rev(&self, sort_type: SortType) -> Fut<Vec<Arc<SortRevision>>> {
|
||||
fn get_sort_rev(&self, sort_type: SortType) -> Fut<Option<Arc<SortRevision>>> {
|
||||
let pad = self.view_revision_pad.clone();
|
||||
to_fut(async move {
|
||||
let field_type_rev: FieldTypeRevision = sort_type.field_type.into();
|
||||
pad.read().await.get_sorts(&sort_type.field_id, &field_type_rev)
|
||||
let mut sorts = pad.read().await.get_sorts(&sort_type.field_id, &field_type_rev);
|
||||
if sorts.is_empty() {
|
||||
None
|
||||
} else {
|
||||
// Currently, one sort_type should have one sort.
|
||||
debug_assert_eq!(sorts.len(), 1);
|
||||
sorts.pop()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
|
||||
use flowy_grid::services::field::{
|
||||
ChecklistTypeOptionPB, DateCellChangeset, MultiSelectTypeOptionPB, SelectOptionPB, SingleSelectTypeOptionPB,
|
||||
URLCellData,
|
||||
};
|
||||
use flowy_grid::services::row::RowRevisionBuilder;
|
||||
use grid_rev_model::{FieldRevision, RowRevision};
|
||||
@ -59,7 +60,8 @@ impl<'a> GridRowTestBuilder<'a> {
|
||||
|
||||
pub fn insert_url_cell(&mut self, data: &str) -> String {
|
||||
let url_field = self.field_rev_with_type(&FieldType::URL);
|
||||
self.inner_builder.insert_text_cell(&url_field.id, data.to_string());
|
||||
let url_data = URLCellData::new(data).to_json().unwrap();
|
||||
self.inner_builder.insert_text_cell(&url_field.id, url_data);
|
||||
url_field.id.clone()
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ async fn grid_filter_multi_select_is_test2() {
|
||||
condition: SelectOptionConditionPB::OptionIs,
|
||||
option_ids: vec![options.remove(1).id],
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected: 3 },
|
||||
AssertNumberOfVisibleRows { expected: 2 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
@ -269,7 +269,7 @@ fn make_test_grid() -> BuildGridContext {
|
||||
FieldType::Number => row_builder.insert_number_cell("2"),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::MultiSelect => row_builder
|
||||
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
|
||||
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(1)]),
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
|
@ -1,2 +1,2 @@
|
||||
// mod script;
|
||||
// mod text_sort_test;
|
||||
mod script;
|
||||
mod sort_test;
|
||||
|
@ -1,20 +1,32 @@
|
||||
use crate::grid::grid_editor::GridEditorTest;
|
||||
use flowy_grid::entities::{AlterSortParams, DeleteSortParams};
|
||||
use flowy_grid::entities::{AlterSortParams, CellPathParams, DeleteSortParams};
|
||||
use grid_rev_model::SortRevision;
|
||||
|
||||
pub enum SortScript {
|
||||
InsertSort { params: AlterSortParams },
|
||||
DeleteSort { params: DeleteSortParams },
|
||||
AssertTextOrder { orders: Vec<String> },
|
||||
InsertSort {
|
||||
params: AlterSortParams,
|
||||
},
|
||||
DeleteSort {
|
||||
params: DeleteSortParams,
|
||||
},
|
||||
AssertTextOrder {
|
||||
field_id: String,
|
||||
orders: Vec<&'static str>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct GridSortTest {
|
||||
inner: GridEditorTest,
|
||||
pub current_sort_rev: Option<SortRevision>,
|
||||
}
|
||||
|
||||
impl GridSortTest {
|
||||
pub async fn new() -> Self {
|
||||
let editor_test = GridEditorTest::new_table().await;
|
||||
Self { inner: editor_test }
|
||||
Self {
|
||||
inner: editor_test,
|
||||
current_sort_rev: None,
|
||||
}
|
||||
}
|
||||
pub async fn run_scripts(&mut self, scripts: Vec<SortScript>) {
|
||||
for script in scripts {
|
||||
@ -25,14 +37,26 @@ impl GridSortTest {
|
||||
pub async fn run_script(&mut self, script: SortScript) {
|
||||
match script {
|
||||
SortScript::InsertSort { params } => {
|
||||
let _ = self.editor.create_or_update_sort(params).await.unwrap();
|
||||
let sort_rev = self.editor.create_or_update_sort(params).await.unwrap();
|
||||
self.current_sort_rev = Some(sort_rev);
|
||||
}
|
||||
SortScript::DeleteSort { params } => {
|
||||
//
|
||||
self.editor.delete_sort(params).await.unwrap();
|
||||
}
|
||||
SortScript::AssertTextOrder { orders: _ } => {
|
||||
//
|
||||
SortScript::AssertTextOrder { field_id, orders } => {
|
||||
let mut cells = vec![];
|
||||
let rows = self.editor.get_grid(&self.grid_id).await.unwrap().rows;
|
||||
for row in rows {
|
||||
let params = CellPathParams {
|
||||
view_id: self.grid_id.clone(),
|
||||
field_id: field_id.clone(),
|
||||
row_id: row.id,
|
||||
};
|
||||
let cell = self.editor.get_cell_display_str(¶ms).await;
|
||||
cells.push(cell);
|
||||
}
|
||||
assert_eq!(cells, orders)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
279
frontend/rust-lib/flowy-grid/tests/grid/sort_test/sort_test.rs
Normal file
279
frontend/rust-lib/flowy-grid/tests/grid/sort_test/sort_test.rs
Normal 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;
|
||||
}
|
@ -32,3 +32,9 @@ impl std::default::Default for SortCondition {
|
||||
Self::Ascending
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<SortCondition> for u8 {
|
||||
fn from(condition: SortCondition) -> Self {
|
||||
condition as u8
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user