use crate::entities::FieldType; 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; /// This trait is used when doing filter/search on the grid. pub trait CellFilterable: TypeOptionConfiguration { /// Return true if type_cell_data match the filter condition. fn apply_filter( &self, type_cell_data: TypeCellData, filter: &::CellFilterConfiguration, ) -> FlowyResult; } pub trait CellComparable { type CellData; fn apply_cmp(&self, cell_data: &Self::CellData, other_cell_data: &Self::CellData) -> Ordering; } /// Decode the opaque cell data into readable format content pub trait CellDataDecoder: TypeOption { /// /// 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, 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: String, decoded_field_type: &FieldType, field_rev: &FieldRevision, ) -> FlowyResult<::CellData>; /// Same as `decode_cell_data` does but Decode the cell data to readable `String` fn decode_cell_data_to_str( &self, cell_data: String, decoded_field_type: &FieldType, field_rev: &FieldRevision, ) -> FlowyResult; } 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<::CellChangeset>, cell_rev: Option, ) -> FlowyResult; } /// changeset: It will be deserialized into specific data base on the FieldType. /// For example, /// FieldType::RichText => String /// FieldType::SingleSelect => SelectOptionChangeset /// /// cell_rev: It will be None if the cell does not contain any data. pub fn apply_cell_data_changeset>( changeset: C, cell_rev: Option, field_rev: T, ) -> Result { let field_rev = field_rev.as_ref(); let changeset = changeset.to_string(); let field_type = field_rev.ty.into(); let s = match field_type { FieldType::RichText => RichTextTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev), FieldType::Number => NumberTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev), FieldType::DateTime => DateTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev), FieldType::SingleSelect => { SingleSelectTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev) } FieldType::MultiSelect => MultiSelectTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev), FieldType::Checklist => ChecklistTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev), FieldType::Checkbox => CheckboxTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev), FieldType::URL => URLTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev), }?; Ok(TypeCellData::new(s, field_type).to_json()) } pub fn decode_type_cell_data + Debug>( data: T, field_rev: &FieldRevision, ) -> (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, &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, CellProtobufBlob::default()) } } } Err(_err) => { // It's okay to ignore this error, because it's okay that the current cell can't // 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, CellProtobufBlob::default()) } } } /// Decode the opaque cell data from one field type to another using the corresponding type option builder /// /// 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: String, from_field_type: &FieldType, to_field_type: &FieldType, field_rev: &FieldRevision, ) -> FlowyResult { 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), } } 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), } } pub fn insert_text_cell(s: String, field_rev: &FieldRevision) -> CellRevision { let data = apply_cell_data_changeset(s, None, field_rev).unwrap(); CellRevision::new(data) } pub fn insert_number_cell(num: i64, field_rev: &FieldRevision) -> CellRevision { let data = apply_cell_data_changeset(num, None, field_rev).unwrap(); CellRevision::new(data) } pub fn insert_url_cell(url: String, field_rev: &FieldRevision) -> CellRevision { let data = apply_cell_data_changeset(url, None, field_rev).unwrap(); CellRevision::new(data) } pub fn insert_checkbox_cell(is_check: bool, field_rev: &FieldRevision) -> CellRevision { let s = if is_check { CHECK.to_string() } else { UNCHECK.to_string() }; let data = apply_cell_data_changeset(s, None, field_rev).unwrap(); CellRevision::new(data) } pub fn insert_date_cell(timestamp: i64, field_rev: &FieldRevision) -> CellRevision { let cell_data = serde_json::to_string(&DateCellChangeset { date: Some(timestamp.to_string()), time: None, is_utc: true, }) .unwrap(); let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap(); CellRevision::new(data) } pub fn insert_select_option_cell(option_ids: Vec, field_rev: &FieldRevision) -> CellRevision { let cell_data = SelectOptionCellChangeset::from_insert_options(option_ids).to_str(); let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap(); CellRevision::new(data) } pub fn delete_select_option_cell(option_ids: Vec, field_rev: &FieldRevision) -> CellRevision { let cell_data = SelectOptionCellChangeset::from_delete_options(option_ids).to_str(); let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap(); CellRevision::new(data) } /// Deserialize the String into cell specific data type. pub trait FromCellString { fn from_cell_str(s: &str) -> FlowyResult where Self: Sized; } /// IntoCellData is a helper struct used to deserialize string into a specific data type that implements /// the `FromCellString` trait. /// pub struct IntoCellData(pub Option); impl IntoCellData { pub fn try_into_inner(self) -> FlowyResult { match self.0 { None => Err(ErrorCode::InvalidData.into()), Some(data) => Ok(data), } } } impl std::convert::From for IntoCellData where T: FromCellString, { fn from(s: String) -> Self { match T::from_cell_str(&s) { Ok(inner) => IntoCellData(Some(inner)), Err(e) => { tracing::error!("Deserialize Cell Data failed: {}", e); IntoCellData(None) } } } } impl std::convert::From for IntoCellData { fn from(val: T) -> Self { IntoCellData(Some(val)) } } impl std::convert::From for IntoCellData { fn from(n: usize) -> Self { IntoCellData(Some(n.to_string())) } } impl std::convert::From> for String { fn from(p: IntoCellData) -> Self { p.try_into_inner().unwrap_or_else(|_| String::new()) } } /// If the changeset applying to the cell is not String type, it should impl this trait. /// Deserialize the string into cell specific changeset. pub trait FromCellChangeset { fn from_changeset(changeset: String) -> FlowyResult where Self: Sized; } pub struct AnyCellChangeset(pub Option); impl AnyCellChangeset { pub fn try_into_inner(self) -> FlowyResult { match self.0 { None => Err(ErrorCode::InvalidData.into()), Some(data) => Ok(data), } } } impl std::convert::From for AnyCellChangeset where T: FromCellChangeset, { fn from(changeset: C) -> Self { match T::from_changeset(changeset.to_string()) { Ok(data) => AnyCellChangeset(Some(data)), Err(e) => { tracing::error!("Deserialize CellDataChangeset failed: {}", e); AnyCellChangeset(None) } } } } impl std::convert::From for AnyCellChangeset { fn from(s: String) -> Self { AnyCellChangeset(Some(s)) } }