2023-04-28 14:08:53 +08:00
use std ::collections ::HashMap ;
2024-02-29 15:48:06 +08:00
use std ::str ::FromStr ;
2023-04-28 14:08:53 +08:00
use collab_database ::fields ::Field ;
use collab_database ::rows ::{ get_field_type_from_cell , Cell , Cells } ;
2024-02-22 07:12:52 +08:00
use flowy_error ::{ FlowyError , FlowyResult } ;
use lib_infra ::box_any ::BoxAny ;
2023-04-28 14:08:53 +08:00
2024-02-20 14:02:32 +08:00
use crate ::entities ::{ CheckboxCellDataPB , FieldType } ;
2023-04-28 14:08:53 +08:00
use crate ::services ::cell ::{ CellCache , CellProtobufBlob } ;
use crate ::services ::field ::* ;
use crate ::services ::group ::make_no_status_group ;
/// Decode the opaque cell data into readable format content
pub trait CellDataDecoder : TypeOption {
2024-04-27 10:20:32 +08:00
/// Decodes the [Cell] into a `CellData` of this `TypeOption`'s field type.
/// The `field_type` of the `Cell` should be the same as that of this
/// `TypeOption`.
///
/// # Arguments
///
/// * `cell`: the cell to be decoded
2023-04-28 14:08:53 +08:00
///
2024-04-11 14:49:36 +08:00
fn decode_cell ( & self , cell : & Cell ) -> FlowyResult < < Self as TypeOption > ::CellData > ;
2024-04-27 10:20:32 +08:00
/// Decodes the [Cell] that is of a particular field type into a `CellData` of this `TypeOption`'s field type.
2024-04-11 14:49:36 +08:00
///
/// # Arguments
///
2024-04-27 10:20:32 +08:00
/// * `cell`: the cell to be decoded
/// * `from_field_type`: the original field type of the `cell``
/// * `field`: the `Field` which this cell belongs to
2024-04-11 14:49:36 +08:00
///
fn decode_cell_with_transform (
2023-04-28 14:08:53 +08:00
& self ,
2024-04-11 14:49:36 +08:00
_cell : & Cell ,
_from_field_type : FieldType ,
_field : & Field ,
) -> Option < < Self as TypeOption > ::CellData > {
None
}
2023-04-28 14:08:53 +08:00
2024-04-27 10:20:32 +08:00
/// Decode the cell data to a readable `String`
2023-04-28 14:08:53 +08:00
/// For example, The string of the Multi-Select cell will be a list of the option's name
/// separated by a comma.
2024-04-27 10:20:32 +08:00
///
2023-05-30 09:41:33 +08:00
fn stringify_cell_data ( & self , cell_data : < Self as TypeOption > ::CellData ) -> String ;
2023-04-28 14:08:53 +08:00
2024-02-03 17:52:38 +01:00
/// Decode the cell into f64
/// Different field type has different way to decode the cell data into f64
/// If the field type doesn't support to decode the cell data into f64, it will return None
2024-04-27 10:20:32 +08:00
///
2024-02-03 17:52:38 +01:00
fn numeric_cell ( & self , cell : & Cell ) -> Option < f64 > ;
2023-04-28 14:08:53 +08:00
}
pub trait CellDataChangeset : TypeOption {
2024-04-27 10:20:32 +08:00
/// Applies a changeset to a given cell, returning the new `Cell` and
/// `TypeOption::CellData`
2024-02-22 07:12:52 +08:00
///
2023-05-30 09:41:33 +08:00
/// # Arguments
2023-04-28 14:08:53 +08:00
///
2023-05-30 09:41:33 +08:00
/// * `changeset`: the cell changeset that represents the changes of the cell.
2024-04-27 10:20:32 +08:00
/// * `cell`: the data of the cell. It might be `None`` if the cell does not contain any data.
///
2023-04-28 14:08:53 +08:00
fn apply_changeset (
& self ,
changeset : < Self as TypeOption > ::CellChangeset ,
cell : Option < Cell > ,
) -> FlowyResult < ( Cell , < Self as TypeOption > ::CellData ) > ;
}
2024-04-27 10:20:32 +08:00
/// Applies a cell changeset to a cell
///
/// Check `TypeOptionCellDataHandler::handle_cell_changeset` for more details
///
/// # Arguments
///
/// * `changeset`: The cell changeset to be applied
/// * `cell`: The cell to be changed
/// * `field`: The field which the cell belongs to
/// * `cell_data_cache`: for quickly getting cell data
2023-04-28 14:08:53 +08:00
///
2024-02-22 07:12:52 +08:00
pub fn apply_cell_changeset (
changeset : BoxAny ,
2023-04-28 14:08:53 +08:00
cell : Option < Cell > ,
field : & Field ,
cell_data_cache : Option < CellCache > ,
2023-05-09 22:37:20 +08:00
) -> Result < Cell , FlowyError > {
2024-04-11 14:49:36 +08:00
match TypeOptionCellExt ::new ( field , cell_data_cache ) . get_type_option_cell_data_handler ( ) {
2023-05-09 22:37:20 +08:00
None = > Ok ( Cell ::default ( ) ) ,
Some ( handler ) = > Ok ( handler . handle_cell_changeset ( changeset , cell , field ) ? ) ,
2023-04-28 14:08:53 +08:00
}
}
2024-04-27 10:20:32 +08:00
/// Gets the cell protobuf of a cell, returning default when parsing isn't
/// successful.
2023-04-28 14:08:53 +08:00
///
2024-04-27 10:20:32 +08:00
/// Check `TypeOptionCellDataHandler::handle_get_protobuf_cell_data` for more
/// details
2023-04-28 14:08:53 +08:00
///
/// # Arguments
///
2024-04-27 10:20:32 +08:00
/// * `cell`: The cell from which the protobuf should be created
/// * `field`: The field which the cell belongs to
/// * `cell_data_cache`: for quickly getting cell data
2023-04-28 14:08:53 +08:00
///
2024-04-27 10:20:32 +08:00
pub fn get_cell_protobuf (
2023-04-28 14:08:53 +08:00
cell : & Cell ,
field : & Field ,
cell_data_cache : Option < CellCache > ,
2024-04-27 10:20:32 +08:00
) -> CellProtobufBlob {
2024-04-11 14:49:36 +08:00
match TypeOptionCellExt ::new ( field , cell_data_cache ) . get_type_option_cell_data_handler ( ) {
2024-04-27 10:20:32 +08:00
None = > CellProtobufBlob ::default ( ) ,
Some ( handler ) = > handler
. handle_get_protobuf_cell_data ( cell , field )
. unwrap_or_default ( ) ,
2023-04-28 14:08:53 +08:00
}
}
2024-04-27 10:20:32 +08:00
/// Returns a string that represents the cell's data. Using the field type of the cell and the field's type option, create a TypeOptionCellDataHandler. Then,
/// get the cell data in that field type and stringify it.
2023-04-28 14:08:53 +08:00
///
/// # Arguments
///
2023-05-30 09:41:33 +08:00
/// * `cell`: the opaque cell string that can be decoded by corresponding structs
/// * `field`: used to get the corresponding TypeOption for the specified field type.
2023-04-28 14:08:53 +08:00
///
2024-04-11 14:49:36 +08:00
pub fn stringify_cell ( cell : & Cell , field : & Field ) -> String {
if let Some ( field_type_of_cell ) = get_field_type_from_cell ::< FieldType > ( cell ) {
TypeOptionCellExt ::new ( field , None )
. get_type_option_cell_data_handler_with_field_type ( field_type_of_cell )
. map ( | handler | handler . handle_stringify_cell ( cell , field ) )
. unwrap_or_default ( )
} else {
" " . to_string ( )
2023-04-28 14:08:53 +08:00
}
}
pub fn insert_text_cell ( s : String , field : & Field ) -> Cell {
2024-02-22 07:12:52 +08:00
apply_cell_changeset ( BoxAny ::new ( s ) , None , field , None ) . unwrap ( )
2023-04-28 14:08:53 +08:00
}
pub fn insert_number_cell ( num : i64 , field : & Field ) -> Cell {
2024-02-22 07:12:52 +08:00
apply_cell_changeset ( BoxAny ::new ( num . to_string ( ) ) , None , field , None ) . unwrap ( )
2023-04-28 14:08:53 +08:00
}
pub fn insert_url_cell ( url : String , field : & Field ) -> Cell {
// checking if url is equal to group id of no status group because everywhere
// except group of rows with empty url the group id is equal to the url
// so then on the case that url is equal to empty url group id we should change
// the url to empty string
let _no_status_group_id = make_no_status_group ( field ) . id ;
let url = match url {
a if a = = _no_status_group_id = > " " . to_owned ( ) ,
_ = > url ,
} ;
2024-02-22 07:12:52 +08:00
apply_cell_changeset ( BoxAny ::new ( url ) , None , field , None ) . unwrap ( )
2023-04-28 14:08:53 +08:00
}
2024-02-20 14:02:32 +08:00
pub fn insert_checkbox_cell ( is_checked : bool , field : & Field ) -> Cell {
let s = if is_checked {
2023-04-28 14:08:53 +08:00
CHECK . to_string ( )
} else {
UNCHECK . to_string ( )
} ;
2024-02-22 07:12:52 +08:00
apply_cell_changeset ( BoxAny ::new ( s ) , None , field , None ) . unwrap ( )
2023-04-28 14:08:53 +08:00
}
2024-02-22 07:12:52 +08:00
pub fn insert_date_cell (
timestamp : i64 ,
time : Option < String > ,
include_time : Option < bool > ,
field : & Field ,
) -> Cell {
let cell_data = DateCellChangeset {
2023-09-02 11:50:16 +08:00
date : Some ( timestamp ) ,
2024-02-22 07:12:52 +08:00
time ,
2023-05-26 14:04:17 +03:30
include_time ,
2023-09-19 09:58:15 +08:00
.. Default ::default ( )
2024-02-22 07:12:52 +08:00
} ;
apply_cell_changeset ( BoxAny ::new ( cell_data ) , None , field , None ) . unwrap ( )
2023-04-28 14:08:53 +08:00
}
pub fn insert_select_option_cell ( option_ids : Vec < String > , field : & Field ) -> Cell {
2024-02-22 07:12:52 +08:00
let changeset = SelectOptionCellChangeset ::from_insert_options ( option_ids ) ;
apply_cell_changeset ( BoxAny ::new ( changeset ) , None , field , None ) . unwrap ( )
2023-04-28 14:08:53 +08:00
}
2024-02-22 08:00:59 +08:00
pub fn insert_checklist_cell ( insert_options : Vec < ( String , bool ) > , field : & Field ) -> Cell {
2023-05-30 09:41:33 +08:00
let changeset = ChecklistCellChangeset {
insert_options ,
.. Default ::default ( )
2024-02-22 07:12:52 +08:00
} ;
apply_cell_changeset ( BoxAny ::new ( changeset ) , None , field , None ) . unwrap ( )
2023-05-30 09:41:33 +08:00
}
2023-04-28 14:08:53 +08:00
pub fn delete_select_option_cell ( option_ids : Vec < String > , field : & Field ) -> Cell {
2024-02-22 07:12:52 +08:00
let changeset = SelectOptionCellChangeset ::from_delete_options ( option_ids ) ;
apply_cell_changeset ( BoxAny ::new ( changeset ) , None , field , None ) . unwrap ( )
2023-04-28 14:08:53 +08:00
}
2023-05-25 23:22:23 +08:00
pub struct CellBuilder < ' a > {
2023-04-28 14:08:53 +08:00
cells : Cells ,
2023-05-25 23:22:23 +08:00
field_maps : HashMap < String , & ' a Field > ,
2023-04-28 14:08:53 +08:00
}
2023-05-25 23:22:23 +08:00
impl < ' a > CellBuilder < ' a > {
2023-05-26 14:04:17 +03:30
/// Build list of Cells from HashMap of cell string by field id.
2023-05-25 23:22:23 +08:00
pub fn with_cells ( cell_by_field_id : HashMap < String , String > , fields : & ' a [ Field ] ) -> Self {
2023-04-28 14:08:53 +08:00
let field_maps = fields
2023-05-31 17:42:14 +08:00
. iter ( )
2023-04-28 14:08:53 +08:00
. map ( | field | ( field . id . clone ( ) , field ) )
2023-05-25 23:22:23 +08:00
. collect ::< HashMap < String , & Field > > ( ) ;
2023-04-28 14:08:53 +08:00
let mut cells = Cells ::new ( ) ;
2023-06-05 09:42:11 +08:00
for ( field_id , cell_str ) in cell_by_field_id {
2023-04-28 14:08:53 +08:00
if let Some ( field ) = field_maps . get ( & field_id ) {
let field_type = FieldType ::from ( field . field_type ) ;
match field_type {
FieldType ::RichText = > {
cells . insert ( field_id , insert_text_cell ( cell_str , field ) ) ;
} ,
FieldType ::Number = > {
if let Ok ( num ) = cell_str . parse ::< i64 > ( ) {
cells . insert ( field_id , insert_number_cell ( num , field ) ) ;
}
} ,
2023-06-03 23:35:55 +08:00
FieldType ::DateTime = > {
2023-04-28 14:08:53 +08:00
if let Ok ( timestamp ) = cell_str . parse ::< i64 > ( ) {
2024-02-22 07:12:52 +08:00
cells . insert (
field_id ,
insert_date_cell ( timestamp , None , Some ( false ) , field ) ,
) ;
2023-04-28 14:08:53 +08:00
}
} ,
2023-06-03 23:35:55 +08:00
FieldType ::LastEditedTime | FieldType ::CreatedTime = > {
tracing ::warn! ( " Shouldn't insert cell data to cell whose field type is LastEditedTime or CreatedTime " ) ;
} ,
2023-04-28 14:08:53 +08:00
FieldType ::SingleSelect | FieldType ::MultiSelect = > {
2024-02-29 15:48:06 +08:00
if let Ok ( ids ) = SelectOptionIds ::from_str ( & cell_str ) {
2023-04-28 14:08:53 +08:00
cells . insert ( field_id , insert_select_option_cell ( ids . into_inner ( ) , field ) ) ;
}
} ,
FieldType ::Checkbox = > {
2024-02-29 15:48:06 +08:00
if let Ok ( value ) = CheckboxCellDataPB ::from_str ( & cell_str ) {
2024-02-20 14:02:32 +08:00
cells . insert ( field_id , insert_checkbox_cell ( value . is_checked , field ) ) ;
2023-04-28 14:08:53 +08:00
}
} ,
FieldType ::URL = > {
cells . insert ( field_id , insert_url_cell ( cell_str , field ) ) ;
} ,
FieldType ::Checklist = > {
2024-02-29 15:48:06 +08:00
if let Ok ( ids ) = SelectOptionIds ::from_str ( & cell_str ) {
2023-04-28 14:08:53 +08:00
cells . insert ( field_id , insert_select_option_cell ( ids . into_inner ( ) , field ) ) ;
}
} ,
2024-02-29 14:38:18 +08:00
FieldType ::Relation = > {
cells . insert ( field_id , ( & RelationCellData ::from ( cell_str ) ) . into ( ) ) ;
} ,
2024-05-05 22:04:34 +08:00
FieldType ::Summary = > {
cells . insert ( field_id , insert_text_cell ( cell_str , field ) ) ;
} ,
2023-04-28 14:08:53 +08:00
}
}
}
CellBuilder { cells , field_maps }
}
pub fn build ( self ) -> Cells {
self . cells
}
pub fn insert_text_cell ( & mut self , field_id : & str , data : String ) {
match self . field_maps . get ( & field_id . to_owned ( ) ) {
None = > tracing ::warn! ( " Can't find the text field with id: {} " , field_id ) ,
Some ( field ) = > {
self
. cells
. insert ( field_id . to_owned ( ) , insert_text_cell ( data , field ) ) ;
} ,
}
}
pub fn insert_url_cell ( & mut self , field_id : & str , data : String ) {
match self . field_maps . get ( & field_id . to_owned ( ) ) {
None = > tracing ::warn! ( " Can't find the url field with id: {} " , field_id ) ,
Some ( field ) = > {
self
. cells
. insert ( field_id . to_owned ( ) , insert_url_cell ( data , field ) ) ;
} ,
}
}
pub fn insert_number_cell ( & mut self , field_id : & str , num : i64 ) {
match self . field_maps . get ( & field_id . to_owned ( ) ) {
None = > tracing ::warn! ( " Can't find the number field with id: {} " , field_id ) ,
Some ( field ) = > {
self
. cells
. insert ( field_id . to_owned ( ) , insert_number_cell ( num , field ) ) ;
} ,
}
}
2024-02-20 14:02:32 +08:00
pub fn insert_checkbox_cell ( & mut self , field_id : & str , is_checked : bool ) {
2023-04-28 14:08:53 +08:00
match self . field_maps . get ( & field_id . to_owned ( ) ) {
None = > tracing ::warn! ( " Can't find the checkbox field with id: {} " , field_id ) ,
Some ( field ) = > {
self
. cells
2024-02-20 14:02:32 +08:00
. insert ( field_id . to_owned ( ) , insert_checkbox_cell ( is_checked , field ) ) ;
2023-04-28 14:08:53 +08:00
} ,
}
}
2024-02-22 07:12:52 +08:00
pub fn insert_date_cell (
& mut self ,
field_id : & str ,
timestamp : i64 ,
time : Option < String > ,
include_time : Option < bool > ,
) {
2023-04-28 14:08:53 +08:00
match self . field_maps . get ( & field_id . to_owned ( ) ) {
None = > tracing ::warn! ( " Can't find the date field with id: {} " , field_id ) ,
Some ( field ) = > {
2023-05-26 14:04:17 +03:30
self . cells . insert (
field_id . to_owned ( ) ,
2024-02-22 07:12:52 +08:00
insert_date_cell ( timestamp , time , include_time , field ) ,
2023-05-26 14:04:17 +03:30
) ;
2023-04-28 14:08:53 +08:00
} ,
}
}
pub fn insert_select_option_cell ( & mut self , field_id : & str , option_ids : Vec < String > ) {
match self . field_maps . get ( & field_id . to_owned ( ) ) {
None = > tracing ::warn! ( " Can't find the select option field with id: {} " , field_id ) ,
Some ( field ) = > {
self . cells . insert (
field_id . to_owned ( ) ,
insert_select_option_cell ( option_ids , field ) ,
) ;
} ,
}
}
2024-02-22 08:00:59 +08:00
pub fn insert_checklist_cell ( & mut self , field_id : & str , options : Vec < ( String , bool ) > ) {
2023-05-30 09:41:33 +08:00
match self . field_maps . get ( & field_id . to_owned ( ) ) {
None = > tracing ::warn! ( " Can't find the field with id: {} " , field_id ) ,
Some ( field ) = > {
2024-02-22 08:00:59 +08:00
self
. cells
. insert ( field_id . to_owned ( ) , insert_checklist_cell ( options , field ) ) ;
2023-05-30 09:41:33 +08:00
} ,
}
}
2023-04-28 14:08:53 +08:00
}