refactor: provide default type option transform (#1579)

This commit is contained in:
Nathan.fooo
2022-12-17 18:12:47 +08:00
committed by GitHub
parent 85e489babb
commit 67e350e797
15 changed files with 253 additions and 144 deletions

View File

@ -9,24 +9,6 @@ pub trait TypeOptionBuilder {
/// Returns a serializer that can be used to serialize the type-option data /// Returns a serializer that can be used to serialize the type-option data
fn serializer(&self) -> &dyn TypeOptionDataSerializer; fn serializer(&self) -> &dyn TypeOptionDataSerializer;
/// Transform the data from passed-in type-option to current type-option
///
/// The current type-option data may be changed if it supports transform
/// the data from the other kind of type-option data.
///
/// For example, when switching from `checkbox` type-option to `single-select`
/// type-option, adding the `Yes` option if the `single-select` type-option doesn't contain it.
/// But the cell content is a string, `Yes`, it's need to do the cell content transform.
/// The `Yes` string will be transformed to the `Yes` option id.
///
///
/// # Arguments
///
/// * `field_type`: represents as the field type of the passed-in type-option data
/// * `type_option_data`: passed-in type-option data
//
fn transform(&mut self, field_type: &FieldType, type_option_data: String);
} }
pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box<dyn TypeOptionBuilder> { pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box<dyn TypeOptionBuilder> {

View File

@ -3,6 +3,7 @@ use crate::impl_type_option;
use crate::services::cell::{AnyCellChangeset, CellDataChangeset, CellDataDecoder, FromCellString}; use crate::services::cell::{AnyCellChangeset, CellDataChangeset, CellDataDecoder, FromCellString};
use crate::services::field::{ use crate::services::field::{
BoxTypeOptionBuilder, CheckboxCellData, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration, BoxTypeOptionBuilder, CheckboxCellData, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
TypeOptionTransform,
}; };
use bytes::Bytes; use bytes::Bytes;
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
@ -31,10 +32,6 @@ impl TypeOptionBuilder for CheckboxTypeOptionBuilder {
fn serializer(&self) -> &dyn TypeOptionDataSerializer { fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0 &self.0
} }
fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
// Do nothing
}
} }
#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] #[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
@ -47,15 +44,17 @@ impl_type_option!(CheckboxTypeOptionPB, FieldType::Checkbox);
impl TypeOption for CheckboxTypeOptionPB { impl TypeOption for CheckboxTypeOptionPB {
type CellData = CheckboxCellData; type CellData = CheckboxCellData;
type CellChangeset = CheckboxCellChangeset; type CellChangeset = CheckboxCellChangeset;
type CellPBType = CheckboxCellData; type CellProtobufType = CheckboxCellData;
} }
impl TypeOptionTransform for CheckboxTypeOptionPB {}
impl TypeOptionConfiguration for CheckboxTypeOptionPB { impl TypeOptionConfiguration for CheckboxTypeOptionPB {
type CellFilterConfiguration = CheckboxFilterPB; type CellFilterConfiguration = CheckboxFilterPB;
} }
impl TypeOptionCellData for CheckboxTypeOptionPB { impl TypeOptionCellData for CheckboxTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType { fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType {
cell_data cell_data
} }

View File

@ -165,7 +165,7 @@ mod tests {
let decoded_data = type_option let decoded_data = type_option
.try_decode_cell_data(encoded_data, &FieldType::DateTime, field_rev) .try_decode_cell_data(encoded_data, &FieldType::DateTime, field_rev)
.unwrap(); .unwrap();
let decoded_data = type_option.convert_into_pb_type(decoded_data); let decoded_data = type_option.convert_to_protobuf(decoded_data);
if type_option.include_time { if type_option.include_time {
format!("{} {}", decoded_data.date, decoded_data.time) format!("{} {}", decoded_data.date, decoded_data.time)
.trim_end() .trim_end()

View File

@ -3,7 +3,7 @@ use crate::impl_type_option;
use crate::services::cell::{AnyCellChangeset, CellDataChangeset, CellDataDecoder, FromCellString}; use crate::services::cell::{AnyCellChangeset, CellDataChangeset, CellDataDecoder, FromCellString};
use crate::services::field::{ use crate::services::field::{
BoxTypeOptionBuilder, DateCellChangeset, DateCellData, DateCellDataPB, DateFormat, TimeFormat, TypeOption, BoxTypeOptionBuilder, DateCellChangeset, DateCellData, DateCellDataPB, DateFormat, TimeFormat, TypeOption,
TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration, TypeOptionTransform,
}; };
use bytes::Bytes; use bytes::Bytes;
use chrono::format::strftime::StrftimeItems; use chrono::format::strftime::StrftimeItems;
@ -30,7 +30,7 @@ impl_type_option!(DateTypeOptionPB, FieldType::DateTime);
impl TypeOption for DateTypeOptionPB { impl TypeOption for DateTypeOptionPB {
type CellData = DateCellData; type CellData = DateCellData;
type CellChangeset = DateCellChangeset; type CellChangeset = DateCellChangeset;
type CellPBType = DateCellDataPB; type CellProtobufType = DateCellDataPB;
} }
impl TypeOptionConfiguration for DateTypeOptionPB { impl TypeOptionConfiguration for DateTypeOptionPB {
@ -38,7 +38,7 @@ impl TypeOptionConfiguration for DateTypeOptionPB {
} }
impl TypeOptionCellData for DateTypeOptionPB { impl TypeOptionCellData for DateTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType { fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType {
self.today_desc_from_timestamp(cell_data) self.today_desc_from_timestamp(cell_data)
} }
@ -128,6 +128,8 @@ impl DateTypeOptionPB {
} }
} }
impl TypeOptionTransform for DateTypeOptionPB {}
impl CellDataDecoder for DateTypeOptionPB { impl CellDataDecoder for DateTypeOptionPB {
fn try_decode_cell_data( fn try_decode_cell_data(
&self, &self,
@ -207,7 +209,4 @@ impl TypeOptionBuilder for DateTypeOptionBuilder {
fn serializer(&self) -> &dyn TypeOptionDataSerializer { fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0 &self.0
} }
fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
// Do nothing
}
} }

View File

@ -4,7 +4,7 @@ use crate::services::cell::{AnyCellChangeset, CellComparable, CellDataChangeset,
use crate::services::field::type_options::number_type_option::format::*; use crate::services::field::type_options::number_type_option::format::*;
use crate::services::field::{ use crate::services::field::{
BoxTypeOptionBuilder, NumberCellData, StrCellData, TypeOption, TypeOptionBuilder, TypeOptionCellData, BoxTypeOptionBuilder, NumberCellData, StrCellData, TypeOption, TypeOptionBuilder, TypeOptionCellData,
TypeOptionConfiguration, TypeOptionConfiguration, TypeOptionTransform,
}; };
use bytes::Bytes; use bytes::Bytes;
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
@ -51,9 +51,6 @@ impl TypeOptionBuilder for NumberTypeOptionBuilder {
fn serializer(&self) -> &dyn TypeOptionDataSerializer { fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0 &self.0
} }
fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
// Do nothing
}
} }
// Number // Number
@ -79,7 +76,7 @@ impl_type_option!(NumberTypeOptionPB, FieldType::Number);
impl TypeOption for NumberTypeOptionPB { impl TypeOption for NumberTypeOptionPB {
type CellData = StrCellData; type CellData = StrCellData;
type CellChangeset = NumberCellChangeset; type CellChangeset = NumberCellChangeset;
type CellPBType = StrCellData; type CellProtobufType = StrCellData;
} }
impl TypeOptionConfiguration for NumberTypeOptionPB { impl TypeOptionConfiguration for NumberTypeOptionPB {
@ -87,7 +84,7 @@ impl TypeOptionConfiguration for NumberTypeOptionPB {
} }
impl TypeOptionCellData for NumberTypeOptionPB { impl TypeOptionCellData for NumberTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType { fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType {
cell_data cell_data
} }
@ -128,6 +125,8 @@ pub(crate) fn strip_currency_symbol<T: ToString>(s: T) -> String {
s s
} }
impl TypeOptionTransform for NumberTypeOptionPB {}
impl CellDataDecoder for NumberTypeOptionPB { impl CellDataDecoder for NumberTypeOptionPB {
fn try_decode_cell_data( fn try_decode_cell_data(
&self, &self,

View File

@ -1,7 +1,7 @@
use crate::entities::{ChecklistFilterPB, FieldType}; use crate::entities::{ChecklistFilterPB, FieldType};
use crate::impl_type_option; use crate::impl_type_option;
use crate::services::cell::{AnyCellChangeset, CellDataChangeset, FromCellString, TypeCellData}; use crate::services::cell::{AnyCellChangeset, CellDataChangeset, FromCellString, TypeCellData};
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer;
use crate::services::field::{ use crate::services::field::{
BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, SelectOptionPB, BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, SelectOptionPB,
SelectTypeOptionSharedAction, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration, SelectTypeOptionSharedAction, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
@ -26,7 +26,7 @@ impl_type_option!(ChecklistTypeOptionPB, FieldType::Checklist);
impl TypeOption for ChecklistTypeOptionPB { impl TypeOption for ChecklistTypeOptionPB {
type CellData = SelectOptionIds; type CellData = SelectOptionIds;
type CellChangeset = SelectOptionCellChangeset; type CellChangeset = SelectOptionCellChangeset;
type CellPBType = SelectOptionCellDataPB; type CellProtobufType = SelectOptionCellDataPB;
} }
impl TypeOptionConfiguration for ChecklistTypeOptionPB { impl TypeOptionConfiguration for ChecklistTypeOptionPB {
@ -34,7 +34,7 @@ impl TypeOptionConfiguration for ChecklistTypeOptionPB {
} }
impl TypeOptionCellData for ChecklistTypeOptionPB { impl TypeOptionCellData for ChecklistTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType { fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType {
self.get_selected_options(cell_data) self.get_selected_options(cell_data)
} }
@ -113,8 +113,4 @@ impl TypeOptionBuilder for ChecklistTypeOptionBuilder {
fn serializer(&self) -> &dyn TypeOptionDataSerializer { fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0 &self.0
} }
fn transform(&mut self, field_type: &FieldType, type_option_data: String) {
SelectOptionTypeOptionTransformer::transform_type_option(&mut self.0, field_type, type_option_data)
}
} }

View File

@ -1,7 +1,7 @@
use crate::entities::{FieldType, SelectOptionFilterPB}; use crate::entities::{FieldType, SelectOptionFilterPB};
use crate::impl_type_option; use crate::impl_type_option;
use crate::services::cell::{AnyCellChangeset, CellDataChangeset, FromCellString, TypeCellData}; use crate::services::cell::{AnyCellChangeset, CellDataChangeset, FromCellString, TypeCellData};
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer;
use crate::services::field::{ use crate::services::field::{
BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, SelectOptionPB, BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, SelectOptionPB,
SelectTypeOptionSharedAction, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration, SelectTypeOptionSharedAction, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
@ -26,7 +26,7 @@ impl_type_option!(MultiSelectTypeOptionPB, FieldType::MultiSelect);
impl TypeOption for MultiSelectTypeOptionPB { impl TypeOption for MultiSelectTypeOptionPB {
type CellData = SelectOptionIds; type CellData = SelectOptionIds;
type CellChangeset = SelectOptionCellChangeset; type CellChangeset = SelectOptionCellChangeset;
type CellPBType = SelectOptionCellDataPB; type CellProtobufType = SelectOptionCellDataPB;
} }
impl TypeOptionConfiguration for MultiSelectTypeOptionPB { impl TypeOptionConfiguration for MultiSelectTypeOptionPB {
@ -34,7 +34,7 @@ impl TypeOptionConfiguration for MultiSelectTypeOptionPB {
} }
impl TypeOptionCellData for MultiSelectTypeOptionPB { impl TypeOptionCellData for MultiSelectTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType { fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType {
self.get_selected_options(cell_data) self.get_selected_options(cell_data)
} }
@ -119,17 +119,14 @@ impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
fn serializer(&self) -> &dyn TypeOptionDataSerializer { fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0 &self.0
} }
fn transform(&mut self, field_type: &FieldType, type_option_data: String) {
SelectOptionTypeOptionTransformer::transform_type_option(&mut self.0, field_type, type_option_data)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::entities::FieldType; use crate::entities::FieldType;
use crate::services::cell::CellDataChangeset; use crate::services::cell::CellDataChangeset;
use crate::services::field::type_options::selection_type_option::*; use crate::services::field::type_options::selection_type_option::*;
use crate::services::field::{CheckboxTypeOptionBuilder, FieldBuilder, TypeOptionBuilder}; use crate::services::field::{CheckboxTypeOptionBuilder, FieldBuilder, TypeOptionBuilder, TypeOptionTransform};
use crate::services::field::{MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB}; use crate::services::field::{MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB};
#[test] #[test]
@ -137,13 +134,13 @@ mod tests {
let checkbox_type_option_builder = CheckboxTypeOptionBuilder::default(); let checkbox_type_option_builder = CheckboxTypeOptionBuilder::default();
let checkbox_type_option_data = checkbox_type_option_builder.serializer().json_str(); let checkbox_type_option_data = checkbox_type_option_builder.serializer().json_str();
let mut multi_select = MultiSelectTypeOptionBuilder::default(); let mut multi_select = MultiSelectTypeOptionBuilder::default().0;
multi_select.transform(&FieldType::Checkbox, checkbox_type_option_data.clone()); multi_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data.clone());
debug_assert_eq!(multi_select.0.options.len(), 2); debug_assert_eq!(multi_select.options.len(), 2);
// Already contain the yes/no option. It doesn't need to insert new options // Already contain the yes/no option. It doesn't need to insert new options
multi_select.transform(&FieldType::Checkbox, checkbox_type_option_data); multi_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data);
debug_assert_eq!(multi_select.0.options.len(), 2); debug_assert_eq!(multi_select.options.len(), 2);
} }
#[test] #[test]
@ -158,13 +155,13 @@ mod tests {
let singleselect_type_option_data = singleselect_type_option_builder.serializer().json_str(); let singleselect_type_option_data = singleselect_type_option_builder.serializer().json_str();
let mut multi_select = MultiSelectTypeOptionBuilder::default(); let mut multi_select = MultiSelectTypeOptionBuilder::default().0;
multi_select.transform(&FieldType::MultiSelect, singleselect_type_option_data.clone()); multi_select.transform_type_option(FieldType::MultiSelect, singleselect_type_option_data.clone());
debug_assert_eq!(multi_select.0.options.len(), 2); debug_assert_eq!(multi_select.options.len(), 2);
// Already contain the yes/no option. It doesn't need to insert new options // Already contain the yes/no option. It doesn't need to insert new options
multi_select.transform(&FieldType::MultiSelect, singleselect_type_option_data); multi_select.transform_type_option(FieldType::MultiSelect, singleselect_type_option_data);
debug_assert_eq!(multi_select.0.options.len(), 2); debug_assert_eq!(multi_select.options.len(), 2);
} }
// #[test] // #[test]

View File

@ -4,9 +4,10 @@ use crate::services::cell::{
CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, FromCellString, CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, FromCellString,
}; };
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer; use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformHelper;
use crate::services::field::{ use crate::services::field::{
ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB, TypeOption, TypeOptionCellData, ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB, TypeOption, TypeOptionCellData,
TypeOptionTransform,
}; };
use bytes::Bytes; use bytes::Bytes;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
@ -128,6 +129,31 @@ pub trait SelectTypeOptionSharedAction: TypeOptionDataSerializer + Send + Sync {
fn mut_options(&mut self) -> &mut Vec<SelectOptionPB>; fn mut_options(&mut self) -> &mut Vec<SelectOptionPB>;
} }
impl<T> TypeOptionTransform for T
where
T: SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds> + TypeOptionDataSerializer,
{
fn transformable(&self) -> bool {
true
}
fn transform_type_option(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String) {
SelectOptionTypeOptionTransformHelper::transform_type_option(
self,
&old_type_option_field_type,
old_type_option_data,
);
}
fn transform_type_option_cell_data(
&self,
cell_data: <Self as TypeOption>::CellData,
decoded_field_type: &FieldType,
) -> <Self as TypeOption>::CellData {
SelectOptionTypeOptionTransformHelper::transform_type_option_cell_data(self, cell_data, decoded_field_type)
}
}
impl<T> CellDataDecoder for T impl<T> CellDataDecoder for T
where where
T: SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds> + TypeOptionCellData, T: SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds> + TypeOptionCellData,
@ -135,16 +161,10 @@ where
fn try_decode_cell_data( fn try_decode_cell_data(
&self, &self,
cell_data: String, cell_data: String,
decoded_field_type: &FieldType, _decoded_field_type: &FieldType,
field_rev: &FieldRevision, _field_rev: &FieldRevision,
) -> FlowyResult<<Self as TypeOption>::CellData> { ) -> FlowyResult<<Self as TypeOption>::CellData> {
let cell_data = self.decode_type_option_cell_data(cell_data)?; self.decode_type_option_cell_data(cell_data)
Ok(SelectOptionTypeOptionTransformer::transform_type_option_cell_data(
self,
cell_data,
decoded_field_type,
field_rev,
))
} }
fn decode_cell_data_to_str( fn decode_cell_data_to_str(

View File

@ -1,7 +1,7 @@
use crate::entities::{FieldType, SelectOptionFilterPB}; use crate::entities::{FieldType, SelectOptionFilterPB};
use crate::impl_type_option; use crate::impl_type_option;
use crate::services::cell::{AnyCellChangeset, CellDataChangeset, FromCellString}; use crate::services::cell::{AnyCellChangeset, CellDataChangeset, FromCellString};
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer;
use crate::services::field::{ use crate::services::field::{
BoxTypeOptionBuilder, SelectOptionCellDataPB, TypeOption, TypeOptionBuilder, TypeOptionCellData, BoxTypeOptionBuilder, SelectOptionCellDataPB, TypeOption, TypeOptionBuilder, TypeOptionCellData,
TypeOptionConfiguration, TypeOptionConfiguration,
@ -29,7 +29,7 @@ impl_type_option!(SingleSelectTypeOptionPB, FieldType::SingleSelect);
impl TypeOption for SingleSelectTypeOptionPB { impl TypeOption for SingleSelectTypeOptionPB {
type CellData = SelectOptionIds; type CellData = SelectOptionIds;
type CellChangeset = SelectOptionCellChangeset; type CellChangeset = SelectOptionCellChangeset;
type CellPBType = SelectOptionCellDataPB; type CellProtobufType = SelectOptionCellDataPB;
} }
impl TypeOptionConfiguration for SingleSelectTypeOptionPB { impl TypeOptionConfiguration for SingleSelectTypeOptionPB {
@ -37,7 +37,7 @@ impl TypeOptionConfiguration for SingleSelectTypeOptionPB {
} }
impl TypeOptionCellData for SingleSelectTypeOptionPB { impl TypeOptionCellData for SingleSelectTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType { fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType {
self.get_selected_options(cell_data) self.get_selected_options(cell_data)
} }
@ -107,10 +107,6 @@ impl TypeOptionBuilder for SingleSelectTypeOptionBuilder {
fn serializer(&self) -> &dyn TypeOptionDataSerializer { fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0 &self.0
} }
fn transform(&mut self, field_type: &FieldType, type_option_data: String) {
SelectOptionTypeOptionTransformer::transform_type_option(&mut self.0, field_type, type_option_data)
}
} }
#[cfg(test)] #[cfg(test)]
@ -125,13 +121,13 @@ mod tests {
let checkbox_type_option_builder = CheckboxTypeOptionBuilder::default(); let checkbox_type_option_builder = CheckboxTypeOptionBuilder::default();
let checkbox_type_option_data = checkbox_type_option_builder.serializer().json_str(); let checkbox_type_option_data = checkbox_type_option_builder.serializer().json_str();
let mut single_select = SingleSelectTypeOptionBuilder::default(); let mut single_select = SingleSelectTypeOptionBuilder::default().0;
single_select.transform(&FieldType::Checkbox, checkbox_type_option_data.clone()); single_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data.clone());
debug_assert_eq!(single_select.0.options.len(), 2); debug_assert_eq!(single_select.options.len(), 2);
// Already contain the yes/no option. It doesn't need to insert new options // Already contain the yes/no option. It doesn't need to insert new options
single_select.transform(&FieldType::Checkbox, checkbox_type_option_data); single_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data);
debug_assert_eq!(single_select.0.options.len(), 2); debug_assert_eq!(single_select.options.len(), 2);
} }
#[test] #[test]
@ -146,13 +142,13 @@ mod tests {
let multiselect_type_option_data = multiselect_type_option_builder.serializer().json_str(); let multiselect_type_option_data = multiselect_type_option_builder.serializer().json_str();
let mut single_select = SingleSelectTypeOptionBuilder::default(); let mut single_select = SingleSelectTypeOptionBuilder::default().0;
single_select.transform(&FieldType::MultiSelect, multiselect_type_option_data.clone()); single_select.transform_type_option(FieldType::MultiSelect, multiselect_type_option_data.clone());
debug_assert_eq!(single_select.0.options.len(), 2); debug_assert_eq!(single_select.options.len(), 2);
// Already contain the yes/no option. It doesn't need to insert new options // Already contain the yes/no option. It doesn't need to insert new options
single_select.transform(&FieldType::MultiSelect, multiselect_type_option_data); single_select.transform_type_option(FieldType::MultiSelect, multiselect_type_option_data);
debug_assert_eq!(single_select.0.options.len(), 2); debug_assert_eq!(single_select.options.len(), 2);
} }
#[test] #[test]

View File

@ -5,24 +5,22 @@ use crate::services::field::{
SingleSelectTypeOptionPB, TypeOption, CHECK, UNCHECK, SingleSelectTypeOptionPB, TypeOption, CHECK, UNCHECK,
}; };
use grid_rev_model::FieldRevision; use grid_rev_model::TypeOptionDataDeserializer;
use serde_json;
/// Handles how to transform the cell data when switching between different field types /// Handles how to transform the cell data when switching between different field types
pub struct SelectOptionTypeOptionTransformer(); pub(crate) struct SelectOptionTypeOptionTransformHelper();
impl SelectOptionTypeOptionTransformer { impl SelectOptionTypeOptionTransformHelper {
/// Transform the TypeOptionData from 'field_type' to single select option type. /// Transform the TypeOptionData from 'field_type' to single select option type.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `field_type`: the FieldType of the passed-in TypeOptionData /// * `old_field_type`: the FieldType of the passed-in TypeOptionData
/// * `type_option_data`: the data that can be parsed into corresponding TypeOptionData.
/// ///
pub fn transform_type_option<T>(shared: &mut T, field_type: &FieldType, _type_option_data: String) pub fn transform_type_option<T>(shared: &mut T, old_field_type: &FieldType, old_type_option_data: String)
where where
T: SelectTypeOptionSharedAction, T: SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds>,
{ {
match field_type { match old_field_type {
FieldType::Checkbox => { FieldType::Checkbox => {
//add Yes and No options if it does not exist. //add Yes and No options if it does not exist.
if !shared.options().iter().any(|option| option.name == CHECK) { if !shared.options().iter().any(|option| option.name == CHECK) {
@ -36,16 +34,16 @@ impl SelectOptionTypeOptionTransformer {
} }
} }
FieldType::MultiSelect => { FieldType::MultiSelect => {
let option_pb: MultiSelectTypeOptionPB = serde_json::from_str(_type_option_data.as_str()).unwrap(); let options = MultiSelectTypeOptionPB::from_json_str(&old_type_option_data).options;
option_pb.options.iter().for_each(|new_option| { options.iter().for_each(|new_option| {
if !shared.options().iter().any(|option| option.name == new_option.name) { if !shared.options().iter().any(|option| option.name == new_option.name) {
shared.mut_options().push(new_option.clone()); shared.mut_options().push(new_option.clone());
} }
}) })
} }
FieldType::SingleSelect => { FieldType::SingleSelect => {
let option_pb: SingleSelectTypeOptionPB = serde_json::from_str(_type_option_data.as_str()).unwrap(); let options = SingleSelectTypeOptionPB::from_json_str(&old_type_option_data).options;
option_pb.options.iter().for_each(|new_option| { options.iter().for_each(|new_option| {
if !shared.options().iter().any(|option| option.name == new_option.name) { if !shared.options().iter().any(|option| option.name == new_option.name) {
shared.mut_options().push(new_option.clone()); shared.mut_options().push(new_option.clone());
} }
@ -59,7 +57,6 @@ impl SelectOptionTypeOptionTransformer {
shared: &T, shared: &T,
cell_data: <T as TypeOption>::CellData, cell_data: <T as TypeOption>::CellData,
decoded_field_type: &FieldType, decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> <T as TypeOption>::CellData ) -> <T as TypeOption>::CellData
where where
T: SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds>, T: SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds>,
@ -75,7 +72,6 @@ impl SelectOptionTypeOptionTransformer {
transformed_ids.push(option.id.clone()); transformed_ids.push(option.id.clone());
} }
}); });
SelectOptionIds::from(transformed_ids) SelectOptionIds::from(transformed_ids)
} }
_ => SelectOptionIds::from(vec![]), _ => SelectOptionIds::from(vec![]),

View File

@ -6,6 +6,7 @@ use crate::services::cell::{
}; };
use crate::services::field::{ use crate::services::field::{
BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration, BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
TypeOptionTransform,
}; };
use bytes::Bytes; use bytes::Bytes;
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
@ -28,9 +29,6 @@ impl TypeOptionBuilder for RichTextTypeOptionBuilder {
fn serializer(&self) -> &dyn TypeOptionDataSerializer { fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0 &self.0
} }
fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
// Do nothing
}
} }
/// For the moment, the `RichTextTypeOptionPB` is empty. The `data` property is not /// For the moment, the `RichTextTypeOptionPB` is empty. The `data` property is not
@ -46,15 +44,17 @@ impl_type_option!(RichTextTypeOptionPB, FieldType::RichText);
impl TypeOption for RichTextTypeOptionPB { impl TypeOption for RichTextTypeOptionPB {
type CellData = StrCellData; type CellData = StrCellData;
type CellChangeset = String; type CellChangeset = String;
type CellPBType = StrCellData; type CellProtobufType = StrCellData;
} }
impl TypeOptionTransform for RichTextTypeOptionPB {}
impl TypeOptionConfiguration for RichTextTypeOptionPB { impl TypeOptionConfiguration for RichTextTypeOptionPB {
type CellFilterConfiguration = TextFilterPB; type CellFilterConfiguration = TextFilterPB;
} }
impl TypeOptionCellData for RichTextTypeOptionPB { impl TypeOptionCellData for RichTextTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType { fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType {
cell_data cell_data
} }

View File

@ -6,20 +6,112 @@ use crate::services::field::{
}; };
use bytes::Bytes; use bytes::Bytes;
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
use grid_rev_model::FieldRevision; use grid_rev_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
use protobuf::ProtobufError; use protobuf::ProtobufError;
use std::fmt::Debug; use std::fmt::Debug;
pub trait TypeOption { pub trait TypeOption {
/// `CellData` represents as the decoded model for current type option. Each of them impl the
/// `FromCellString` and `Default` trait. If the cell string can not be decoded into the specified
/// cell data type then the default value will be returned.
/// For example:
/// FieldType::Checkbox => CheckboxCellData
/// FieldType::Date => DateCellData
/// FieldType::URL => URLCellData
///
/// Uses `StrCellData` for any `TypeOption` if their cell data is pure `String`.
///
type CellData: FromCellString + Default; type CellData: FromCellString + Default;
///
type CellChangeset; type CellChangeset;
type CellPBType: TryInto<Bytes, Error = ProtobufError> + Debug;
/// For the moment, the protobuf type only be used in the FFI of `Dart`. If the decoded cell
/// struct is just a `String`, then use the `StrCellData` as its `CellProtobufType`.
/// Otherwise, providing a custom protobuf type as its `CellProtobufType`.
/// For example:
/// FieldType::Date => DateCellDataPB
/// FieldType::URL => URLCellDataPB
///
type CellProtobufType: TryInto<Bytes, Error = ProtobufError> + Debug;
}
pub trait TypeOptionCellData: TypeOption {
/// Convert the decoded cell data into corresponding `Protobuf struct`.
/// For example:
/// FieldType::URL => URLCellDataPB
/// FieldType::Date=> DateCellDataPB
fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType;
/// Decodes the opaque cell data to corresponding data struct.
// For example, the cell data is timestamp if its field type is `FieldType::Date`. This cell
// data can not directly show to user. So it needs to be encode as the date string with custom
// format setting. Encode `1647251762` to `"Mar 14,2022`
fn decode_type_option_cell_data(&self, cell_data: String) -> FlowyResult<<Self as TypeOption>::CellData>;
} }
pub trait TypeOptionConfiguration { pub trait TypeOptionConfiguration {
type CellFilterConfiguration; type CellFilterConfiguration;
} }
pub trait TypeOptionTransform: TypeOption {
/// Returns true if the current `TypeOption` provides custom type option transformation
fn transformable(&self) -> bool {
false
}
/// Transform the TypeOption from one field type to another
/// For example, when switching from `checkbox` type-option to `single-select`
/// type-option, adding the `Yes` option if the `single-select` type-option doesn't contain it.
/// But the cell content is a string, `Yes`, it's need to do the cell content transform.
/// The `Yes` string will be transformed to the `Yes` option id.
///
/// # Arguments
///
/// * `old_type_option_field_type`: the FieldType of the passed-in TypeOption
/// * `old_type_option_data`: the data that can be parsed into corresponding `TypeOption`.
///
///
fn transform_type_option(&mut self, _old_type_option_field_type: FieldType, _old_type_option_data: String) {}
/// Transform the cell data from one field type to another
///
/// # Arguments
///
/// * `cell_data`: the cell data of the current field type
/// * `decoded_field_type`: the field type of the cell data that's going to be transformed into.
///
fn transform_type_option_cell_data(
&self,
cell_data: <Self as TypeOption>::CellData,
_decoded_field_type: &FieldType,
) -> <Self as TypeOption>::CellData {
// Do nothing, just return the passed-in cell data
cell_data
}
}
pub trait TypeOptionTransformHandler {
fn transform(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String);
fn json_str(&self) -> String;
}
impl<T> TypeOptionTransformHandler for T
where
T: TypeOptionTransform + TypeOptionDataSerializer,
{
fn transform(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String) {
if self.transformable() {
self.transform_type_option(old_type_option_field_type, old_type_option_data)
}
}
fn json_str(&self) -> String {
self.json_str()
}
}
pub trait TypeOptionCellDataHandler { pub trait TypeOptionCellDataHandler {
fn handle_cell_data( fn handle_cell_data(
&self, &self,
@ -31,24 +123,9 @@ pub trait TypeOptionCellDataHandler {
fn stringify_cell_data(&self, cell_data: String, field_type: &FieldType, field_rev: &FieldRevision) -> String; fn stringify_cell_data(&self, cell_data: String, field_type: &FieldType, field_rev: &FieldRevision) -> String;
} }
pub trait TypeOptionCellData: TypeOption {
///
/// Convert the decoded cell data into corresponding `Protobuf struct`.
/// For example:
/// FieldType::URL => URLCellDataPB
/// FieldType::Date=> DateCellDataPB
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType;
/// Decodes the opaque cell data to corresponding data struct.
// For example, the cell data is timestamp if its field type is `FieldType::Date`. This cell
// data can not directly show to user. So it needs to be encode as the date string with custom
// format setting. Encode `1647251762` to `"Mar 14,2022`
fn decode_type_option_cell_data(&self, cell_data: String) -> FlowyResult<<Self as TypeOption>::CellData>;
}
impl<T> TypeOptionCellDataHandler for T impl<T> TypeOptionCellDataHandler for T
where where
T: TypeOption + CellDataDecoder + CellDataChangeset + TypeOptionCellData, T: TypeOption + CellDataDecoder + CellDataChangeset + TypeOptionCellData + TypeOptionTransform,
{ {
fn handle_cell_data( fn handle_cell_data(
&self, &self,
@ -56,8 +133,11 @@ where
field_type: &FieldType, field_type: &FieldType,
field_rev: &FieldRevision, field_rev: &FieldRevision,
) -> FlowyResult<CellProtobufBlob> { ) -> FlowyResult<CellProtobufBlob> {
let cell_data = self.try_decode_cell_data(cell_data, field_type, field_rev)?; let mut cell_data = self.try_decode_cell_data(cell_data, field_type, field_rev)?;
CellProtobufBlob::from(self.convert_into_pb_type(cell_data)) if self.transformable() {
cell_data = self.transform_type_option_cell_data(cell_data, field_type);
}
CellProtobufBlob::from(self.convert_to_protobuf(cell_data))
} }
fn stringify_cell_data( fn stringify_cell_data(
@ -117,3 +197,48 @@ impl<'a> FieldRevisionExt<'a> {
} }
} }
} }
pub fn transform_type_option(
type_option_data: &str,
new_field_type: &FieldType,
old_type_option_data: Option<String>,
old_field_type: FieldType,
) -> String {
let mut transform_handler = get_type_option_transform_handler(type_option_data, new_field_type);
if let Some(old_type_option_data) = old_type_option_data {
transform_handler.transform(old_field_type, old_type_option_data);
}
transform_handler.json_str()
}
pub fn get_type_option_transform_handler(
type_option_data: &str,
field_type: &FieldType,
) -> Box<dyn TypeOptionTransformHandler> {
match field_type {
FieldType::RichText => {
Box::new(RichTextTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
}
FieldType::Number => {
Box::new(NumberTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
}
FieldType::DateTime => {
Box::new(DateTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
}
FieldType::SingleSelect => {
Box::new(SingleSelectTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
}
FieldType::MultiSelect => {
Box::new(MultiSelectTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
}
FieldType::Checkbox => {
Box::new(CheckboxTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
}
FieldType::URL => {
Box::new(URLTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
}
FieldType::Checklist => {
Box::new(ChecklistTypeOptionPB::from_json_str(type_option_data)) as Box<dyn TypeOptionTransformHandler>
}
}
}

View File

@ -2,8 +2,8 @@ use crate::entities::{FieldType, TextFilterPB};
use crate::impl_type_option; use crate::impl_type_option;
use crate::services::cell::{AnyCellChangeset, CellDataChangeset, CellDataDecoder, FromCellString}; use crate::services::cell::{AnyCellChangeset, CellDataChangeset, CellDataDecoder, FromCellString};
use crate::services::field::{ use crate::services::field::{
BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration, URLCellData, BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionConfiguration,
URLCellDataPB, TypeOptionTransform, URLCellData, URLCellDataPB,
}; };
use bytes::Bytes; use bytes::Bytes;
use fancy_regex::Regex; use fancy_regex::Regex;
@ -26,10 +26,6 @@ impl TypeOptionBuilder for URLTypeOptionBuilder {
fn serializer(&self) -> &dyn TypeOptionDataSerializer { fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0 &self.0
} }
fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
// Do nothing
}
} }
#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] #[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
@ -42,15 +38,17 @@ impl_type_option!(URLTypeOptionPB, FieldType::URL);
impl TypeOption for URLTypeOptionPB { impl TypeOption for URLTypeOptionPB {
type CellData = URLCellData; type CellData = URLCellData;
type CellChangeset = URLCellChangeset; type CellChangeset = URLCellChangeset;
type CellPBType = URLCellDataPB; type CellProtobufType = URLCellDataPB;
} }
impl TypeOptionTransform for URLTypeOptionPB {}
impl TypeOptionConfiguration for URLTypeOptionPB { impl TypeOptionConfiguration for URLTypeOptionPB {
type CellFilterConfiguration = TextFilterPB; type CellFilterConfiguration = TextFilterPB;
} }
impl TypeOptionCellData for URLTypeOptionPB { impl TypeOptionCellData for URLTypeOptionPB {
fn convert_into_pb_type(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellPBType { fn convert_to_protobuf(&self, cell_data: <Self as TypeOption>::CellData) -> <Self as TypeOption>::CellProtobufType {
cell_data.into() cell_data.into()
} }

View File

@ -5,8 +5,7 @@ use crate::manager::GridUser;
use crate::services::block_manager::GridBlockManager; use crate::services::block_manager::GridBlockManager;
use crate::services::cell::{apply_cell_data_changeset, decode_type_cell_data, CellProtobufBlob}; use crate::services::cell::{apply_cell_data_changeset, decode_type_cell_data, CellProtobufBlob};
use crate::services::field::{ use crate::services::field::{
default_type_option_builder_from_type, type_option_builder_from_bytes, type_option_builder_from_json_str, default_type_option_builder_from_type, transform_type_option, type_option_builder_from_bytes, FieldBuilder,
FieldBuilder,
}; };
use crate::services::filter::FilterType; use crate::services::filter::FilterType;
@ -280,11 +279,13 @@ impl GridRevisionEditor {
let type_option_transform = let type_option_transform =
|old_field_type: FieldTypeRevision, old_type_option: Option<String>, new_type_option: String| { |old_field_type: FieldTypeRevision, old_type_option: Option<String>, new_type_option: String| {
let old_field_type: FieldType = old_field_type.into(); let old_field_type: FieldType = old_field_type.into();
let mut type_option_builder = type_option_builder_from_json_str(&new_type_option, new_field_type); // let mut type_option_builder = type_option_builder_from_json_str(&new_type_option, new_field_type);
if let Some(old_type_option) = old_type_option { // if let Some(old_type_option) = old_type_option {
type_option_builder.transform(&old_field_type, old_type_option) // type_option_builder.transform(&old_field_type, old_type_option)
} // }
type_option_builder.serializer().json_str() // type_option_builder.serializer().json_str()
transform_type_option(&new_type_option, new_field_type, old_type_option, old_field_type)
}; };
let _ = self let _ = self

View File

@ -3,6 +3,7 @@ use bytes::Bytes;
use indexmap::IndexMap; use indexmap::IndexMap;
use nanoid::nanoid; use nanoid::nanoid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
pub fn gen_grid_id() -> String { pub fn gen_grid_id() -> String {