mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: transform cell data
This commit is contained in:
parent
2262dd0d7b
commit
ca45673a9e
@ -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>> {
|
||||
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1 @@
|
||||
// #[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
|
||||
// pub struct TypeOptionData {
|
||||
// #[pb(index = 1)]
|
||||
// pub map: HashMap<FieldType, String>,
|
||||
// }
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user