Refactor/type option (#1578)

This commit is contained in:
Nathan.fooo 2022-12-17 10:15:13 +08:00 committed by GitHub
parent d0a914c10f
commit 85e489babb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 977 additions and 627 deletions

View File

@ -1,11 +1,11 @@
use crate::entities::*;
use crate::manager::GridManager;
use crate::services::cell::TypeCellData;
use crate::services::cell::{FromCellString, TypeCellData};
use crate::services::field::{
default_type_option_builder_from_type, select_type_option_from_field_rev, type_option_builder_from_json_str,
DateCellChangeset, DateChangesetPB, SelectOptionCellChangeset, SelectOptionCellChangesetPB,
SelectOptionCellChangesetParams, SelectOptionCellDataPB, SelectOptionChangeset, SelectOptionChangesetPB,
SelectOptionPB,
SelectOptionIds, SelectOptionPB,
};
use crate::services::row::make_row_from_row_rev;
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
@ -418,7 +418,8 @@ pub(crate) async fn get_select_option_handler(
},
Some(cell_rev) => cell_rev.try_into()?,
};
let selected_options = type_option.get_selected_options(type_cell_data.into());
let ids = SelectOptionIds::from_cell_str(&type_cell_data.data)?;
let selected_options = type_option.get_selected_options(ids);
data_result(selected_options)
}
}

View File

@ -1,3 +1,5 @@
extern crate core;
#[macro_use]
mod macros;

View File

@ -1,113 +1,66 @@
use crate::entities::FieldType;
use crate::services::cell::{CellBytes, TypeCellData};
use crate::services::cell::{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;
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use grid_rev_model::{CellRevision, FieldRevision, FieldTypeRevision};
/// This trait is used when doing filter/search on the grid.
pub trait CellFilterable<T> {
pub trait CellFilterable: TypeOptionConfiguration {
/// Return true if type_cell_data match the filter condition.
fn apply_filter(&self, type_cell_data: TypeCellData, filter: &T) -> FlowyResult<bool>;
fn apply_filter(
&self,
type_cell_data: TypeCellData,
filter: &<Self as TypeOptionConfiguration>::CellFilterConfiguration,
) -> FlowyResult<bool>;
}
pub trait CellComparable {
fn apply_cmp(&self, type_cell_data: &TypeCellData, other_type_cell_data: &TypeCellData) -> FlowyResult<Ordering>;
type CellData;
fn apply_cmp(&self, cell_data: &Self::CellData, other_cell_data: &Self::CellData) -> Ordering;
}
/// Serialize the cell data in Protobuf/String format.
///
/// Each cell data is a opaque data, it needs to deserialized to a concrete data struct.
/// Essentially when the field type is SingleSelect/Multi-Select, the cell data contains a
/// list of option ids. So it need to be decoded including convert the option's id to
/// option's name
///
pub trait CellDataSerialize<CD> {
/// Serialize the cell data into `CellBytes` that will be posted to the `Dart` side. Using the
/// corresponding protobuf struct implemented in `Dart` to deserialize the data.
/// Decode the opaque cell data into readable format content
pub trait CellDataDecoder: TypeOption {
///
/// Using `utf8` to encode the cell data if the cell data use `String` as its data container.
/// Using `protobuf` to encode the cell data if the cell data use `Protobuf struct` as its data container.
///
/// When switching the field type of the `FieldRevision` to another field type. The `field_type`
/// of the `FieldRevision` is not equal to the `decoded_field_type`. The cell data is need to do
/// some custom transformation.
/// Tries to decode the opaque cell data to `decoded_field_type`. Sometimes, the `field_type`
/// of the `FieldRevision` is not equal to the `decoded_field_type`(This happened When switching
/// the field type of the `FieldRevision` to another field type). So the cell data is need to do
/// some transformation.
///
/// For example, the current field type of the `FieldRevision` is a checkbox. When switching the field
/// type from the checkbox to single select, the `TypeOptionBuilder`'s transform method gets called.
/// It will create two new options,`Yes` and `No`, if they don't exist. But the cell data didn't change,
/// because we can't iterate all the rows to transform the cell data that can be parsed by the current
/// field type. One approach is to transform the cell data when it get read. For the moment,
/// the cell data is a string, `Yes` or `No`. It needs to compare with the option's name, if match
/// return the id of the option. Otherwise, return a default value of `CellBytes`.
///
/// # Arguments
///
/// * `cell_data`: the generic annotation `CD` represents as the deserialize data type of the cell.
/// * `decoded_field_type`: the field type of the cell_data when doing serialization
///
/// returns: Result<CellBytes, FlowyError>
///
fn serialize_cell_data_to_bytes(
/// type from the checkbox to single select, it will create two new options,`Yes` and `No`, if they don't exist.
/// But the data of the cell doesn't change. We can't iterate all the rows to transform the cell
/// data that can be parsed by the current field type. One approach is to transform the cell data
/// when it get read. For the moment, the cell data is a string, `Yes` or `No`. It needs to compare
/// with the option's name, if match return the id of the option.
fn try_decode_cell_data(
&self,
cell_data: IntoCellData<CD>,
cell_data: String,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellBytes>;
) -> FlowyResult<<Self as TypeOption>::CellData>;
/// Serialize the cell data into `String` that is readable
///
/// The cell data is not readable which means it can't display the cell data directly to user.
/// For example,
/// 1. the cell data is timestamp if its field type is FieldType::Date that is not readable.
/// So it needs to be parsed as the date string with custom format setting.
///
/// 2. the cell data is a commas separated id if its field type if FieldType::MultiSelect that is not readable.
/// So it needs to be parsed as a commas separated option name.
///
fn serialize_cell_data_to_str(
/// Same as `decode_cell_data` does but Decode the cell data to readable `String`
fn decode_cell_data_to_str(
&self,
cell_data: IntoCellData<CD>,
cell_data: String,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<String>;
}
pub trait CellDataOperation<CD, CS> {
/// The generic annotation `CD` represents as the deserialize data type of the cell data.
/// The Serialize/Deserialize struct of the cell is base on the field type of the cell.
///
/// For example:
/// FieldType::URL => URLCellData
/// FieldType::Date=> DateCellData
///
/// Each cell data is a opaque data, it needs to deserialized to a concrete data struct.
/// Essentially when the field type is SingleSelect/Multi-Select, the cell data contains a
/// list of option ids. So it need to be decoded including convert the option's id to
/// option's name
///
/// `cell_data`: the opaque data of the cell.
/// `decoded_field_type`: the field type of the cell data when doing serialization
/// `field_rev`: the field of the cell data
///
/// Returns the error if the cell data can't be parsed into `CD`.
///
fn decode_cell_data(
&self,
cell_data: IntoCellData<CD>,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellBytes>;
/// The changeset is able to parse into the concrete data struct if CS implements
/// the `FromCellChangeset` trait.
///
/// For example:
/// SelectOptionCellChangeset,DateCellChangeset. etc.
pub trait CellDataChangeset: TypeOption {
/// The changeset is able to parse into the concrete data struct if `TypeOption::CellChangeset`
/// implements the `FromCellChangeset` trait.
/// For example,the SelectOptionCellChangeset,DateCellChangeset. etc.
///
fn apply_changeset(&self, changeset: AnyCellChangeset<CS>, cell_rev: Option<CellRevision>) -> FlowyResult<String>;
fn apply_changeset(
&self,
changeset: AnyCellChangeset<<Self as TypeOption>::CellChangeset>,
cell_rev: Option<CellRevision>,
) -> FlowyResult<String>;
}
/// changeset: It will be deserialized into specific data base on the FieldType.
@ -143,16 +96,16 @@ 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,
) -> (FieldType, CellBytes) {
) -> (FieldType, CellProtobufBlob) {
let to_field_type = field_rev.ty.into();
match data.try_into() {
Ok(type_cell_data) => {
let TypeCellData { data, field_type } = type_cell_data;
match try_decode_cell_data(data.into(), &field_type, &to_field_type, field_rev) {
match try_decode_cell_data(data, &field_type, &to_field_type, field_rev) {
Ok(cell_bytes) => (field_type, cell_bytes),
Err(e) => {
tracing::error!("Decode cell data failed, {:?}", e);
(field_type, CellBytes::default())
(field_type, CellProtobufBlob::default())
}
}
}
@ -161,109 +114,43 @@ pub fn decode_type_cell_data<T: TryInto<TypeCellData, Error = FlowyError> + Debu
// display the existing cell data. For example, the UI of the text cell will be blank if
// the type of the data of cell is Number.
(to_field_type, CellBytes::default())
(to_field_type, CellProtobufBlob::default())
}
}
}
pub fn decode_cell_data_to_string<C: Into<IntoCellData<String>>>(
cell_data: C,
from_field_type: &FieldType,
to_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<String> {
let cell_data = cell_data.into().try_into_inner()?;
let get_cell_display_str = || {
let field_type: FieldTypeRevision = to_field_type.into();
let result = match to_field_type {
FieldType::RichText => field_rev
.get_type_option::<RichTextTypeOptionPB>(field_type)?
.serialize_cell_data_to_str(cell_data.into(), from_field_type, field_rev),
FieldType::Number => field_rev
.get_type_option::<NumberTypeOptionPB>(field_type)?
.serialize_cell_data_to_str(cell_data.into(), from_field_type, field_rev),
FieldType::DateTime => field_rev
.get_type_option::<DateTypeOptionPB>(field_type)?
.serialize_cell_data_to_str(cell_data.into(), from_field_type, field_rev),
FieldType::SingleSelect => field_rev
.get_type_option::<SingleSelectTypeOptionPB>(field_type)?
.serialize_cell_data_to_str(cell_data.into(), from_field_type, field_rev),
FieldType::MultiSelect => field_rev
.get_type_option::<MultiSelectTypeOptionPB>(field_type)?
.serialize_cell_data_to_str(cell_data.into(), from_field_type, field_rev),
FieldType::Checklist => field_rev
.get_type_option::<ChecklistTypeOptionPB>(field_type)?
.serialize_cell_data_to_str(cell_data.into(), from_field_type, field_rev),
FieldType::Checkbox => field_rev
.get_type_option::<CheckboxTypeOptionPB>(field_type)?
.serialize_cell_data_to_str(cell_data.into(), from_field_type, field_rev),
FieldType::URL => field_rev
.get_type_option::<URLTypeOptionPB>(field_type)?
.serialize_cell_data_to_str(cell_data.into(), from_field_type, field_rev),
};
Some(result)
};
match get_cell_display_str() {
Some(Ok(s)) => Ok(s),
Some(Err(err)) => {
tracing::error!("{:?}", err);
Ok("".to_owned())
}
None => Ok("".to_owned()),
}
}
/// Use the `to_field_type`'s TypeOption to parse the cell data into `from_field_type` type's data.
/// Decode the opaque cell data from one field type to another using the corresponding type option builder
///
/// Each `FieldType` has its corresponding `TypeOption` that implements the `CellDisplayable`
/// and `CellDataOperation` traits.
/// The cell data might become an empty string depends on these two fields' `TypeOptionBuilder`
/// support transform or not.
///
/// # Arguments
///
/// * `cell_data`: the opaque cell data
/// * `from_field_type`: the original field type of the passed-in cell data. Check the `TypeCellData`
/// that is used to save the origin field type of the cell data.
/// * `to_field_type`: decode the passed-in cell data to this field type. It will use the to_field_type's
/// TypeOption to decode this cell data.
/// * `field_rev`: used to get the corresponding TypeOption for the specified field type.
///
/// returns: CellBytes
///
pub fn try_decode_cell_data(
cell_data: IntoCellData<String>,
cell_data: String,
from_field_type: &FieldType,
to_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
let cell_data = cell_data.try_into_inner()?;
let get_cell_data = || {
let field_type: FieldTypeRevision = to_field_type.into();
let data = match to_field_type {
FieldType::RichText => field_rev
.get_type_option::<RichTextTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), from_field_type, field_rev),
FieldType::Number => field_rev
.get_type_option::<NumberTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), from_field_type, field_rev),
FieldType::DateTime => field_rev
.get_type_option::<DateTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), from_field_type, field_rev),
FieldType::SingleSelect => field_rev
.get_type_option::<SingleSelectTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), from_field_type, field_rev),
FieldType::MultiSelect => field_rev
.get_type_option::<MultiSelectTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), from_field_type, field_rev),
FieldType::Checklist => field_rev
.get_type_option::<ChecklistTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), from_field_type, field_rev),
FieldType::Checkbox => field_rev
.get_type_option::<CheckboxTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), from_field_type, field_rev),
FieldType::URL => field_rev
.get_type_option::<URLTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), from_field_type, field_rev),
};
Some(data)
};
) -> FlowyResult<CellProtobufBlob> {
match FieldRevisionExt::new(field_rev).get_type_option_handler(to_field_type) {
None => Ok(CellProtobufBlob::default()),
Some(handler) => handler.handle_cell_data(cell_data, from_field_type, field_rev),
}
}
match get_cell_data() {
Some(Ok(data)) => Ok(data),
Some(Err(err)) => {
tracing::error!("{:?}", err);
Ok(CellBytes::default())
}
None => Ok(CellBytes::default()),
pub fn stringify_cell_data(cell_data: String, field_type: &FieldType, field_rev: &FieldRevision) -> String {
match FieldRevisionExt::new(field_rev).get_type_option_handler(field_type) {
None => "".to_string(),
Some(handler) => handler.stringify_cell_data(cell_data, field_type, field_rev),
}
}
@ -322,7 +209,8 @@ pub trait FromCellString {
Self: Sized;
}
/// IntoCellData is a helper struct. String will be parser into Option<T> only if the T impl the FromCellString trait.
/// IntoCellData is a helper struct used to deserialize string into a specific data type that implements
/// the `FromCellString` trait.
///
pub struct IntoCellData<T>(pub Option<T>);
impl<T> IntoCellData<T> {
@ -349,18 +237,18 @@ where
}
}
impl std::convert::From<usize> for IntoCellData<String> {
fn from(n: usize) -> Self {
IntoCellData(Some(n.to_string()))
}
}
impl<T> std::convert::From<T> for IntoCellData<T> {
fn from(val: T) -> Self {
IntoCellData(Some(val))
}
}
impl std::convert::From<usize> for IntoCellData<String> {
fn from(n: usize) -> Self {
IntoCellData(Some(n.to_string()))
}
}
impl std::convert::From<IntoCellData<String>> for String {
fn from(p: IntoCellData<String>) -> Self {
p.try_into_inner().unwrap_or_else(|_| String::new())

View File

@ -4,11 +4,16 @@ use bytes::Bytes;
use flowy_error::{internal_error, FlowyError, FlowyResult};
use grid_rev_model::CellRevision;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
/// TypeCellData is a generic CellData, you can parse the type_cell_data according to the field_type.
/// When the type of field is changed, it's different from the field_type of TypeCellData.
/// So it will return an empty data. You could check the CellDataOperation trait for more information.
/// The `data` is encoded by JSON format. You can use `IntoCellData` to decode the opaque data to
/// concrete cell type.
/// TypeCellData -> IntoCellData<T> -> T
///
/// The `TypeCellData` is the same as the cell data that was saved to disk except it carries the
/// field_type. The field_type indicates the cell data original `FieldType`. The field_type will
/// be changed if the current Field's type switch from one to another.
///
#[derive(Debug, Serialize, Deserialize)]
pub struct TypeCellData {
pub data: String,
@ -22,41 +27,25 @@ impl TypeCellData {
field_type: field_type.clone(),
}
}
}
impl std::str::FromStr for TypeCellData {
type Err = FlowyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let type_option_cell_data: TypeCellData = serde_json::from_str(s).map_err(|err| {
let msg = format!("Deserialize {} to any cell data failed. Serde error: {}", s, err);
pub fn from_json_str(s: &str) -> FlowyResult<Self> {
let type_cell_data: TypeCellData = serde_json::from_str(s).map_err(|err| {
let msg = format!("Deserialize {} to any cell data failed.{}", s, err);
FlowyError::internal().context(msg)
})?;
Ok(type_option_cell_data)
Ok(type_cell_data)
}
pub fn into_inner(self) -> String {
self.data
}
}
impl std::convert::TryInto<TypeCellData> for String {
impl std::convert::TryFrom<String> for TypeCellData {
type Error = FlowyError;
fn try_into(self) -> Result<TypeCellData, Self::Error> {
TypeCellData::from_str(&self)
}
}
impl std::convert::TryFrom<&CellRevision> for TypeCellData {
type Error = FlowyError;
fn try_from(value: &CellRevision) -> Result<Self, Self::Error> {
Self::from_str(&value.data)
}
}
impl std::convert::TryFrom<CellRevision> for TypeCellData {
type Error = FlowyError;
fn try_from(value: CellRevision) -> Result<Self, Self::Error> {
Self::try_from(&value)
fn try_from(value: String) -> Result<Self, Self::Error> {
TypeCellData::from_json_str(&value)
}
}
@ -69,6 +58,28 @@ where
}
}
impl ToString for TypeCellData {
fn to_string(&self) -> String {
self.data.clone()
}
}
impl std::convert::TryFrom<&CellRevision> for TypeCellData {
type Error = FlowyError;
fn try_from(value: &CellRevision) -> Result<Self, Self::Error> {
Self::from_json_str(&value.data)
}
}
impl std::convert::TryFrom<CellRevision> for TypeCellData {
type Error = FlowyError;
fn try_from(value: CellRevision) -> Result<Self, Self::Error> {
Self::try_from(&value)
}
}
impl TypeCellData {
pub fn new(content: String, field_type: FieldType) -> Self {
TypeCellData {
@ -104,6 +115,7 @@ impl TypeCellData {
pub fn is_multi_select(&self) -> bool {
self.field_type == FieldType::MultiSelect
}
pub fn is_checklist(&self) -> bool {
self.field_type == FieldType::Checklist
}
@ -121,28 +133,34 @@ impl TypeCellData {
///
/// For example:
///
/// * Use DateCellData to parse the data when the FieldType is Date.
/// * Use URLCellData to parse the data when the FieldType is URL.
/// * Use DateCellDataPB to parse the data when the FieldType is Date.
/// * Use URLCellDataPB to parse the data when the FieldType is URL.
/// * Use String to parse the data when the FieldType is RichText, Number, or Checkbox.
/// * Check out the implementation of CellDataOperation trait for more information.
#[derive(Default, Debug)]
pub struct CellBytes(pub Bytes);
pub struct CellProtobufBlob(pub Bytes);
pub trait CellDataIsEmpty {
pub trait DecodedCellData {
type Object;
fn is_empty(&self) -> bool;
}
pub trait CellBytesParser {
type Object: CellDataIsEmpty;
pub trait CellProtobufBlobParser {
type Object: DecodedCellData;
fn parser(bytes: &Bytes) -> FlowyResult<Self::Object>;
}
pub trait CellStringParser {
type Object;
fn parser_cell_str(&self, s: &str) -> Option<Self::Object>;
}
pub trait CellBytesCustomParser {
type Object;
fn parse(&self, bytes: &Bytes) -> FlowyResult<Self::Object>;
}
impl CellBytes {
impl CellProtobufBlob {
pub fn new<T: AsRef<[u8]>>(data: T) -> Self {
let bytes = Bytes::from(data.as_ref().to_vec());
Self(bytes)
@ -158,7 +176,7 @@ impl CellBytes {
pub fn parser<P>(&self) -> FlowyResult<P::Object>
where
P: CellBytesParser,
P: CellProtobufBlobParser,
{
P::parser(&self.0)
}
@ -178,7 +196,7 @@ impl CellBytes {
// }
}
impl ToString for CellBytes {
impl ToString for CellProtobufBlob {
fn to_string(&self) -> String {
match String::from_utf8(self.0.to_vec()) {
Ok(s) => s,
@ -190,7 +208,7 @@ impl ToString for CellBytes {
}
}
impl std::ops::Deref for CellBytes {
impl std::ops::Deref for CellProtobufBlob {
type Target = Bytes;
fn deref(&self) -> &Self::Target {

View File

@ -1,6 +1,6 @@
use crate::entities::{CheckboxFilterConditionPB, CheckboxFilterPB};
use crate::services::cell::{CellFilterable, IntoCellData, TypeCellData};
use crate::services::field::{CheckboxCellData, CheckboxTypeOptionPB};
use crate::services::cell::{CellFilterable, TypeCellData};
use crate::services::field::{CheckboxCellData, CheckboxTypeOptionPB, TypeOptionCellData, TypeOptionConfiguration};
use flowy_error::FlowyResult;
impl CheckboxFilterPB {
@ -13,13 +13,16 @@ impl CheckboxFilterPB {
}
}
impl CellFilterable<CheckboxFilterPB> for CheckboxTypeOptionPB {
fn apply_filter(&self, type_cell_data: TypeCellData, filter: &CheckboxFilterPB) -> FlowyResult<bool> {
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 cell_data: IntoCellData<CheckboxCellData> = type_cell_data.into();
let checkbox_cell_data = cell_data.try_into_inner()?;
let checkbox_cell_data = self.decode_type_option_cell_data(type_cell_data.data)?;
Ok(filter.is_visible(&checkbox_cell_data))
}
}

View File

@ -1,9 +1,10 @@
#[cfg(test)]
mod tests {
use crate::entities::FieldType;
use crate::services::cell::CellDataOperation;
use crate::services::cell::CellDataDecoder;
use crate::services::field::type_options::checkbox_type_option::*;
use crate::services::field::FieldBuilder;
use grid_rev_model::FieldRevision;
#[test]
@ -35,7 +36,7 @@ mod tests {
) {
assert_eq!(
type_option
.decode_cell_data(input_str.to_owned().into(), field_type, field_rev)
.try_decode_cell_data(input_str.to_owned(), field_type, field_rev)
.unwrap()
.to_string(),
expected_str.to_owned()

View File

@ -1,7 +1,9 @@
use crate::entities::FieldType;
use crate::entities::{CheckboxFilterPB, FieldType};
use crate::impl_type_option;
use crate::services::cell::{AnyCellChangeset, CellBytes, CellDataOperation, CellDataSerialize, IntoCellData};
use crate::services::field::{BoxTypeOptionBuilder, CheckboxCellData, TypeOptionBuilder};
use crate::services::cell::{AnyCellChangeset, CellDataChangeset, CellDataDecoder, FromCellString};
use crate::services::field::{
BoxTypeOptionBuilder, CheckboxCellData, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
};
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
@ -42,47 +44,56 @@ pub struct CheckboxTypeOptionPB {
}
impl_type_option!(CheckboxTypeOptionPB, FieldType::Checkbox);
impl CellDataSerialize<CheckboxCellData> for CheckboxTypeOptionPB {
fn serialize_cell_data_to_bytes(
&self,
cell_data: IntoCellData<CheckboxCellData>,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
let cell_data = cell_data.try_into_inner()?;
Ok(CellBytes::new(cell_data))
impl TypeOption for CheckboxTypeOptionPB {
type CellData = CheckboxCellData;
type CellChangeset = CheckboxCellChangeset;
type CellPBType = CheckboxCellData;
}
impl TypeOptionConfiguration for CheckboxTypeOptionPB {
type CellFilterConfiguration = CheckboxFilterPB;
}
impl TypeOptionCellData for CheckboxTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType {
cell_data
}
fn serialize_cell_data_to_str(
fn decode_type_option_cell_data(&self, cell_data: String) -> FlowyResult<<Self as TypeOption>::CellData> {
CheckboxCellData::from_cell_str(&cell_data)
}
}
impl CellDataDecoder for CheckboxTypeOptionPB {
fn try_decode_cell_data(
&self,
cell_data: IntoCellData<CheckboxCellData>,
cell_data: String,
decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<<Self as TypeOption>::CellData> {
if !decoded_field_type.is_checkbox() {
return Ok(Default::default());
}
self.decode_type_option_cell_data(cell_data)
}
fn decode_cell_data_to_str(
&self,
cell_data: String,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<String> {
let cell_data = cell_data.try_into_inner()?;
Ok(cell_data.to_string())
Ok(cell_data)
}
}
pub type CheckboxCellChangeset = String;
impl CellDataOperation<CheckboxCellData, CheckboxCellChangeset> for CheckboxTypeOptionPB {
fn decode_cell_data(
&self,
cell_data: IntoCellData<CheckboxCellData>,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
if !decoded_field_type.is_checkbox() {
return Ok(CellBytes::default());
}
self.serialize_cell_data_to_bytes(cell_data, decoded_field_type, field_rev)
}
impl CellDataChangeset for CheckboxTypeOptionPB {
fn apply_changeset(
&self,
changeset: AnyCellChangeset<String>,
changeset: AnyCellChangeset<CheckboxCellChangeset>,
_cell_rev: Option<CellRevision>,
) -> Result<String, FlowyError> {
let changeset = changeset.try_into_inner()?;

View File

@ -1,11 +1,13 @@
use crate::services::cell::{CellBytesParser, CellDataIsEmpty, FromCellString};
use crate::services::cell::{CellProtobufBlobParser, DecodedCellData, FromCellString};
use bytes::Bytes;
use flowy_error::{FlowyError, FlowyResult};
use protobuf::ProtobufError;
use std::str::FromStr;
pub const CHECK: &str = "Yes";
pub const UNCHECK: &str = "No";
#[derive(Default, Debug)]
pub struct CheckboxCellData(String);
impl CheckboxCellData {
@ -47,6 +49,14 @@ impl FromStr for CheckboxCellData {
}
}
impl std::convert::TryFrom<CheckboxCellData> for Bytes {
type Error = ProtobufError;
fn try_from(value: CheckboxCellData) -> Result<Self, Self::Error> {
Ok(Bytes::from(value.0))
}
}
impl FromCellString for CheckboxCellData {
fn from_cell_str(s: &str) -> FlowyResult<Self>
where
@ -62,14 +72,16 @@ impl ToString for CheckboxCellData {
}
}
impl CellDataIsEmpty for CheckboxCellData {
impl DecodedCellData for CheckboxCellData {
type Object = CheckboxCellData;
fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
pub struct CheckboxCellDataParser();
impl CellBytesParser for CheckboxCellDataParser {
impl CellProtobufBlobParser for CheckboxCellDataParser {
type Object = CheckboxCellData;
fn parser(bytes: &Bytes) -> FlowyResult<Self::Object> {
match String::from_utf8(bytes.to_vec()) {

View File

@ -1,7 +1,8 @@
use crate::entities::{DateFilterConditionPB, DateFilterPB};
use crate::services::cell::{CellFilterable, IntoCellData, TypeCellData};
use crate::services::field::{DateTimestamp, DateTypeOptionPB};
use crate::services::cell::{CellFilterable, TypeCellData};
use crate::services::field::{DateTypeOptionPB, TypeOptionCellData, TypeOptionConfiguration};
use chrono::NaiveDateTime;
use flowy_error::FlowyResult;
impl DateFilterPB {
@ -59,14 +60,18 @@ impl DateFilterPB {
}
}
impl CellFilterable<DateFilterPB> for DateTypeOptionPB {
fn apply_filter(&self, type_cell_data: TypeCellData, filter: &DateFilterPB) -> FlowyResult<bool> {
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 cell_data: IntoCellData<DateTimestamp> = type_cell_data.into();
let timestamp = cell_data.try_into_inner()?;
Ok(filter.is_visible(timestamp))
let date_cell_data = self.decode_type_option_cell_data(type_cell_data.data)?;
Ok(filter.is_visible(date_cell_data))
}
}

View File

@ -1,7 +1,8 @@
#[cfg(test)]
mod tests {
use crate::entities::FieldType;
use crate::services::cell::CellDataOperation;
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
use crate::services::field::*;
// use crate::services::field::{DateCellChangeset, DateCellData, DateFormat, DateTypeOptionPB, TimeFormat};
use chrono::format::strftime::StrftimeItems;
@ -162,11 +163,9 @@ mod tests {
fn decode_cell_data(encoded_data: String, type_option: &DateTypeOptionPB, field_rev: &FieldRevision) -> String {
let decoded_data = type_option
.decode_cell_data(encoded_data.into(), &FieldType::DateTime, field_rev)
.unwrap()
.parser::<DateCellDataParser>()
.try_decode_cell_data(encoded_data, &FieldType::DateTime, field_rev)
.unwrap();
let decoded_data = type_option.convert_into_pb_type(decoded_data);
if type_option.include_time {
format!("{} {}", decoded_data.date, decoded_data.time)
.trim_end()

View File

@ -1,8 +1,9 @@
use crate::entities::FieldType;
use crate::entities::{DateFilterPB, FieldType};
use crate::impl_type_option;
use crate::services::cell::{AnyCellChangeset, CellBytes, CellDataOperation, CellDataSerialize, IntoCellData};
use crate::services::cell::{AnyCellChangeset, CellDataChangeset, CellDataDecoder, FromCellString};
use crate::services::field::{
BoxTypeOptionBuilder, DateCellChangeset, DateCellDataPB, DateFormat, DateTimestamp, TimeFormat, TypeOptionBuilder,
BoxTypeOptionBuilder, DateCellChangeset, DateCellData, DateCellDataPB, DateFormat, TimeFormat, TypeOption,
TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
};
use bytes::Bytes;
use chrono::format::strftime::StrftimeItems;
@ -26,6 +27,26 @@ pub struct DateTypeOptionPB {
}
impl_type_option!(DateTypeOptionPB, FieldType::DateTime);
impl TypeOption for DateTypeOptionPB {
type CellData = DateCellData;
type CellChangeset = DateCellChangeset;
type CellPBType = DateCellDataPB;
}
impl TypeOptionConfiguration for DateTypeOptionPB {
type CellFilterConfiguration = DateFilterPB;
}
impl TypeOptionCellData for DateTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType {
self.today_desc_from_timestamp(cell_data)
}
fn decode_type_option_cell_data(&self, cell_data: String) -> FlowyResult<<Self as TypeOption>::CellData> {
DateCellData::from_cell_str(&cell_data)
}
}
impl DateTypeOptionPB {
#[allow(dead_code)]
pub fn new() -> Self {
@ -107,47 +128,37 @@ impl DateTypeOptionPB {
}
}
impl CellDataSerialize<DateTimestamp> for DateTypeOptionPB {
fn serialize_cell_data_to_bytes(
impl CellDataDecoder for DateTypeOptionPB {
fn try_decode_cell_data(
&self,
cell_data: IntoCellData<DateTimestamp>,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
let timestamp = cell_data.try_into_inner()?;
let cell_data_pb = self.today_desc_from_timestamp(timestamp);
CellBytes::from(cell_data_pb)
}
fn serialize_cell_data_to_str(
&self,
cell_data: IntoCellData<DateTimestamp>,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<String> {
let timestamp = cell_data.try_into_inner()?;
let date_cell_data = self.today_desc_from_timestamp(timestamp);
Ok(date_cell_data.date)
}
}
impl CellDataOperation<DateTimestamp, DateCellChangeset> for DateTypeOptionPB {
fn decode_cell_data(
&self,
cell_data: IntoCellData<DateTimestamp>,
cell_data: String,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
_field_rev: &FieldRevision,
) -> FlowyResult<<Self as TypeOption>::CellData> {
// Return default data if the type_option_cell_data is not FieldType::DateTime.
// It happens when switching from one field to another.
// For example:
// FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen.
if !decoded_field_type.is_date() {
return Ok(CellBytes::default());
return Ok(Default::default());
}
self.serialize_cell_data_to_bytes(cell_data, decoded_field_type, field_rev)
self.decode_type_option_cell_data(cell_data)
}
fn decode_cell_data_to_str(
&self,
cell_data: String,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<String> {
let cell_data = self.decode_type_option_cell_data(cell_data)?;
let cell_data_pb = self.today_desc_from_timestamp(cell_data);
Ok(cell_data_pb.date)
}
}
impl CellDataChangeset for DateTypeOptionPB {
fn apply_changeset(
&self,
changeset: AnyCellChangeset<DateCellChangeset>,

View File

@ -1,5 +1,5 @@
use crate::entities::CellPathPB;
use crate::services::cell::{CellBytesParser, CellDataIsEmpty, FromCellChangeset, FromCellString};
use crate::services::cell::{CellProtobufBlobParser, DecodedCellData, FromCellChangeset, FromCellString};
use bytes::Bytes;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::{internal_error, FlowyResult};
@ -68,27 +68,28 @@ impl ToString for DateCellChangeset {
}
}
pub struct DateTimestamp(Option<i64>);
#[derive(Default)]
pub struct DateCellData(pub Option<i64>);
impl std::convert::From<DateTimestamp> for i64 {
fn from(timestamp: DateTimestamp) -> Self {
impl std::convert::From<DateCellData> for i64 {
fn from(timestamp: DateCellData) -> Self {
timestamp.0.unwrap_or(0)
}
}
impl std::convert::From<DateTimestamp> for Option<i64> {
fn from(timestamp: DateTimestamp) -> Self {
impl std::convert::From<DateCellData> for Option<i64> {
fn from(timestamp: DateCellData) -> Self {
timestamp.0
}
}
impl FromCellString for DateTimestamp {
impl FromCellString for DateCellData {
fn from_cell_str(s: &str) -> FlowyResult<Self>
where
Self: Sized,
{
let num = s.parse::<i64>().ok();
Ok(DateTimestamp(num))
Ok(DateCellData(num))
}
}
@ -174,14 +175,16 @@ impl std::default::Default for TimeFormat {
}
}
impl CellDataIsEmpty for DateCellDataPB {
impl DecodedCellData for DateCellDataPB {
type Object = DateCellDataPB;
fn is_empty(&self) -> bool {
self.date.is_empty()
}
}
pub struct DateCellDataParser();
impl CellBytesParser for DateCellDataParser {
impl CellProtobufBlobParser for DateCellDataParser {
type Object = DateCellDataPB;
fn parser(bytes: &Bytes) -> FlowyResult<Self::Object> {

View File

@ -3,12 +3,13 @@ pub mod date_type_option;
pub mod number_type_option;
pub mod selection_type_option;
pub mod text_type_option;
mod type_option;
pub mod url_type_option;
mod util;
pub use checkbox_type_option::*;
pub use date_type_option::*;
pub use number_type_option::*;
pub use selection_type_option::*;
pub use text_type_option::*;
pub use type_option::*;
pub use url_type_option::*;

View File

@ -1,6 +1,6 @@
use crate::entities::{NumberFilterConditionPB, NumberFilterPB};
use crate::services::cell::{CellFilterable, TypeCellData};
use crate::services::field::{NumberCellData, NumberTypeOptionPB};
use crate::services::field::{NumberCellData, NumberTypeOptionPB, TypeOptionConfiguration};
use flowy_error::FlowyResult;
use rust_decimal::prelude::Zero;
use rust_decimal::Decimal;
@ -37,8 +37,12 @@ impl NumberFilterPB {
}
}
impl CellFilterable<NumberFilterPB> for NumberTypeOptionPB {
fn apply_filter(&self, type_cell_data: TypeCellData, filter: &NumberFilterPB) -> FlowyResult<bool> {
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);
}

View File

@ -1,8 +1,9 @@
#[cfg(test)]
mod tests {
use crate::entities::FieldType;
use crate::services::cell::CellDataOperation;
use crate::services::cell::CellDataDecoder;
use crate::services::field::FieldBuilder;
use crate::services::field::{strip_currency_symbol, NumberFormat, NumberTypeOptionPB};
use grid_rev_model::FieldRevision;
use strum::IntoEnumIterator;
@ -438,7 +439,7 @@ mod tests {
) {
assert_eq!(
type_option
.decode_cell_data(input_str.to_owned().into(), field_type, field_rev)
.try_decode_cell_data(input_str.to_owned(), field_type, field_rev)
.unwrap()
.to_string(),
expected_str.to_owned()

View File

@ -1,14 +1,19 @@
use crate::entities::FieldType;
use crate::entities::{FieldType, NumberFilterPB};
use crate::impl_type_option;
use crate::services::cell::{AnyCellChangeset, CellBytes, CellDataOperation, CellDataSerialize, IntoCellData};
use crate::services::cell::{AnyCellChangeset, CellComparable, CellDataChangeset, CellDataDecoder};
use crate::services::field::type_options::number_type_option::format::*;
use crate::services::field::{BoxTypeOptionBuilder, NumberCellData, TypeOptionBuilder};
use crate::services::field::{
BoxTypeOptionBuilder, NumberCellData, StrCellData, TypeOption, TypeOptionBuilder, TypeOptionCellData,
TypeOptionConfiguration,
};
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
use grid_rev_model::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::default::Default;
use std::str::FromStr;
#[derive(Default)]
@ -71,6 +76,26 @@ pub struct NumberTypeOptionPB {
}
impl_type_option!(NumberTypeOptionPB, FieldType::Number);
impl TypeOption for NumberTypeOptionPB {
type CellData = StrCellData;
type CellChangeset = NumberCellChangeset;
type CellPBType = StrCellData;
}
impl TypeOptionConfiguration for NumberTypeOptionPB {
type CellFilterConfiguration = NumberFilterPB;
}
impl TypeOptionCellData for NumberTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType {
cell_data
}
fn decode_type_option_cell_data(&self, cell_data: String) -> FlowyResult<<Self as TypeOption>::CellData> {
Ok(cell_data.into())
}
}
impl NumberTypeOptionPB {
pub fn new() -> Self {
Self::default()
@ -103,50 +128,38 @@ pub(crate) fn strip_currency_symbol<T: ToString>(s: T) -> String {
s
}
impl CellDataSerialize<String> for NumberTypeOptionPB {
fn serialize_cell_data_to_bytes(
impl CellDataDecoder for NumberTypeOptionPB {
fn try_decode_cell_data(
&self,
cell_data: IntoCellData<String>,
_decoded_field_type: &FieldType,
cell_data: String,
decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
let cell_data: String = cell_data.try_into_inner()?;
match self.format_cell_data(&cell_data) {
Ok(num) => Ok(CellBytes::new(num.to_string())),
Err(_) => Ok(CellBytes::default()),
) -> FlowyResult<<Self as TypeOption>::CellData> {
if decoded_field_type.is_date() {
return Ok(Default::default());
}
let str_cell_data = self.decode_type_option_cell_data(cell_data)?;
let s = self.format_cell_data(&str_cell_data)?.to_string();
Ok(s.into())
}
fn serialize_cell_data_to_str(
fn decode_cell_data_to_str(
&self,
cell_data: IntoCellData<String>,
cell_data: String,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<String> {
let cell_data: String = cell_data.try_into_inner()?;
Ok(cell_data)
}
}
pub type NumberCellChangeset = String;
impl CellDataOperation<String, NumberCellChangeset> for NumberTypeOptionPB {
fn decode_cell_data(
&self,
cell_data: IntoCellData<String>,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
if decoded_field_type.is_date() {
return Ok(CellBytes::default());
}
self.serialize_cell_data_to_bytes(cell_data, decoded_field_type, field_rev)
}
impl CellDataChangeset for NumberTypeOptionPB {
fn apply_changeset(
&self,
changeset: AnyCellChangeset<String>,
changeset: AnyCellChangeset<NumberCellChangeset>,
_cell_rev: Option<CellRevision>,
) -> Result<String, FlowyError> {
let changeset = changeset.try_into_inner()?;
@ -155,6 +168,13 @@ impl CellDataOperation<String, NumberCellChangeset> for NumberTypeOptionPB {
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
}
}
impl std::default::Default for NumberTypeOptionPB {
fn default() -> Self {

View File

@ -1,4 +1,4 @@
use crate::services::cell::{CellBytesCustomParser, CellBytesParser, CellDataIsEmpty};
use crate::services::cell::{CellBytesCustomParser, CellProtobufBlobParser, DecodedCellData};
use crate::services::field::number_currency::Currency;
use crate::services::field::{strip_currency_symbol, NumberFormat, STRIP_SYMBOL};
use bytes::Bytes;
@ -94,14 +94,16 @@ impl ToString for NumberCellData {
}
}
impl CellDataIsEmpty for NumberCellData {
impl DecodedCellData for NumberCellData {
type Object = NumberCellData;
fn is_empty(&self) -> bool {
self.decimal.is_none()
}
}
pub struct NumberCellDataParser();
impl CellBytesParser for NumberCellDataParser {
impl CellProtobufBlobParser for NumberCellDataParser {
type Object = NumberCellData;
fn parser(bytes: &Bytes) -> FlowyResult<Self::Object> {
match String::from_utf8(bytes.to_vec()) {

View File

@ -1,11 +1,10 @@
use crate::entities::FieldType;
use crate::entities::{ChecklistFilterPB, FieldType};
use crate::impl_type_option;
use crate::services::cell::{AnyCellChangeset, CellBytes, CellDataOperation, CellDataSerialize, IntoCellData};
use crate::services::cell::{AnyCellChangeset, CellDataChangeset, FromCellString, TypeCellData};
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer;
use crate::services::field::type_options::util::get_cell_data;
use crate::services::field::{
BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction,
TypeOptionBuilder,
BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, SelectOptionPB,
SelectTypeOptionSharedAction, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
};
use bytes::Bytes;
use flowy_derive::ProtoBuf;
@ -24,6 +23,26 @@ pub struct ChecklistTypeOptionPB {
}
impl_type_option!(ChecklistTypeOptionPB, FieldType::Checklist);
impl TypeOption for ChecklistTypeOptionPB {
type CellData = SelectOptionIds;
type CellChangeset = SelectOptionCellChangeset;
type CellPBType = SelectOptionCellDataPB;
}
impl TypeOptionConfiguration for ChecklistTypeOptionPB {
type CellFilterConfiguration = ChecklistFilterPB;
}
impl TypeOptionCellData for ChecklistTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType {
self.get_selected_options(cell_data)
}
fn decode_type_option_cell_data(&self, cell_data: String) -> FlowyResult<<Self as TypeOption>::CellData> {
SelectOptionIds::from_cell_str(&cell_data)
}
}
impl SelectTypeOptionSharedAction for ChecklistTypeOptionPB {
fn number_of_max_options(&self) -> Option<usize> {
None
@ -38,16 +57,7 @@ impl SelectTypeOptionSharedAction for ChecklistTypeOptionPB {
}
}
impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for ChecklistTypeOptionPB {
fn decode_cell_data(
&self,
cell_data: IntoCellData<SelectOptionIds>,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
self.serialize_cell_data_to_bytes(cell_data, decoded_field_type, field_rev)
}
impl CellDataChangeset for ChecklistTypeOptionPB {
fn apply_changeset(
&self,
changeset: AnyCellChangeset<SelectOptionCellChangeset>,
@ -64,7 +74,9 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for Checklist
match cell_rev {
None => Ok(SelectOptionIds::from(insert_option_ids).to_string()),
Some(cell_rev) => {
let cell_data = get_cell_data(&cell_rev);
let cell_data = TypeCellData::try_from(cell_rev)
.map(|data| data.into_inner())
.unwrap_or_default();
let mut select_ids: SelectOptionIds = cell_data.into();
for insert_option_id in insert_option_ids {
if !select_ids.contains(&insert_option_id) {

View File

@ -1,11 +1,10 @@
use crate::entities::FieldType;
use crate::entities::{FieldType, SelectOptionFilterPB};
use crate::impl_type_option;
use crate::services::cell::{AnyCellChangeset, CellBytes, CellDataOperation, CellDataSerialize, IntoCellData};
use crate::services::cell::{AnyCellChangeset, CellDataChangeset, FromCellString, TypeCellData};
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer;
use crate::services::field::type_options::util::get_cell_data;
use crate::services::field::{
BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction,
TypeOptionBuilder,
BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, SelectOptionPB,
SelectTypeOptionSharedAction, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
};
use bytes::Bytes;
use flowy_derive::ProtoBuf;
@ -24,6 +23,26 @@ pub struct MultiSelectTypeOptionPB {
}
impl_type_option!(MultiSelectTypeOptionPB, FieldType::MultiSelect);
impl TypeOption for MultiSelectTypeOptionPB {
type CellData = SelectOptionIds;
type CellChangeset = SelectOptionCellChangeset;
type CellPBType = SelectOptionCellDataPB;
}
impl TypeOptionConfiguration for MultiSelectTypeOptionPB {
type CellFilterConfiguration = SelectOptionFilterPB;
}
impl TypeOptionCellData for MultiSelectTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType {
self.get_selected_options(cell_data)
}
fn decode_type_option_cell_data(&self, cell_data: String) -> FlowyResult<<Self as TypeOption>::CellData> {
SelectOptionIds::from_cell_str(&cell_data)
}
}
impl SelectTypeOptionSharedAction for MultiSelectTypeOptionPB {
fn number_of_max_options(&self) -> Option<usize> {
None
@ -38,16 +57,7 @@ impl SelectTypeOptionSharedAction for MultiSelectTypeOptionPB {
}
}
impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for MultiSelectTypeOptionPB {
fn decode_cell_data(
&self,
cell_data: IntoCellData<SelectOptionIds>,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
self.serialize_cell_data_to_bytes(cell_data, decoded_field_type, field_rev)
}
impl CellDataChangeset for MultiSelectTypeOptionPB {
fn apply_changeset(
&self,
changeset: AnyCellChangeset<SelectOptionCellChangeset>,
@ -67,7 +77,9 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for MultiSele
new_cell_data = SelectOptionIds::from(insert_option_ids).to_string();
}
Some(cell_rev) => {
let cell_data = get_cell_data(&cell_rev);
let cell_data = TypeCellData::try_from(cell_rev)
.map(|data| data.into_inner())
.unwrap_or_default();
let mut select_ids: SelectOptionIds = cell_data.into();
for insert_option_id in insert_option_ids {
if !select_ids.contains(&insert_option_id) {
@ -115,7 +127,7 @@ impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
#[cfg(test)]
mod tests {
use crate::entities::FieldType;
use crate::services::cell::CellDataOperation;
use crate::services::cell::CellDataChangeset;
use crate::services::field::type_options::selection_type_option::*;
use crate::services::field::{CheckboxTypeOptionBuilder, FieldBuilder, TypeOptionBuilder};
use crate::services::field::{MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB};

View File

@ -2,7 +2,9 @@
use crate::entities::{ChecklistFilterPB, FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
use crate::services::cell::{CellFilterable, TypeCellData};
use crate::services::field::{ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
use crate::services::field::{
ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB, TypeOptionCellData,
};
use crate::services::field::{SelectTypeOptionSharedAction, SelectedSelectOptions};
use flowy_error::FlowyResult;
@ -78,33 +80,35 @@ impl SelectOptionFilterPB {
}
}
impl CellFilterable<SelectOptionFilterPB> for MultiSelectTypeOptionPB {
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 selected_options = SelectedSelectOptions::from(self.get_selected_options(type_cell_data.into()));
let ids = self.decode_type_option_cell_data(type_cell_data.data)?;
let selected_options = SelectedSelectOptions::from(self.get_selected_options(ids));
Ok(filter.is_visible(&selected_options, FieldType::MultiSelect))
}
}
impl CellFilterable<SelectOptionFilterPB> for SingleSelectTypeOptionPB {
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 selected_options = SelectedSelectOptions::from(self.get_selected_options(type_cell_data.into()));
let ids = self.decode_type_option_cell_data(type_cell_data.data)?;
let selected_options = SelectedSelectOptions::from(self.get_selected_options(ids));
Ok(filter.is_visible(&selected_options, FieldType::SingleSelect))
}
}
impl CellFilterable<ChecklistFilterPB> for ChecklistTypeOptionPB {
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 selected_options = SelectedSelectOptions::from(self.get_selected_options(type_cell_data.into()));
let ids = self.decode_type_option_cell_data(type_cell_data.data)?;
let selected_options = SelectedSelectOptions::from(self.get_selected_options(ids));
Ok(filter.is_visible(&self.options, &selected_options))
}
}

View File

@ -1,10 +1,13 @@
use crate::entities::parser::NotEmptyStr;
use crate::entities::{CellChangesetPB, CellPathPB, CellPathParams, FieldType};
use crate::services::cell::{
CellBytes, CellBytesParser, CellDataIsEmpty, CellDataSerialize, FromCellChangeset, FromCellString, IntoCellData,
CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, FromCellString,
};
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer;
use crate::services::field::{ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
use crate::services::field::{
ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB, TypeOption, TypeOptionCellData,
};
use bytes::Bytes;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::{internal_error, ErrorCode, FlowyResult};
@ -69,17 +72,10 @@ impl std::default::Default for SelectOptionColorPB {
}
}
pub fn make_selected_options(
cell_data: IntoCellData<SelectOptionIds>,
options: &[SelectOptionPB],
) -> Vec<SelectOptionPB> {
if let Ok(ids) = cell_data.try_into_inner() {
ids.iter()
.flat_map(|option_id| options.iter().find(|option| &option.id == option_id).cloned())
.collect()
} else {
vec![]
}
pub fn make_selected_options(ids: SelectOptionIds, options: &[SelectOptionPB]) -> Vec<SelectOptionPB> {
ids.iter()
.flat_map(|option_id| options.iter().find(|option| &option.id == option_id).cloned())
.collect()
}
/// Defines the shared actions used by SingleSelect or Multi-Select.
pub trait SelectTypeOptionSharedAction: TypeOptionDataSerializer + Send + Sync {
@ -113,8 +109,8 @@ pub trait SelectTypeOptionSharedAction: TypeOptionDataSerializer + Send + Sync {
}
/// Return a list of options that are selected by user
fn get_selected_options(&self, cell_data: IntoCellData<SelectOptionIds>) -> SelectOptionCellDataPB {
let mut select_options = make_selected_options(cell_data, self.options());
fn get_selected_options(&self, ids: SelectOptionIds) -> SelectOptionCellDataPB {
let mut select_options = make_selected_options(ids, self.options());
match self.number_of_max_options() {
None => {}
Some(number_of_max_options) => {
@ -127,75 +123,39 @@ pub trait SelectTypeOptionSharedAction: TypeOptionDataSerializer + Send + Sync {
}
}
fn transform_cell_data(
&self,
cell_data: IntoCellData<SelectOptionIds>,
decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
match decoded_field_type {
FieldType::SingleSelect | FieldType::MultiSelect => {
// Do nothing
}
FieldType::Checkbox => {
// transform the cell data to the option id
let mut transformed_ids = Vec::new();
let options = self.options();
cell_data.0.iter().for_each(|ids| {
ids.0.iter().for_each(|name| {
let id = options
.iter()
.find(|option| option.name == name.clone())
.unwrap()
.id
.clone();
transformed_ids.push(id);
})
});
return CellBytes::from(
self.get_selected_options(IntoCellData(Some(SelectOptionIds(transformed_ids)))),
);
}
_ => {
return Ok(CellBytes::default());
}
}
CellBytes::from(self.get_selected_options(cell_data))
}
fn options(&self) -> &Vec<SelectOptionPB>;
fn mut_options(&mut self) -> &mut Vec<SelectOptionPB>;
}
impl<T> CellDataSerialize<SelectOptionIds> for T
impl<T> CellDataDecoder for T
where
T: SelectTypeOptionSharedAction,
T: SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds> + TypeOptionCellData,
{
fn serialize_cell_data_to_bytes(
fn try_decode_cell_data(
&self,
cell_data: IntoCellData<SelectOptionIds>,
cell_data: String,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
SelectOptionTypeOptionTransformer::transform_type_option_cell_data(
) -> FlowyResult<<Self as TypeOption>::CellData> {
let cell_data = self.decode_type_option_cell_data(cell_data)?;
Ok(SelectOptionTypeOptionTransformer::transform_type_option_cell_data(
self,
cell_data,
decoded_field_type,
field_rev,
)
))
}
fn serialize_cell_data_to_str(
fn decode_cell_data_to_str(
&self,
cell_data: IntoCellData<SelectOptionIds>,
cell_data: String,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<String> {
let ids = self.decode_type_option_cell_data(cell_data)?;
Ok(self
.get_selected_options(cell_data)
.get_selected_options(ids)
.select_options
.into_iter()
.map(|option| option.name)
@ -333,14 +293,16 @@ impl std::ops::DerefMut for SelectOptionIds {
}
}
impl CellDataIsEmpty for SelectOptionIds {
impl DecodedCellData for SelectOptionIds {
type Object = SelectOptionIds;
fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
pub struct SelectOptionIdsParser();
impl CellBytesParser for SelectOptionIdsParser {
impl CellProtobufBlobParser for SelectOptionIdsParser {
type Object = SelectOptionIds;
fn parser(bytes: &Bytes) -> FlowyResult<Self::Object> {
match String::from_utf8(bytes.to_vec()) {
@ -350,14 +312,16 @@ impl CellBytesParser for SelectOptionIdsParser {
}
}
impl CellDataIsEmpty for SelectOptionCellDataPB {
impl DecodedCellData for SelectOptionCellDataPB {
type Object = SelectOptionCellDataPB;
fn is_empty(&self) -> bool {
self.select_options.is_empty()
}
}
pub struct SelectOptionCellDataParser();
impl CellBytesParser for SelectOptionCellDataParser {
impl CellProtobufBlobParser for SelectOptionCellDataParser {
type Object = SelectOptionCellDataPB;
fn parser(bytes: &Bytes) -> FlowyResult<Self::Object> {

View File

@ -1,8 +1,11 @@
use crate::entities::FieldType;
use crate::entities::{FieldType, SelectOptionFilterPB};
use crate::impl_type_option;
use crate::services::cell::{AnyCellChangeset, CellBytes, CellDataOperation, CellDataSerialize, IntoCellData};
use crate::services::cell::{AnyCellChangeset, CellDataChangeset, FromCellString};
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer;
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use crate::services::field::{
BoxTypeOptionBuilder, SelectOptionCellDataPB, TypeOption, TypeOptionBuilder, TypeOptionCellData,
TypeOptionConfiguration,
};
use crate::services::field::{
SelectOptionCellChangeset, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction,
};
@ -23,6 +26,26 @@ pub struct SingleSelectTypeOptionPB {
}
impl_type_option!(SingleSelectTypeOptionPB, FieldType::SingleSelect);
impl TypeOption for SingleSelectTypeOptionPB {
type CellData = SelectOptionIds;
type CellChangeset = SelectOptionCellChangeset;
type CellPBType = SelectOptionCellDataPB;
}
impl TypeOptionConfiguration for SingleSelectTypeOptionPB {
type CellFilterConfiguration = SelectOptionFilterPB;
}
impl TypeOptionCellData for SingleSelectTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType {
self.get_selected_options(cell_data)
}
fn decode_type_option_cell_data(&self, cell_data: String) -> FlowyResult<<Self as TypeOption>::CellData> {
SelectOptionIds::from_cell_str(&cell_data)
}
}
impl SelectTypeOptionSharedAction for SingleSelectTypeOptionPB {
fn number_of_max_options(&self) -> Option<usize> {
Some(1)
@ -37,16 +60,7 @@ impl SelectTypeOptionSharedAction for SingleSelectTypeOptionPB {
}
}
impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for SingleSelectTypeOptionPB {
fn decode_cell_data(
&self,
cell_data: IntoCellData<SelectOptionIds>,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
self.serialize_cell_data_to_bytes(cell_data, decoded_field_type, field_rev)
}
impl CellDataChangeset for SingleSelectTypeOptionPB {
fn apply_changeset(
&self,
changeset: AnyCellChangeset<SelectOptionCellChangeset>,
@ -102,7 +116,7 @@ impl TypeOptionBuilder for SingleSelectTypeOptionBuilder {
#[cfg(test)]
mod tests {
use crate::entities::FieldType;
use crate::services::cell::CellDataOperation;
use crate::services::cell::CellDataChangeset;
use crate::services::field::type_options::*;
use crate::services::field::{FieldBuilder, TypeOptionBuilder};

View File

@ -1,10 +1,10 @@
use crate::entities::FieldType;
use crate::services::cell::{CellBytes, IntoCellData};
use crate::services::field::{
MultiSelectTypeOptionPB, SelectOptionColorPB, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction,
SingleSelectTypeOptionPB, CHECK, UNCHECK,
SingleSelectTypeOptionPB, TypeOption, CHECK, UNCHECK,
};
use flowy_error::FlowyResult;
use grid_rev_model::FieldRevision;
use serde_json;
@ -57,31 +57,28 @@ impl SelectOptionTypeOptionTransformer {
pub fn transform_type_option_cell_data<T>(
shared: &T,
cell_data: IntoCellData<SelectOptionIds>,
cell_data: <T as TypeOption>::CellData,
decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<CellBytes>
) -> <T as TypeOption>::CellData
where
T: SelectTypeOptionSharedAction,
T: SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds>,
{
match decoded_field_type {
FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => {
//
CellBytes::from(shared.get_selected_options(cell_data))
}
FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => cell_data,
FieldType::Checkbox => {
// transform the cell data to the option id
let mut transformed_ids = Vec::new();
let options = shared.options();
cell_data.try_into_inner()?.iter().for_each(|name| {
cell_data.iter().for_each(|name| {
if let Some(option) = options.iter().find(|option| &option.name == name) {
transformed_ids.push(option.id.clone());
}
});
let transformed_cell_data = IntoCellData::from(SelectOptionIds::from(transformed_ids));
CellBytes::from(shared.get_selected_options(transformed_cell_data))
SelectOptionIds::from(transformed_ids)
}
_ => Ok(CellBytes::default()),
_ => SelectOptionIds::from(vec![]),
}
}
}

View File

@ -1,6 +1,6 @@
use crate::entities::{TextFilterConditionPB, TextFilterPB};
use crate::services::cell::{CellFilterable, IntoCellData, TypeCellData};
use crate::services::field::{RichTextTypeOptionPB, TextCellData};
use crate::services::cell::{CellFilterable, TypeCellData};
use crate::services::field::{RichTextTypeOptionPB, TypeOptionCellData, TypeOptionConfiguration};
use flowy_error::FlowyResult;
impl TextFilterPB {
@ -20,17 +20,21 @@ impl TextFilterPB {
}
}
impl CellFilterable<TextFilterPB> for RichTextTypeOptionPB {
fn apply_filter(&self, type_cell_data: TypeCellData, filter: &TextFilterPB) -> FlowyResult<bool> {
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 cell_data: IntoCellData<TextCellData> = type_cell_data.into();
let text_cell_data = cell_data.try_into_inner()?;
let text_cell_data = self.decode_type_option_cell_data(type_cell_data.data)?;
Ok(filter.is_visible(text_cell_data))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::all)]

View File

@ -1,8 +1,9 @@
#[cfg(test)]
mod tests {
use crate::entities::FieldType;
use crate::services::cell::CellDataOperation;
use crate::services::cell::CellDataDecoder;
use crate::services::field::FieldBuilder;
use crate::services::field::*;
// Test parser the cell data which field's type is FieldType::Date to cell data
@ -15,11 +16,9 @@ mod tests {
assert_eq!(
type_option
.decode_cell_data(1647251762.into(), &field_type, &field_rev)
.try_decode_cell_data(1647251762.to_string(), &field_type, &field_rev)
.unwrap()
.parser::<TextCellDataParser>()
.unwrap()
.as_ref(),
.as_str(),
"Mar 14,2022"
);
}
@ -38,9 +37,7 @@ mod tests {
assert_eq!(
type_option
.decode_cell_data(option_id.into(), &field_type, &field_rev)
.unwrap()
.parser::<TextCellDataParser>()
.try_decode_cell_data(option_id, &field_type, &field_rev)
.unwrap()
.to_string(),
done_option.name,

View File

@ -1,15 +1,19 @@
use crate::entities::FieldType;
use crate::entities::{FieldType, TextFilterPB};
use crate::impl_type_option;
use crate::services::cell::{
decode_cell_data_to_string, AnyCellChangeset, CellBytes, CellBytesParser, CellDataIsEmpty, CellDataOperation,
CellDataSerialize, FromCellString, IntoCellData,
stringify_cell_data, AnyCellChangeset, CellComparable, CellDataChangeset, CellDataDecoder, CellProtobufBlobParser,
DecodedCellData, FromCellString,
};
use crate::services::field::{
BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
};
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
use grid_rev_model::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
use protobuf::ProtobufError;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
#[derive(Default)]
pub struct RichTextTypeOptionBuilder(RichTextTypeOptionPB);
@ -39,48 +43,57 @@ pub struct RichTextTypeOptionPB {
}
impl_type_option!(RichTextTypeOptionPB, FieldType::RichText);
impl CellDataSerialize<RichTextCellData> for RichTextTypeOptionPB {
fn serialize_cell_data_to_bytes(
&self,
cell_data: IntoCellData<RichTextCellData>,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
let cell_str: RichTextCellData = cell_data.try_into_inner()?;
Ok(CellBytes::new(cell_str))
impl TypeOption for RichTextTypeOptionPB {
type CellData = StrCellData;
type CellChangeset = String;
type CellPBType = StrCellData;
}
impl TypeOptionConfiguration for RichTextTypeOptionPB {
type CellFilterConfiguration = TextFilterPB;
}
impl TypeOptionCellData for RichTextTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType {
cell_data
}
fn serialize_cell_data_to_str(
&self,
cell_data: IntoCellData<RichTextCellData>,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<String> {
let cell_str: RichTextCellData = cell_data.try_into_inner()?;
Ok(cell_str)
fn decode_type_option_cell_data(&self, cell_data: String) -> FlowyResult<<Self as TypeOption>::CellData> {
StrCellData::from_cell_str(&cell_data)
}
}
impl CellDataOperation<RichTextCellData, String> for RichTextTypeOptionPB {
fn decode_cell_data(
impl CellDataDecoder for RichTextTypeOptionPB {
fn try_decode_cell_data(
&self,
cell_data: IntoCellData<RichTextCellData>,
cell_data: String,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
) -> FlowyResult<<Self as TypeOption>::CellData> {
if decoded_field_type.is_date()
|| decoded_field_type.is_single_select()
|| decoded_field_type.is_multi_select()
|| decoded_field_type.is_number()
|| decoded_field_type.is_url()
{
let s = decode_cell_data_to_string(cell_data, decoded_field_type, decoded_field_type, field_rev);
Ok(CellBytes::new(s.unwrap_or_else(|_| "".to_owned())))
Ok(stringify_cell_data(cell_data, decoded_field_type, field_rev).into())
} else {
self.serialize_cell_data_to_bytes(cell_data, decoded_field_type, field_rev)
StrCellData::from_cell_str(&cell_data)
}
}
fn decode_cell_data_to_str(
&self,
cell_data: String,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<String> {
let cell_str = StrCellData::from_cell_str(&cell_data)?;
Ok(cell_str.into())
}
}
impl CellDataChangeset for RichTextTypeOptionPB {
fn apply_changeset(
&self,
changeset: AnyCellChangeset<String>,
@ -95,6 +108,14 @@ impl CellDataOperation<RichTextCellData, String> for RichTextTypeOptionPB {
}
}
impl CellComparable for RichTextTypeOptionPB {
type CellData = String;
fn apply_cmp(&self, cell_data: &Self::CellData, other_cell_data: &Self::CellData) -> Ordering {
cell_data.cmp(other_cell_data)
}
}
pub struct TextCellData(pub String);
impl AsRef<str> for TextCellData {
fn as_ref(&self) -> &str {
@ -125,14 +146,16 @@ impl ToString for TextCellData {
}
}
impl CellDataIsEmpty for TextCellData {
impl DecodedCellData for TextCellData {
type Object = TextCellData;
fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
pub struct TextCellDataParser();
impl CellBytesParser for TextCellDataParser {
impl CellProtobufBlobParser for TextCellDataParser {
type Object = TextCellData;
fn parser(bytes: &Bytes) -> FlowyResult<Self::Object> {
match String::from_utf8(bytes.to_vec()) {
@ -142,4 +165,61 @@ impl CellBytesParser for TextCellDataParser {
}
}
pub type RichTextCellData = String;
#[derive(Default, Debug)]
pub struct StrCellData(pub String);
impl std::ops::Deref for StrCellData {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for StrCellData {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl FromCellString for StrCellData {
fn from_cell_str(s: &str) -> FlowyResult<Self> {
Ok(Self(s.to_owned()))
}
}
impl std::convert::From<String> for StrCellData {
fn from(s: String) -> Self {
Self(s)
}
}
impl std::convert::From<StrCellData> for String {
fn from(value: StrCellData) -> Self {
value.0
}
}
impl std::convert::From<&str> for StrCellData {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl std::convert::TryFrom<StrCellData> for Bytes {
type Error = ProtobufError;
fn try_from(value: StrCellData) -> Result<Self, Self::Error> {
Ok(Bytes::from(value.0))
}
}
impl AsRef<[u8]> for StrCellData {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl AsRef<str> for StrCellData {
fn as_ref(&self) -> &str {
self.0.as_str()
}
}

View File

@ -0,0 +1,119 @@
use crate::entities::FieldType;
use crate::services::cell::{CellDataChangeset, CellDataDecoder, CellProtobufBlob, FromCellString};
use crate::services::field::{
CheckboxTypeOptionPB, ChecklistTypeOptionPB, DateTypeOptionPB, MultiSelectTypeOptionPB, NumberTypeOptionPB,
RichTextTypeOptionPB, SingleSelectTypeOptionPB, URLTypeOptionPB,
};
use bytes::Bytes;
use flowy_error::FlowyResult;
use grid_rev_model::FieldRevision;
use protobuf::ProtobufError;
use std::fmt::Debug;
pub trait TypeOption {
type CellData: FromCellString + Default;
type CellChangeset;
type CellPBType: TryInto<Bytes, Error = ProtobufError> + Debug;
}
pub trait TypeOptionConfiguration {
type CellFilterConfiguration;
}
pub trait TypeOptionCellDataHandler {
fn handle_cell_data(
&self,
cell_data: String,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellProtobufBlob>;
fn stringify_cell_data(&self, cell_data: String, field_type: &FieldType, field_rev: &FieldRevision) -> String;
}
pub trait TypeOptionCellData: TypeOption {
///
/// Convert the decoded cell data into corresponding `Protobuf struct`.
/// For example:
/// FieldType::URL => URLCellDataPB
/// FieldType::Date=> DateCellDataPB
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType;
/// Decodes the opaque cell data to corresponding data struct.
// For example, the cell data is timestamp if its field type is `FieldType::Date`. This cell
// data can not directly show to user. So it needs to be encode as the date string with custom
// format setting. Encode `1647251762` to `"Mar 14,2022`
fn decode_type_option_cell_data(&self, cell_data: String) -> FlowyResult<<Self as TypeOption>::CellData>;
}
impl<T> TypeOptionCellDataHandler for T
where
T: TypeOption + CellDataDecoder + CellDataChangeset + TypeOptionCellData,
{
fn handle_cell_data(
&self,
cell_data: String,
field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellProtobufBlob> {
let cell_data = self.try_decode_cell_data(cell_data, field_type, field_rev)?;
CellProtobufBlob::from(self.convert_into_pb_type(cell_data))
}
fn stringify_cell_data(
&self,
cell_data: String,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> String {
self.decode_cell_data_to_str(cell_data, decoded_field_type, field_rev)
.unwrap_or_default()
}
}
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_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| 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>),
}
}
}

View File

@ -1,16 +1,18 @@
use crate::entities::TextFilterPB;
use crate::services::cell::{CellFilterable, IntoCellData, TypeCellData};
use crate::services::field::{TextCellData, URLTypeOptionPB};
use crate::services::cell::{CellFilterable, TypeCellData};
use crate::services::field::{TypeOptionCellData, TypeOptionConfiguration, URLTypeOptionPB};
use flowy_error::FlowyResult;
impl CellFilterable<TextFilterPB> for URLTypeOptionPB {
fn apply_filter(&self, type_cell_data: TypeCellData, filter: &TextFilterPB) -> FlowyResult<bool> {
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 cell_data: IntoCellData<TextCellData> = type_cell_data.into();
let text_cell_data = cell_data.try_into_inner()?;
Ok(filter.is_visible(&text_cell_data))
let url_cell_data = self.decode_type_option_cell_data(type_cell_data.data)?;
Ok(filter.is_visible(&url_cell_data))
}
}

View File

@ -1,8 +1,9 @@
#[cfg(test)]
mod tests {
use crate::entities::FieldType;
use crate::services::cell::{CellDataOperation, IntoCellData};
use crate::services::field::{FieldBuilder, URLCellDataParser};
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
use crate::services::field::FieldBuilder;
use crate::services::field::{URLCellData, URLTypeOptionPB};
use grid_rev_model::FieldRevision;
@ -175,16 +176,14 @@ mod tests {
assert_eq!(expected_url.to_owned(), decode_cell_data.url);
}
fn decode_cell_data<T: Into<IntoCellData<URLCellData>>>(
encoded_data: T,
fn decode_cell_data(
encoded_data: String,
type_option: &URLTypeOptionPB,
field_rev: &FieldRevision,
field_type: &FieldType,
) -> URLCellData {
type_option
.decode_cell_data(encoded_data.into(), field_type, field_rev)
.unwrap()
.parser::<URLCellDataParser>()
.try_decode_cell_data(encoded_data, field_type, field_rev)
.unwrap()
}
}

View File

@ -1,7 +1,10 @@
use crate::entities::FieldType;
use crate::entities::{FieldType, TextFilterPB};
use crate::impl_type_option;
use crate::services::cell::{AnyCellChangeset, CellBytes, CellDataOperation, CellDataSerialize, IntoCellData};
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder, URLCellData, URLCellDataPB};
use crate::services::cell::{AnyCellChangeset, CellDataChangeset, CellDataDecoder, FromCellString};
use crate::services::field::{
BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration, URLCellData,
URLCellDataPB,
};
use bytes::Bytes;
use fancy_regex::Regex;
use flowy_derive::ProtoBuf;
@ -36,47 +39,57 @@ pub struct URLTypeOptionPB {
}
impl_type_option!(URLTypeOptionPB, FieldType::URL);
impl CellDataSerialize<URLCellData> for URLTypeOptionPB {
fn serialize_cell_data_to_bytes(
&self,
cell_data: IntoCellData<URLCellData>,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
let cell_data_pb: URLCellDataPB = cell_data.try_into_inner()?.into();
CellBytes::from(cell_data_pb)
impl TypeOption for URLTypeOptionPB {
type CellData = URLCellData;
type CellChangeset = URLCellChangeset;
type CellPBType = URLCellDataPB;
}
impl TypeOptionConfiguration for URLTypeOptionPB {
type CellFilterConfiguration = TextFilterPB;
}
impl TypeOptionCellData for URLTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType {
cell_data.into()
}
fn serialize_cell_data_to_str(
fn decode_type_option_cell_data(&self, cell_data: String) -> FlowyResult<<Self as TypeOption>::CellData> {
URLCellData::from_cell_str(&cell_data)
}
}
impl CellDataDecoder for URLTypeOptionPB {
fn try_decode_cell_data(
&self,
cell_data: IntoCellData<URLCellData>,
cell_data: String,
decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<<Self as TypeOption>::CellData> {
if !decoded_field_type.is_url() {
return Ok(Default::default());
}
self.decode_type_option_cell_data(cell_data)
}
fn decode_cell_data_to_str(
&self,
cell_data: String,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<String> {
let cell_data: URLCellData = cell_data.try_into_inner()?;
let cell_data = self.decode_type_option_cell_data(cell_data)?;
Ok(cell_data.content)
}
}
pub type URLCellChangeset = String;
impl CellDataOperation<URLCellData, URLCellChangeset> for URLTypeOptionPB {
fn decode_cell_data(
&self,
cell_data: IntoCellData<URLCellData>,
decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
if !decoded_field_type.is_url() {
return Ok(CellBytes::default());
}
let cell_data = cell_data.try_into_inner()?.to_json()?;
Ok(CellBytes::new(cell_data))
}
impl CellDataChangeset for URLTypeOptionPB {
fn apply_changeset(
&self,
changeset: AnyCellChangeset<String>,
changeset: AnyCellChangeset<URLCellChangeset>,
_cell_rev: Option<CellRevision>,
) -> Result<String, FlowyError> {
let content = changeset.try_into_inner()?;

View File

@ -1,4 +1,4 @@
use crate::services::cell::{CellBytesParser, CellDataIsEmpty, FromCellString};
use crate::services::cell::{CellProtobufBlobParser, DecodedCellData, FromCellString};
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{internal_error, FlowyResult};
@ -22,6 +22,14 @@ impl From<URLCellData> for URLCellDataPB {
}
}
impl DecodedCellData for URLCellDataPB {
type Object = URLCellDataPB;
fn is_empty(&self) -> bool {
self.content.is_empty()
}
}
#[derive(Clone, Default, Serialize, Deserialize)]
pub struct URLCellData {
pub url: String,
@ -41,21 +49,26 @@ impl URLCellData {
}
}
impl CellDataIsEmpty for URLCellData {
impl AsRef<str> for URLCellData {
fn as_ref(&self) -> &str {
&self.url
}
}
impl DecodedCellData for URLCellData {
type Object = URLCellData;
fn is_empty(&self) -> bool {
self.content.is_empty()
}
}
pub struct URLCellDataParser();
impl CellBytesParser for URLCellDataParser {
type Object = URLCellData;
impl CellProtobufBlobParser for URLCellDataParser {
type Object = URLCellDataPB;
fn parser(bytes: &Bytes) -> FlowyResult<Self::Object> {
match String::from_utf8(bytes.to_vec()) {
Ok(s) => URLCellData::from_cell_str(&s),
Err(_) => Ok(URLCellData::default()),
}
URLCellDataPB::try_from(bytes.as_ref()).map_err(internal_error)
}
}

View File

@ -1,10 +0,0 @@
use crate::services::cell::TypeCellData;
use grid_rev_model::CellRevision;
use std::str::FromStr;
pub fn get_cell_data(cell_rev: &CellRevision) -> String {
match TypeCellData::from_str(&cell_rev.data) {
Ok(type_option) => type_option.data,
Err(_) => String::new(),
}
}

View File

@ -1,3 +0,0 @@
mod cell_data_util;
pub use cell_data_util::*;

View File

@ -3,7 +3,7 @@ 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, CellBytes};
use crate::services::cell::{apply_cell_data_changeset, decode_type_cell_data, CellProtobufBlob};
use crate::services::field::{
default_type_option_builder_from_type, type_option_builder_from_bytes, type_option_builder_from_json_str,
FieldBuilder,
@ -435,12 +435,12 @@ impl GridRevisionEditor {
Some(CellPB::new(&params.field_id, field_type, cell_bytes.to_vec()))
}
pub async fn get_cell_bytes(&self, params: &CellPathParams) -> Option<CellBytes> {
pub async fn get_cell_bytes(&self, params: &CellPathParams) -> Option<CellProtobufBlob> {
let (_, cell_data) = self.decode_cell_data_from(params).await?;
Some(cell_data)
}
async fn decode_cell_data_from(&self, params: &CellPathParams) -> Option<(FieldType, CellBytes)> {
async fn decode_cell_data_from(&self, params: &CellPathParams) -> Option<(FieldType, CellProtobufBlob)> {
let field_rev = self.get_field_rev(&params.field_id).await?;
let (_, row_rev) = self.block_manager.get_row_rev(&params.row_id).await.ok()??;
let cell_rev = row_rev.cells.get(&params.field_id)?.clone();

View File

@ -1,5 +1,5 @@
use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB};
use crate::services::cell::CellDataIsEmpty;
use crate::services::cell::DecodedCellData;
use crate::services::group::controller::MoveGroupRowContext;
use crate::services::group::Group;
use flowy_error::FlowyResult;
@ -11,7 +11,7 @@ use std::sync::Arc;
/// For example, the `CheckboxGroupController` implements this trait to provide custom behavior.
///
pub trait GroupControllerCustomActions: Send + Sync {
type CellDataType: CellDataIsEmpty;
type CellDataType: DecodedCellData;
/// Returns the a value of the cell, default value is None
///
/// Determine which group the row is placed in based on the data of the cell. If the cell data

View File

@ -1,5 +1,5 @@
use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB, InsertedRowPB, RowPB};
use crate::services::cell::{decode_type_cell_data, CellBytesParser, CellDataIsEmpty};
use crate::services::cell::{decode_type_cell_data, CellProtobufBlobParser, DecodedCellData};
use crate::services::group::action::{GroupControllerCustomActions, GroupControllerSharedActions};
use crate::services::group::configuration::GroupContext;
use crate::services::group::entities::Group;
@ -55,7 +55,7 @@ pub struct MoveGroupRowContext<'a> {
/// C: represents the group configuration that impl [GroupConfigurationSerde]
/// T: the type-option data deserializer that impl [TypeOptionDataDeserializer]
/// G: the group generator, [GroupGenerator]
/// P: the parser that impl [CellBytesParser] for the CellBytes
/// P: the parser that impl [CellProtobufBlobParser] for the CellBytes
pub struct GenericGroupController<C, T, G, P> {
pub field_id: String,
pub type_option: Option<T>,
@ -154,7 +154,7 @@ where
impl<C, T, G, P> GroupControllerSharedActions for GenericGroupController<C, T, G, P>
where
P: CellBytesParser,
P: CellProtobufBlobParser,
C: GroupConfigurationContentSerde,
T: TypeOptionDataDeserializer,
G: GroupGenerator<Context = GroupContext<C>, TypeOptionType = T>,

View File

@ -1,10 +1,17 @@
#![allow(clippy::all)]
use crate::entities::FieldType;
#[allow(unused_attributes)]
use crate::entities::{GridSortPB, SortChangesetNotificationPB};
use crate::entities::SortChangesetNotificationPB;
use crate::services::sort::{SortChangeset, SortType};
use flowy_task::TaskDispatcher;
use grid_rev_model::{FieldRevision, RowRevision, SortRevision};
use grid_rev_model::{CellRevision, FieldRevision, RowRevision, SortCondition, SortRevision};
use lib_infra::future::Fut;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
@ -23,7 +30,9 @@ pub struct SortController {
delegate: Box<dyn SortDelegate>,
task_scheduler: Arc<RwLock<TaskDispatcher>>,
#[allow(dead_code)]
sorts: Vec<GridSortPB>,
sorts: Vec<SortRevision>,
#[allow(dead_code)]
row_orders: HashMap<String, usize>,
}
impl SortController {
@ -37,6 +46,7 @@ impl SortController {
delegate: Box::new(delegate),
task_scheduler,
sorts: vec![],
row_orders: HashMap::new(),
}
}
@ -49,10 +59,71 @@ impl SortController {
}
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 did_receive_changes(&mut self, _changeset: SortChangeset) -> Option<SortChangesetNotificationPB> {
None
}
}
#[allow(dead_code)]
fn cmp_row(
left: &Arc<RowRevision>,
right: &Arc<RowRevision>,
sorts: &[SortRevision],
field_revs: &[Arc<FieldRevision>],
) -> Ordering {
let mut order = Ordering::Equal;
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),
}
}
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
_ => Ordering::Equal,
};
if cmp_order.is_ne() {
// If the cmp_order is not Ordering::Equal, then break the loop.
order = match sort.condition {
SortCondition::Ascending => cmp_order,
SortCondition::Descending => cmp_order.reverse(),
};
break;
}
}
order
}
#[allow(dead_code)]
fn cmp_cell(
_left: &CellRevision,
_right: &CellRevision,
_field_rev: &Arc<FieldRevision>,
field_type: FieldType,
) -> 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)
};
cal_order().unwrap_or(Ordering::Equal)
}

View File

@ -386,7 +386,7 @@ impl GridViewRevisionEditor {
id: sort_id,
field_id: params.field_id.clone(),
field_type: params.field_type,
condition: params.condition,
condition: params.condition.into(),
};
let mut sort_controller = self.sort_controller.write().await;

View File

@ -5,3 +5,4 @@ mod filter_test;
mod grid_editor;
mod group_test;
mod snapshot_test;
mod sort_test;

View File

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

View File

@ -0,0 +1,53 @@
use crate::grid::grid_editor::GridEditorTest;
use flowy_grid::entities::{AlterSortParams, DeleteSortParams};
pub enum SortScript {
InsertSort { params: AlterSortParams },
DeleteSort { params: DeleteSortParams },
AssertTextOrder { orders: Vec<String> },
}
pub struct GridSortTest {
inner: GridEditorTest,
}
impl GridSortTest {
pub async fn new() -> Self {
let editor_test = GridEditorTest::new_table().await;
Self { inner: editor_test }
}
pub async fn run_scripts(&mut self, scripts: Vec<SortScript>) {
for script in scripts {
self.run_script(script).await;
}
}
pub async fn run_script(&mut self, script: SortScript) {
match script {
SortScript::InsertSort { params } => {
let _ = self.editor.create_or_update_sort(params).await.unwrap();
}
SortScript::DeleteSort { params } => {
//
self.editor.delete_sort(params).await.unwrap();
}
SortScript::AssertTextOrder { orders: _ } => {
//
}
}
}
}
impl std::ops::Deref for GridSortTest {
type Target = GridEditorTest;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for GridSortTest {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}

View File

@ -1,10 +1,34 @@
use crate::FieldTypeRevision;
use serde::{Deserialize, Serialize};
use serde_repr::*;
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
pub struct SortRevision {
pub id: String,
pub field_id: String,
pub field_type: FieldTypeRevision,
pub condition: u8,
pub condition: SortCondition,
}
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Hash, Clone, Debug)]
#[repr(u8)]
pub enum SortCondition {
Ascending = 0,
Descending = 1,
}
impl std::convert::From<u8> for SortCondition {
fn from(num: u8) -> Self {
match num {
0 => SortCondition::Ascending,
1 => SortCondition::Descending,
_ => SortCondition::Ascending,
}
}
}
impl std::default::Default for SortCondition {
fn default() -> Self {
Self::Ascending
}
}