mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Refactor/type option (#1578)
This commit is contained in:
parent
d0a914c10f
commit
85e489babb
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
extern crate core;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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()?;
|
||||
|
@ -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()) {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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>,
|
||||
|
@ -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> {
|
||||
|
@ -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::*;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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()) {
|
||||
|
@ -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) {
|
||||
|
@ -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};
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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![]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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>),
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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()?;
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
mod cell_data_util;
|
||||
|
||||
pub use cell_data_util::*;
|
@ -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(¶ms.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(¶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();
|
||||
|
@ -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
|
||||
|
@ -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>,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -5,3 +5,4 @@ mod filter_test;
|
||||
mod grid_editor;
|
||||
mod group_test;
|
||||
mod snapshot_test;
|
||||
mod sort_test;
|
||||
|
2
frontend/rust-lib/flowy-grid/tests/grid/sort_test/mod.rs
Normal file
2
frontend/rust-lib/flowy-grid/tests/grid/sort_test/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
// mod script;
|
||||
// mod text_sort_test;
|
53
frontend/rust-lib/flowy-grid/tests/grid/sort_test/script.rs
Normal file
53
frontend/rust-lib/flowy-grid/tests/grid/sort_test/script.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user