diff --git a/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs b/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs index 58e8e2ca9e..353185f498 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs @@ -148,9 +148,10 @@ impl GridBlockMetaEditorManager { } // Optimization: Using the shared memory(Arc, Cow,etc.) to reduce memory usage. + #[allow(dead_code)] pub async fn get_cell_metas( &self, - block_ids: Option<String>, + block_ids: Vec<String>, field_id: &str, row_ids: Option<Vec<String>>, ) -> FlowyResult<Vec<CellMeta>> { diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs index 3611f5a0db..84d693edd0 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs @@ -1,11 +1,12 @@ use crate::impl_type_option; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::CellDataSerde; +use crate::services::row::{CellDataSerde, TypeOptionCellData}; use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::FlowyError; use flowy_grid_data_model::entities::{FieldMeta, FieldType, TypeOptionDataEntity, TypeOptionDataEntry}; use serde::{Deserialize, Serialize}; +use std::str::FromStr; #[derive(Default)] pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption); @@ -36,17 +37,30 @@ pub struct CheckboxTypeOption { } impl_type_option!(CheckboxTypeOption, FieldType::Checkbox); +const YES: &str = "Yes"; +const NO: &str = "No"; + impl CellDataSerde for CheckboxTypeOption { - fn deserialize_cell_data(&self, data: String) -> String { - data + fn deserialize_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String { + if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { + if !type_option_cell_data.is_text() || !type_option_cell_data.is_checkbox() { + return String::new(); + } + let cell_data = type_option_cell_data.data; + if cell_data == YES || cell_data == NO { + return cell_data; + } + } + + String::new() } fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> { let s = match string_to_bool(data) { - true => "No", - false => "Yes", + true => YES, + false => NO, }; - Ok(s.to_owned()) + Ok(TypeOptionCellData::new(s, self.field_type()).json()) } } @@ -66,11 +80,15 @@ fn string_to_bool(bool_str: &str) -> bool { #[cfg(test)] mod tests { use crate::services::field::CheckboxTypeOption; + use crate::services::field::FieldBuilder; use crate::services::row::CellDataSerde; + use flowy_grid_data_model::entities::FieldType; #[test] fn checkout_box_description_test() { let type_option = CheckboxTypeOption::default(); + let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); + assert_eq!(type_option.serialize_cell_data("true").unwrap(), "1".to_owned()); assert_eq!(type_option.serialize_cell_data("1").unwrap(), "1".to_owned()); assert_eq!(type_option.serialize_cell_data("yes").unwrap(), "1".to_owned()); @@ -79,6 +97,9 @@ mod tests { assert_eq!(type_option.serialize_cell_data("no").unwrap(), "0".to_owned()); assert_eq!(type_option.serialize_cell_data("123").unwrap(), "0".to_owned()); - assert_eq!(type_option.deserialize_cell_data("1".to_owned()), "1".to_owned()); + assert_eq!( + type_option.deserialize_cell_data("1".to_owned(), &field_meta), + "1".to_owned() + ); } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs index 86c15e13c5..5dd88a4a67 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs @@ -1,5 +1,5 @@ use crate::impl_type_option; -use crate::services::row::CellDataSerde; +use crate::services::row::{CellDataSerde, TypeOptionCellData}; use bytes::Bytes; use chrono::format::strftime::StrftimeItems; use chrono::NaiveDateTime; @@ -7,6 +7,7 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::FlowyError; use flowy_grid_data_model::entities::{FieldMeta, FieldType, TypeOptionDataEntity, TypeOptionDataEntry}; use serde::{Deserialize, Serialize}; +use std::str::FromStr; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use strum_macros::EnumIter; @@ -32,25 +33,34 @@ impl DateTypeOption { fn today_from_native(&self, naive: chrono::NaiveDateTime) -> String { let utc: chrono::DateTime<chrono::Utc> = chrono::DateTime::from_utc(naive, chrono::Utc); let local: chrono::DateTime<chrono::Local> = chrono::DateTime::from(utc); - - let fmt_str = format!("{} {}", self.date_format.format_str(), self.time_format.format_str()); - let output = format!("{}", local.format_with_items(StrftimeItems::new(&fmt_str))); + let output = format!("{}", local.format_with_items(StrftimeItems::new(&self.fmt_str()))); output } + + fn fmt_str(&self) -> String { + format!("{} {}", self.date_format.format_str(), self.time_format.format_str()) + } } impl CellDataSerde for DateTypeOption { - fn deserialize_cell_data(&self, data: String) -> String { - match data.parse::<i64>() { - Ok(timestamp) => { - let native = NaiveDateTime::from_timestamp(timestamp, 0); - self.today_from_native(native) + fn deserialize_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String { + if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { + if !type_option_cell_data.is_date() { + return String::new(); } - Err(e) => { - tracing::debug!("DateDescription format {} fail. error: {:?}", data, e); - String::new() + + let cell_data = type_option_cell_data.data; + if let Ok(timestamp) = cell_data.parse::<i64>() { + let native = NaiveDateTime::from_timestamp(timestamp, 0); + return self.today_from_native(native); + } + + if NaiveDateTime::parse_from_str(&cell_data, &self.fmt_str()).is_ok() { + return cell_data; } } + + String::new() } fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> { @@ -58,7 +68,8 @@ impl CellDataSerde for DateTypeOption { tracing::error!("Parse {} to i64 failed: {}", data, e); return Err(FlowyError::internal().context(e)); }; - Ok(data.to_owned()) + + Ok(TypeOptionCellData::new(data, self.field_type()).json()) } } @@ -172,56 +183,59 @@ impl std::default::Default for TimeFormat { #[cfg(test)] mod tests { + use crate::services::field::FieldBuilder; use crate::services::field::{DateFormat, DateTypeOption, TimeFormat}; - use crate::services::row::CellDataSerde; + use crate::services::row::{CellDataSerde, TypeOptionCellData}; + use flowy_grid_data_model::entities::FieldType; use strum::IntoEnumIterator; #[test] - fn date_description_date_format_test() { - let mut description = DateTypeOption::default(); - let _timestamp = 1647251762; + fn date_description_invalid_input_test() { + let type_option = DateTypeOption::default(); + let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); + assert_eq!( + "".to_owned(), + type_option.deserialize_cell_data("1e".to_owned(), &field_meta) + ); + } + #[test] + fn date_description_date_format_test() { + let mut type_option = DateTypeOption::default(); + let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); for date_format in DateFormat::iter() { - description.date_format = date_format; + type_option.date_format = date_format; match date_format { DateFormat::Friendly => { assert_eq!( "Mar 14,2022 17:56".to_owned(), - description.today_from_timestamp(1647251762) + type_option.deserialize_cell_data(data("1647251762"), &field_meta) ); assert_eq!( "Mar 14,2022 17:56".to_owned(), - description.deserialize_cell_data("1647251762".to_owned()) + type_option.deserialize_cell_data(data("Mar 14,2022 17:56"), &field_meta) ); } DateFormat::US => { assert_eq!( "2022/03/14 17:56".to_owned(), - description.today_from_timestamp(1647251762) + type_option.deserialize_cell_data(data("1647251762"), &field_meta) ); assert_eq!( "2022/03/14 17:56".to_owned(), - description.deserialize_cell_data("1647251762".to_owned()) + type_option.deserialize_cell_data(data("2022/03/14 17:56"), &field_meta) ); } DateFormat::ISO => { assert_eq!( "2022-03-14 17:56".to_owned(), - description.today_from_timestamp(1647251762) - ); - assert_eq!( - "2022-03-14 17:56".to_owned(), - description.deserialize_cell_data("1647251762".to_owned()) + type_option.deserialize_cell_data(data("1647251762"), &field_meta) ); } DateFormat::Local => { assert_eq!( "2022/03/14 17:56".to_owned(), - description.today_from_timestamp(1647251762) - ); - assert_eq!( - "2022/03/14 17:56".to_owned(), - description.deserialize_cell_data("1647251762".to_owned()) + type_option.deserialize_cell_data(data("1647251762"), &field_meta) ); } } @@ -230,28 +244,29 @@ mod tests { #[test] fn date_description_time_format_test() { - let mut description = DateTypeOption::default(); + let mut type_option = DateTypeOption::default(); + let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); for time_format in TimeFormat::iter() { - description.time_format = time_format; + type_option.time_format = time_format; match time_format { TimeFormat::TwentyFourHour => { assert_eq!( "Mar 14,2022 17:56".to_owned(), - description.today_from_timestamp(1647251762) + type_option.today_from_timestamp(1647251762) ); assert_eq!( "Mar 14,2022 17:56".to_owned(), - description.deserialize_cell_data("1647251762".to_owned()) + type_option.deserialize_cell_data(data("1647251762"), &field_meta) ); } TimeFormat::TwelveHour => { assert_eq!( "Mar 14,2022 05:56:02 PM".to_owned(), - description.today_from_timestamp(1647251762) + type_option.today_from_timestamp(1647251762) ); assert_eq!( "Mar 14,2022 05:56:02 PM".to_owned(), - description.deserialize_cell_data("1647251762".to_owned()) + type_option.deserialize_cell_data(data("1647251762"), &field_meta) ); } } @@ -264,4 +279,8 @@ mod tests { let type_option = DateTypeOption::default(); type_option.serialize_cell_data("he").unwrap(); } + + fn data(s: &str) -> String { + TypeOptionCellData::new(s, FieldType::DateTime).json() + } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs index 7e557955df..7cee8a3b39 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs @@ -1,10 +1,10 @@ use crate::impl_type_option; -use crate::services::row::CellDataSerde; +use crate::services::row::{CellDataSerde, TypeOptionCellData}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::FlowyError; use flowy_grid_data_model::entities::{FieldMeta, FieldType, TypeOptionDataEntity, TypeOptionDataEntry}; use lazy_static::lazy_static; -use rust_decimal::prelude::Zero; + use rust_decimal::Decimal; use rusty_money::iso::{Currency, CNY, EUR, USD}; use serde::{Deserialize, Serialize}; @@ -76,6 +76,42 @@ pub struct NumberTypeOption { } impl_type_option!(NumberTypeOption, FieldType::Number); +impl CellDataSerde for NumberTypeOption { + fn deserialize_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String { + if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { + if type_option_cell_data.is_date() { + return String::new(); + } + + let cell_data = type_option_cell_data.data; + match self.format { + NumberFormat::Number => { + if cell_data.parse::<i64>().is_ok() { + cell_data + } else { + String::new() + } + } + NumberFormat::USD => self.money_from_str(&cell_data, USD), + NumberFormat::CNY => self.money_from_str(&cell_data, CNY), + NumberFormat::EUR => self.money_from_str(&cell_data, EUR), + } + } else { + String::new() + } + } + + fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> { + let data = self.strip_symbol(data); + + if !data.chars().all(char::is_numeric) { + return Err(FlowyError::invalid_data().context("Should only contain numbers")); + } + + Ok(TypeOptionCellData::new(&data, self.field_type()).json()) + } +} + impl std::default::Default for NumberTypeOption { fn default() -> Self { let format = NumberFormat::default(); @@ -96,22 +132,21 @@ impl NumberTypeOption { self.symbol = format.symbol(); } - fn decimal_from_str(&self, s: &str) -> Decimal { - let mut decimal = Decimal::from_str(s).unwrap_or_else(|_| Decimal::zero()); - match decimal.set_scale(self.scale) { - Ok(_) => {} - Err(e) => { - tracing::error!("Set decimal scale failed: {:?}", e); - } - } - decimal.set_sign_positive(self.sign_positive); - decimal - } - fn money_from_str(&self, s: &str, currency: &'static Currency) -> String { - let decimal = self.decimal_from_str(s); - let money = rusty_money::Money::from_decimal(decimal, currency); - money.to_string() + match Decimal::from_str(s) { + Ok(mut decimal) => { + match decimal.set_scale(self.scale) { + Ok(_) => {} + Err(e) => { + tracing::error!("Set decimal scale failed: {:?}", e); + } + } + decimal.set_sign_positive(self.sign_positive); + let money = rusty_money::Money::from_decimal(decimal, currency); + money.to_string() + } + Err(_) => String::new(), + } } fn strip_symbol(&self, s: &str) -> String { @@ -158,26 +193,6 @@ impl NumberFormat { } } -impl CellDataSerde for NumberTypeOption { - fn deserialize_cell_data(&self, data: String) -> String { - match self.format { - NumberFormat::Number => data, - NumberFormat::USD => self.money_from_str(&data, USD), - NumberFormat::CNY => self.money_from_str(&data, CNY), - NumberFormat::EUR => self.money_from_str(&data, EUR), - } - } - - fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> { - let data = self.strip_symbol(data); - - if !data.chars().all(char::is_numeric) { - return Err(FlowyError::invalid_data().context("Should only contain numbers")); - } - Ok(data) - } -} - fn make_strip_symbol() -> Vec<String> { let mut symbols = vec![",".to_owned(), ".".to_owned()]; for format in NumberFormat::iter() { @@ -188,41 +203,60 @@ fn make_strip_symbol() -> Vec<String> { #[cfg(test)] mod tests { + use crate::services::field::FieldBuilder; use crate::services::field::{NumberFormat, NumberTypeOption}; - use crate::services::row::CellDataSerde; + use crate::services::row::{CellDataSerde, TypeOptionCellData}; + use flowy_grid_data_model::entities::FieldType; use strum::IntoEnumIterator; + #[test] + fn number_description_invalid_input_test() { + let type_option = NumberTypeOption::default(); + let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); + assert_eq!("".to_owned(), type_option.deserialize_cell_data(data(""), &field_meta)); + assert_eq!( + "".to_owned(), + type_option.deserialize_cell_data(data("abc"), &field_meta) + ); + } + #[test] fn number_description_test() { let mut type_option = NumberTypeOption::default(); - assert_eq!(type_option.serialize_cell_data("¥18,443").unwrap(), "18443".to_owned()); - assert_eq!(type_option.serialize_cell_data("$18,443").unwrap(), "18443".to_owned()); - assert_eq!(type_option.serialize_cell_data("€18.443").unwrap(), "18443".to_owned()); + let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); + assert_eq!(type_option.strip_symbol("¥18,443"), "18443".to_owned()); + assert_eq!(type_option.strip_symbol("$18,443"), "18443".to_owned()); + assert_eq!(type_option.strip_symbol("€18.443"), "18443".to_owned()); for format in NumberFormat::iter() { type_option.format = format; match format { NumberFormat::Number => { assert_eq!( - type_option.deserialize_cell_data("18443".to_owned()), + type_option.deserialize_cell_data(data("18443"), &field_meta), "18443".to_owned() ); } NumberFormat::USD => { assert_eq!( - type_option.deserialize_cell_data("18443".to_owned()), + type_option.deserialize_cell_data(data("18443"), &field_meta), "$18,443".to_owned() ); + assert_eq!(type_option.deserialize_cell_data(data(""), &field_meta), "".to_owned()); + assert_eq!( + type_option.deserialize_cell_data(data("abc"), &field_meta), + "".to_owned() + ); } NumberFormat::CNY => { assert_eq!( - type_option.deserialize_cell_data("18443".to_owned()), + type_option.deserialize_cell_data(data("18443"), &field_meta), "¥18,443".to_owned() ); } NumberFormat::EUR => { assert_eq!( - type_option.deserialize_cell_data("18443".to_owned()), + type_option.deserialize_cell_data(data("18443"), &field_meta), "€18.443".to_owned() ); } @@ -230,37 +264,42 @@ mod tests { } } + fn data(s: &str) -> String { + TypeOptionCellData::new(s, FieldType::Number).json() + } + #[test] fn number_description_scale_test() { let mut type_option = NumberTypeOption { scale: 1, ..Default::default() }; + let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); for format in NumberFormat::iter() { type_option.format = format; match format { NumberFormat::Number => { assert_eq!( - type_option.deserialize_cell_data("18443".to_owned()), + type_option.deserialize_cell_data(data("18443"), &field_meta), "18443".to_owned() ); } NumberFormat::USD => { assert_eq!( - type_option.deserialize_cell_data("18443".to_owned()), + type_option.deserialize_cell_data(data("18443"), &field_meta), "$1,844.3".to_owned() ); } NumberFormat::CNY => { assert_eq!( - type_option.deserialize_cell_data("18443".to_owned()), + type_option.deserialize_cell_data(data("18443"), &field_meta), "¥1,844.3".to_owned() ); } NumberFormat::EUR => { assert_eq!( - type_option.deserialize_cell_data("18443".to_owned()), + type_option.deserialize_cell_data(data("18443"), &field_meta), "€1.844,3".to_owned() ); } @@ -274,31 +313,32 @@ mod tests { sign_positive: false, ..Default::default() }; + let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); for format in NumberFormat::iter() { type_option.format = format; match format { NumberFormat::Number => { assert_eq!( - type_option.deserialize_cell_data("18443".to_owned()), + type_option.deserialize_cell_data(data("18443"), &field_meta), "18443".to_owned() ); } NumberFormat::USD => { assert_eq!( - type_option.deserialize_cell_data("18443".to_owned()), + type_option.deserialize_cell_data(data("18443"), &field_meta), "-$18,443".to_owned() ); } NumberFormat::CNY => { assert_eq!( - type_option.deserialize_cell_data("18443".to_owned()), + type_option.deserialize_cell_data(data("18443"), &field_meta), "-¥18,443".to_owned() ); } NumberFormat::EUR => { assert_eq!( - type_option.deserialize_cell_data("18443".to_owned()), + type_option.deserialize_cell_data(data("18443"), &field_meta), "-€18.443".to_owned() ); } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs index abc9c81288..5768a92f70 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs @@ -1,6 +1,6 @@ use crate::impl_type_option; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::CellDataSerde; +use crate::services::row::{CellDataSerde, TypeOptionCellData}; use crate::services::util::*; use bytes::Bytes; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; @@ -8,6 +8,7 @@ use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{FieldMeta, FieldType, TypeOptionDataEntity, TypeOptionDataEntry}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use serde::{Deserialize, Serialize}; +use std::str::FromStr; use uuid::Uuid; pub const SELECTION_IDS_SEPARATOR: &str = ","; @@ -24,12 +25,25 @@ pub struct SingleSelectTypeOption { impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect); impl CellDataSerde for SingleSelectTypeOption { - fn deserialize_cell_data(&self, data: String) -> String { - data + fn deserialize_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String { + if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { + if !type_option_cell_data.is_single_select() || !type_option_cell_data.is_multi_select() { + return String::new(); + } + + let option_id = type_option_cell_data.data; + match self.options.iter().find(|option| option.id == option_id) { + None => String::new(), + Some(option) => option.name.clone(), + } + } else { + String::new() + } } fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> { - single_select_option_id_from_data(data.to_owned()) + let data = single_select_option_id_from_data(data.to_owned())?; + Ok(TypeOptionCellData::new(&data, self.field_type()).json()) } } @@ -67,12 +81,32 @@ pub struct MultiSelectTypeOption { impl_type_option!(MultiSelectTypeOption, FieldType::MultiSelect); impl CellDataSerde for MultiSelectTypeOption { - fn deserialize_cell_data(&self, data: String) -> String { - data + fn deserialize_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String { + if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { + if !type_option_cell_data.is_single_select() || !type_option_cell_data.is_multi_select() { + return String::new(); + } + + match select_option_ids(type_option_cell_data.data) { + Ok(option_ids) => { + // + self.options + .iter() + .filter(|option| option_ids.contains(&option.id)) + .map(|option| option.name.clone()) + .collect::<Vec<String>>() + .join(SELECTION_IDS_SEPARATOR) + } + Err(_) => String::new(), + } + } else { + String::new() + } } fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> { - multi_select_option_id_from_data(data.to_owned()) + let data = multi_select_option_id_from_data(data.to_owned())?; + Ok(TypeOptionCellData::new(&data, self.field_type()).json()) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs index 6bffc8606f..87434a3cdb 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs @@ -1,11 +1,12 @@ use crate::impl_type_option; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::CellDataSerde; +use crate::services::row::{deserialize_cell_data, CellDataSerde, TypeOptionCellData}; use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::FlowyError; use flowy_grid_data_model::entities::{FieldMeta, FieldType, TypeOptionDataEntity, TypeOptionDataEntry}; use serde::{Deserialize, Serialize}; +use std::str::FromStr; #[derive(Default)] pub struct RichTextTypeOptionBuilder(RichTextTypeOption); @@ -30,8 +31,20 @@ pub struct RichTextTypeOption { impl_type_option!(RichTextTypeOption, FieldType::RichText); impl CellDataSerde for RichTextTypeOption { - fn deserialize_cell_data(&self, data: String) -> String { - data + fn deserialize_cell_data(&self, data: String, field_meta: &FieldMeta) -> String { + if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { + if type_option_cell_data.is_date() + || type_option_cell_data.is_single_select() + || type_option_cell_data.is_multi_select() + || type_option_cell_data.is_number() + { + deserialize_cell_data(data, field_meta).unwrap_or_else(|_| "".to_owned()) + } else { + type_option_cell_data.data + } + } else { + String::new() + } } fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> { @@ -39,7 +52,68 @@ impl CellDataSerde for RichTextTypeOption { if data.len() > 10000 { Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000")) } else { - Ok(data) + Ok(TypeOptionCellData::new(&data, self.field_type()).json()) } } } + +#[cfg(test)] +mod tests { + use crate::services::field::FieldBuilder; + use crate::services::field::*; + use crate::services::row::{CellDataSerde, TypeOptionCellData}; + use flowy_grid_data_model::entities::FieldType; + + #[test] + fn text_description_test() { + let type_option = RichTextTypeOption::default(); + + // date + let date_time_field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); + let data = TypeOptionCellData::new("1647251762", FieldType::DateTime).json(); + assert_eq!( + type_option.deserialize_cell_data(data, &date_time_field_meta), + "Mar 14,2022 17:56".to_owned() + ); + + // Single select + let done_option = SelectOption::new("Done"); + let done_option_id = done_option.id.clone(); + let single_select = SingleSelectTypeOptionBuilder::default().option(done_option); + let single_select_field_meta = FieldBuilder::new(single_select).build(); + let data = TypeOptionCellData::new(&done_option_id, FieldType::SingleSelect).json(); + assert_eq!( + type_option.deserialize_cell_data(data, &single_select_field_meta), + "Done".to_owned() + ); + + // Multiple select + let google_option = SelectOption::new("Google"); + let google_option_id = google_option.id.clone(); + let facebook_option = SelectOption::new("Facebook"); + let face_option_id = facebook_option.id.clone(); + let multi_select = MultiSelectTypeOptionBuilder::default() + .option(google_option) + .option(facebook_option) + .option(SelectOption::new("Twitter")); + let multi_select_field_meta = FieldBuilder::new(multi_select).build(); + let data = TypeOptionCellData::new( + &vec![google_option_id, face_option_id].join(SELECTION_IDS_SEPARATOR), + FieldType::SingleSelect, + ) + .json(); + assert_eq!( + type_option.deserialize_cell_data(data, &multi_select_field_meta), + "Google,Facebook".to_owned() + ); + + //Number + let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); + let number_field_meta = FieldBuilder::new(number).build(); + let data = TypeOptionCellData::new("18443", FieldType::Number).json(); + assert_eq!( + type_option.deserialize_cell_data(data, &number_field_meta), + "$18,443".to_owned() + ); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option_data.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option_data.rs index fbba14feab..8b13789179 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option_data.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/type_option_data.rs @@ -1,5 +1 @@ -// #[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] -// pub struct TypeOptionData { -// #[pb(index = 1)] -// pub map: HashMap<FieldType, String>, -// } + diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index 88738aa082..e7d57634b8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -114,7 +114,16 @@ impl ClientGridEditor { } pub async fn switch_to_field_type(&self, field_id: &str, field_type: &FieldType) -> FlowyResult<()> { - // let cell_metas = self.block_meta_manager.get_cell_metas(None, field_id, None).await?; + // let block_ids = self + // .get_block_metas() + // .await? + // .into_iter() + // .map(|block_meta| block_meta.block_id) + // .collect(); + // let cell_metas = self + // .block_meta_manager + // .get_cell_metas(block_ids, field_id, None) + // .await?; let type_option_json_builder = |field_type: &FieldType| -> String { return default_type_option_builder_from_type(field_type).entry().json_str(); @@ -290,15 +299,16 @@ impl ClientGridEditor { } pub async fn grid_block_snapshots(&self, block_ids: Option<Vec<String>>) -> FlowyResult<Vec<GridBlockSnapshot>> { - let block_ids = block_ids.unwrap_or( - self.pad + let block_ids = match block_ids { + None => self.pad .read() .await .get_block_metas() .into_iter() .map(|block_meta| block_meta.block_id) .collect::<Vec<String>>(), - ); + Some(block_ids) => block_ids, + }; let snapshots = self.block_meta_manager.make_block_snapshots(block_ids).await?; Ok(snapshots) } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_serde.rs b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_serde.rs index 1e60583df8..259e052ab7 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_serde.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_serde.rs @@ -1,12 +1,65 @@ use crate::services::field::*; use flowy_error::FlowyError; use flowy_grid_data_model::entities::{FieldMeta, FieldType}; +use serde::{Deserialize, Serialize}; pub trait CellDataSerde { - fn deserialize_cell_data(&self, data: String) -> String; + fn deserialize_cell_data(&self, data: String, field_meta: &FieldMeta) -> String; fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError>; } +#[derive(Serialize, Deserialize)] +pub struct TypeOptionCellData { + pub data: String, + pub field_type: FieldType, +} + +impl std::str::FromStr for TypeOptionCellData { + type Err = FlowyError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let type_option_cell_data: TypeOptionCellData = serde_json::from_str(s)?; + Ok(type_option_cell_data) + } +} + +impl TypeOptionCellData { + pub fn new(data: &str, field_type: FieldType) -> Self { + TypeOptionCellData { + data: data.to_owned(), + field_type, + } + } + + pub fn json(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| "".to_owned()) + } + + pub fn is_number(&self) -> bool { + self.field_type == FieldType::Number + } + + pub fn is_text(&self) -> bool { + self.field_type == FieldType::RichText + } + + pub fn is_checkbox(&self) -> bool { + self.field_type == FieldType::Checkbox + } + + pub fn is_date(&self) -> bool { + self.field_type == FieldType::DateTime + } + + pub fn is_single_select(&self) -> bool { + self.field_type == FieldType::SingleSelect + } + + pub fn is_multi_select(&self) -> bool { + self.field_type == FieldType::MultiSelect + } +} + #[allow(dead_code)] pub fn serialize_cell_data(data: &str, field_meta: &FieldMeta) -> Result<String, FlowyError> { match field_meta.field_type { @@ -21,12 +74,12 @@ pub fn serialize_cell_data(data: &str, field_meta: &FieldMeta) -> Result<String, pub fn deserialize_cell_data(data: String, field_meta: &FieldMeta) -> Result<String, FlowyError> { let s = match field_meta.field_type { - FieldType::RichText => RichTextTypeOption::from(field_meta).deserialize_cell_data(data), - FieldType::Number => NumberTypeOption::from(field_meta).deserialize_cell_data(data), - FieldType::DateTime => DateTypeOption::from(field_meta).deserialize_cell_data(data), - FieldType::SingleSelect => SingleSelectTypeOption::from(field_meta).deserialize_cell_data(data), - FieldType::MultiSelect => MultiSelectTypeOption::from(field_meta).deserialize_cell_data(data), - FieldType::Checkbox => CheckboxTypeOption::from(field_meta).deserialize_cell_data(data), + FieldType::RichText => RichTextTypeOption::from(field_meta).deserialize_cell_data(data, field_meta), + FieldType::Number => NumberTypeOption::from(field_meta).deserialize_cell_data(data, field_meta), + FieldType::DateTime => DateTypeOption::from(field_meta).deserialize_cell_data(data, field_meta), + FieldType::SingleSelect => SingleSelectTypeOption::from(field_meta).deserialize_cell_data(data, field_meta), + FieldType::MultiSelect => MultiSelectTypeOption::from(field_meta).deserialize_cell_data(data, field_meta), + FieldType::Checkbox => CheckboxTypeOption::from(field_meta).deserialize_cell_data(data, field_meta), }; Ok(s) } diff --git a/shared-lib/flowy-grid-data-model/src/entities/meta.rs b/shared-lib/flowy-grid-data-model/src/entities/meta.rs index edb5236139..a543085a78 100644 --- a/shared-lib/flowy-grid-data-model/src/entities/meta.rs +++ b/shared-lib/flowy-grid-data-model/src/entities/meta.rs @@ -246,6 +246,7 @@ impl TryInto<FieldChangesetParams> for FieldChangesetPayload { #[derive( Debug, Clone, PartialEq, Eq, ProtoBuf_Enum, EnumCountMacro, EnumString, EnumIter, Display, Serialize, Deserialize, )] +#[repr(u8)] pub enum FieldType { RichText = 0, Number = 1, @@ -278,18 +279,6 @@ impl FieldType { let ty = self.clone(); format!("{}", ty as u8) } - - pub fn from_type_id(type_id: &str) -> Result<FieldType, String> { - match type_id { - "0" => Ok(FieldType::RichText), - "1" => Ok(FieldType::Number), - "2" => Ok(FieldType::DateTime), - "3" => Ok(FieldType::SingleSelect), - "4" => Ok(FieldType::MultiSelect), - "5" => Ok(FieldType::Checkbox), - _ => Err(format!("Invalid type_id: {}", type_id)), - } - } } #[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] diff --git a/shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs index 0b9459a625..c0b86ac4af 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs @@ -5,7 +5,7 @@ use flowy_grid_data_model::entities::{CellMeta, GridBlockMetaData, RowMeta, RowM use lib_infra::uuid; use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder}; use serde::{Deserialize, Serialize}; -use std::borrow::Cow; + use std::collections::HashMap; use std::sync::Arc;