Refactor/update type options (#1265)

* chore: add documentation

* chore: update type option data after switching to a new field type

* chore: insert yes/no option when switch from checkbox to single/multi select

Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
Nathan.fooo 2022-10-11 21:51:02 +08:00 committed by GitHub
parent 0bc0a72d8a
commit 1adf6530fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 276 additions and 111 deletions

View File

@ -320,7 +320,7 @@ impl std::convert::From<String> for RepeatedFieldIdPB {
}
}
/// [UpdateFieldTypeOptionPayloadPB] is used to update the type option data.
/// [UpdateFieldTypeOptionPayloadPB] is used to update the type-option data.
#[derive(ProtoBuf, Default)]
pub struct UpdateFieldTypeOptionPayloadPB {
#[pb(index = 1)]
@ -465,6 +465,18 @@ pub struct FieldChangesetParams {
pub type_option_data: Option<Vec<u8>>,
}
impl FieldChangesetParams {
pub fn has_changes(&self) -> bool {
self.name.is_some()
|| self.desc.is_some()
|| self.field_type.is_some()
|| self.frozen.is_some()
|| self.type_option_data.is_some()
|| self.frozen.is_some()
|| self.width.is_some()
}
}
/// Certain field types have user-defined options such as color, date format, number format,
/// or a list of values for a multi-select list. These options are defined within a specialization
/// of the FieldTypeOption class.

View File

@ -142,6 +142,7 @@ pub(crate) async fn switch_to_field_handler(
.await
.unwrap_or(Arc::new(editor.next_field_rev(&params.field_type).await?));
// Update the type-option data after the field type has been changed
let type_option_data = get_type_option_data(&field_rev, &params.field_type).await?;
let _ = editor
.update_field_type_option(&params.grid_id, &field_rev.id, type_option_data)
@ -220,12 +221,12 @@ pub(crate) async fn move_field_handler(
async fn get_type_option_data(field_rev: &FieldRevision, field_type: &FieldType) -> FlowyResult<Vec<u8>> {
let s = field_rev.get_type_option_str(field_type).unwrap_or_else(|| {
default_type_option_builder_from_type(field_type)
.data_format()
.serializer()
.json_str()
});
let field_type: FieldType = field_rev.ty.into();
let builder = type_option_builder_from_json_str(&s, &field_type);
let type_option_data = builder.data_format().protobuf_bytes().to_vec();
let type_option_data = builder.serializer().protobuf_bytes().to_vec();
Ok(type_option_data)
}
@ -373,7 +374,7 @@ pub(crate) async fn update_select_option_handler(
tokio::spawn(async move {
match cloned_editor.update_cell(changeset).await {
Ok(_) => {}
Err(_) => {}
Err(e) => tracing::error!("{}", e),
}
});
}

View File

@ -92,7 +92,7 @@ pub enum GridEvent {
#[event(input = "FieldChangesetPayloadPB")]
UpdateField = 11,
/// [UpdateFieldTypeOption] event is used to update the field's type option data. Certain field
/// [UpdateFieldTypeOption] event is used to update the field's type-option data. Certain field
/// types have user-defined options such as color, date format, number format, or a list of values
/// for a multi-select list. These options are defined within a specialization of the
/// FieldTypeOption class.

View File

@ -52,7 +52,7 @@ macro_rules! impl_type_option {
}
}
impl TypeOptionDataFormat for $target {
impl TypeOptionDataSerializer for $target {
fn json_str(&self) -> String {
match serde_json::to_string(&self) {
Ok(s) => s,

View File

@ -79,7 +79,7 @@ impl FieldBuilder {
pub fn build(self) -> FieldRevision {
let mut field_rev = self.field_rev;
field_rev.insert_type_option(self.type_option_builder.data_format());
field_rev.insert_type_option(self.type_option_builder.serializer());
field_rev
}
}

View File

@ -1,7 +1,7 @@
use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
use crate::services::grid_editor::GridRevisionEditor;
use flowy_error::FlowyResult;
use flowy_grid_data_model::revision::{TypeOptionDataDeserializer, TypeOptionDataFormat};
use flowy_grid_data_model::revision::{TypeOptionDataDeserializer, TypeOptionDataSerializer};
use std::sync::Arc;
pub async fn edit_field_type_option<T>(
@ -10,7 +10,7 @@ pub async fn edit_field_type_option<T>(
action: impl FnOnce(&mut T),
) -> FlowyResult<()>
where
T: TypeOptionDataDeserializer + TypeOptionDataFormat,
T: TypeOptionDataDeserializer + TypeOptionDataSerializer,
{
let get_type_option = async {
let field_rev = editor.get_field_rev(field_id).await?;

View File

@ -1,11 +1,29 @@
use crate::entities::FieldType;
use crate::services::field::type_options::*;
use bytes::Bytes;
use flowy_grid_data_model::revision::TypeOptionDataFormat;
use flowy_grid_data_model::revision::TypeOptionDataSerializer;
pub trait TypeOptionBuilder {
fn field_type(&self) -> FieldType;
fn data_format(&self) -> &dyn TypeOptionDataFormat;
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> {

View File

@ -5,7 +5,9 @@ use crate::services::field::{BoxTypeOptionBuilder, CheckboxCellData, TypeOptionB
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
use flowy_grid_data_model::revision::{
CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer,
};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
@ -26,9 +28,13 @@ impl TypeOptionBuilder for CheckboxTypeOptionBuilder {
FieldType::Checkbox
}
fn data_format(&self) -> &dyn TypeOptionDataFormat {
fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0
}
fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
// Do nothing
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]

View File

@ -9,7 +9,9 @@ use chrono::format::strftime::StrftimeItems;
use chrono::{NaiveDateTime, Timelike};
use flowy_derive::ProtoBuf;
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
use flowy_grid_data_model::revision::{
CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer,
};
use serde::{Deserialize, Serialize};
// Date
@ -200,7 +202,10 @@ impl TypeOptionBuilder for DateTypeOptionBuilder {
FieldType::DateTime
}
fn data_format(&self) -> &dyn TypeOptionDataFormat {
fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0
}
fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
// Do nothing
}
}

View File

@ -6,7 +6,9 @@ use crate::services::field::{BoxTypeOptionBuilder, NumberCellData, TypeOptionBui
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
use flowy_grid_data_model::revision::{
CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer,
};
use rust_decimal::Decimal;
@ -45,9 +47,12 @@ impl TypeOptionBuilder for NumberTypeOptionBuilder {
FieldType::Number
}
fn data_format(&self) -> &dyn TypeOptionDataFormat {
fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0
}
fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
// Do nothing
}
}
// Number

View File

@ -4,12 +4,14 @@ use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOper
use crate::services::field::type_options::util::get_cell_data;
use crate::services::field::{
make_selected_select_options, BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB,
SelectOptionIds, SelectOptionOperation, SelectOptionPB, TypeOptionBuilder,
SelectOptionColorPB, SelectOptionIds, SelectOptionOperation, SelectOptionPB, TypeOptionBuilder, CHECK, UNCHECK,
};
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
use flowy_grid_data_model::revision::{
CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer,
};
use serde::{Deserialize, Serialize};
// Multiple select
@ -48,10 +50,6 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for MultiSele
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
if !decoded_field_type.is_select_option() {
return Ok(CellBytes::default());
}
self.display_data(cell_data, decoded_field_type, field_rev)
}
@ -111,17 +109,51 @@ impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
FieldType::MultiSelect
}
fn data_format(&self) -> &dyn TypeOptionDataFormat {
fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0
}
fn transform(&mut self, field_type: &FieldType, _type_option_data: String) {
match field_type {
FieldType::Checkbox => {
//Add Yes and No options if it's not exist.
if self.0.options.iter().find(|option| option.name == CHECK).is_none() {
let check_option = SelectOptionPB::with_color(CHECK, SelectOptionColorPB::Green);
self.0.options.push(check_option);
}
if self.0.options.iter().find(|option| option.name == UNCHECK).is_none() {
let uncheck_option = SelectOptionPB::with_color(UNCHECK, SelectOptionColorPB::Yellow);
self.0.options.push(uncheck_option);
}
}
FieldType::SingleSelect => {}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use crate::entities::FieldType;
use crate::services::cell::CellDataOperation;
use crate::services::field::type_options::selection_type_option::*;
use crate::services::field::FieldBuilder;
use crate::services::field::{CheckboxTypeOptionBuilder, FieldBuilder, TypeOptionBuilder};
use crate::services::field::{MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB};
#[test]
fn multi_select_transform_with_checkbox_type_option_test() {
let checkbox_type_option_builder = CheckboxTypeOptionBuilder::default();
let checkbox_type_option_data = checkbox_type_option_builder.serializer().json_str();
let mut multi_select = MultiSelectTypeOptionBuilder::default();
multi_select.transform(&FieldType::Checkbox, checkbox_type_option_data.clone());
debug_assert_eq!(multi_select.0.options.len(), 2);
// Already contain the yes/no option. It doesn't need to insert new options
multi_select.transform(&FieldType::Checkbox, checkbox_type_option_data);
debug_assert_eq!(multi_select.0.options.len(), 2);
}
#[test]
fn multi_select_insert_multi_option_test() {
let google = SelectOptionPB::new("Google");

View File

@ -5,7 +5,7 @@ use bytes::Bytes;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::{internal_error, ErrorCode, FlowyResult};
use flowy_grid_data_model::parser::NotEmptyStr;
use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataFormat};
use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataSerializer};
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
@ -75,9 +75,8 @@ pub fn make_selected_select_options(
}
}
pub trait SelectOptionOperation: TypeOptionDataFormat + Send + Sync {
pub trait SelectOptionOperation: TypeOptionDataSerializer + Send + Sync {
/// Insert the `SelectOptionPB` into corresponding type option.
/// Replace the old value if the option already exists in the option list.
fn insert_option(&mut self, new_option: SelectOptionPB) {
let options = self.mut_options();
if let Some(index) = options
@ -117,9 +116,13 @@ where
fn display_data(
&self,
cell_data: CellData<SelectOptionIds>,
_decoded_field_type: &FieldType,
decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
if !decoded_field_type.is_select_option() {
return Ok(CellBytes::default());
}
CellBytes::from(self.selected_select_option(cell_data))
}

View File

@ -2,14 +2,16 @@ use crate::entities::FieldType;
use crate::impl_type_option;
use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
use crate::services::field::{
make_selected_select_options, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds,
SelectOptionOperation, SelectOptionPB,
make_selected_select_options, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionColorPB,
SelectOptionIds, SelectOptionOperation, SelectOptionPB, CHECK, UNCHECK,
};
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
use flowy_grid_data_model::revision::{
CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer,
};
use serde::{Deserialize, Serialize};
// Single select
@ -50,10 +52,6 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for SingleSel
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
if !decoded_field_type.is_select_option() {
return Ok(CellBytes::default());
}
self.display_data(cell_data, decoded_field_type, field_rev)
}
@ -103,17 +101,50 @@ impl TypeOptionBuilder for SingleSelectTypeOptionBuilder {
FieldType::SingleSelect
}
fn data_format(&self) -> &dyn TypeOptionDataFormat {
fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0
}
fn transform(&mut self, field_type: &FieldType, _type_option_data: String) {
match field_type {
FieldType::Checkbox => {
//add Yes and No options if it's not exist.
if self.0.options.iter().find(|option| option.name == CHECK).is_none() {
let check_option = SelectOptionPB::with_color(CHECK, SelectOptionColorPB::Green);
self.0.options.push(check_option);
}
if self.0.options.iter().find(|option| option.name == UNCHECK).is_none() {
let uncheck_option = SelectOptionPB::with_color(UNCHECK, SelectOptionColorPB::Yellow);
self.0.options.push(uncheck_option);
}
}
FieldType::MultiSelect => {}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use crate::entities::FieldType;
use crate::services::cell::CellDataOperation;
use crate::services::field::type_options::*;
use crate::services::field::FieldBuilder;
use crate::services::field::{FieldBuilder, TypeOptionBuilder};
#[test]
fn single_select_transform_with_checkbox_type_option_test() {
let checkbox_type_option_builder = CheckboxTypeOptionBuilder::default();
let checkbox_type_option_data = checkbox_type_option_builder.serializer().json_str();
let mut single_select = SingleSelectTypeOptionBuilder::default();
single_select.transform(&FieldType::Checkbox, checkbox_type_option_data.clone());
debug_assert_eq!(single_select.0.options.len(), 2);
// Already contain the yes/no option. It doesn't need to insert new options
single_select.transform(&FieldType::Checkbox, checkbox_type_option_data);
debug_assert_eq!(single_select.0.options.len(), 2);
}
#[test]
fn single_select_insert_multi_option_test() {

View File

@ -8,7 +8,9 @@ use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
use flowy_grid_data_model::revision::{
CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer,
};
use serde::{Deserialize, Serialize};
#[derive(Default)]
@ -21,9 +23,12 @@ impl TypeOptionBuilder for RichTextTypeOptionBuilder {
FieldType::RichText
}
fn data_format(&self) -> &dyn TypeOptionDataFormat {
fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0
}
fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
// Do nothing
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]

View File

@ -6,7 +6,9 @@ use bytes::Bytes;
use fancy_regex::Regex;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
use flowy_grid_data_model::revision::{
CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer,
};
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
@ -20,9 +22,13 @@ impl TypeOptionBuilder for URLTypeOptionBuilder {
FieldType::URL
}
fn data_format(&self) -> &dyn TypeOptionDataFormat {
fn serializer(&self) -> &dyn TypeOptionDataSerializer {
&self.0
}
fn transform(&mut self, _field_type: &FieldType, _type_option_data: String) {
// Do nothing
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]

View File

@ -5,7 +5,10 @@ use crate::manager::{GridTaskSchedulerRwLock, GridUser};
use crate::services::block_manager::GridBlockManager;
use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data, CellBytes};
use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder};
use crate::services::field::{
default_type_option_builder_from_type, type_option_builder_from_bytes, type_option_builder_from_json_str,
FieldBuilder,
};
use crate::services::filter::GridFilterService;
use crate::services::grid_view_manager::GridViewManager;
use crate::services::persistence::block_index::BlockIndexCache;
@ -87,12 +90,27 @@ impl GridRevisionEditor {
Ok(editor)
}
/// Save the type-option data to disk and send a `GridNotification::DidUpdateField` notification
/// to dart side.
///
/// It will do nothing if the passed-in type_option_data is empty
/// # Arguments
///
/// * `grid_id`: the id of the grid
/// * `field_id`: the id of the field
/// * `type_option_data`: the updated type-option data.
///
pub async fn update_field_type_option(
&self,
grid_id: &str,
field_id: &str,
type_option_data: Vec<u8>,
) -> FlowyResult<()> {
debug_assert!(!type_option_data.is_empty());
if type_option_data.is_empty() {
return Ok(());
}
let result = self.get_field_rev(field_id).await;
if result.is_none() {
tracing::warn!("Can't find the field with id: {}", field_id);
@ -124,7 +142,7 @@ impl GridRevisionEditor {
let mut field_rev = self.next_field_rev(field_type).await?;
if let Some(type_option_data) = type_option_data {
let type_option_builder = type_option_builder_from_bytes(type_option_data, field_type);
field_rev.insert_type_option(type_option_builder.data_format());
field_rev.insert_type_option(type_option_builder.serializer());
}
let _ = self
.modify(|grid| Ok(grid.create_field_rev(field_rev.clone(), None)?))
@ -165,7 +183,7 @@ impl GridRevisionEditor {
let _ = self
.modify(|grid| {
let changeset = grid.modify_field(field_id, |field_rev| {
Ok(f(field_rev).map_err(|e| CollaborateError::internal().context(e))?)
f(field_rev).map_err(|e| CollaborateError::internal().context(e))
})?;
is_changed = changeset.is_some();
Ok(changeset)
@ -197,26 +215,42 @@ impl GridRevisionEditor {
/// Switch the field with id to a new field type.
///
/// If the field type is not exist before, the default type option data will be created.
/// Each field type has its corresponding data, aka, the type option data. Check out [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid#fieldtype)
/// If the field type is not exist before, the default type-option data will be created.
/// Each field type has its corresponding data, aka, the type-option data. Check out [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid#fieldtype)
/// for more information
///
/// # Arguments
///
/// * `field_id`: the id of the field
/// * `field_type`: the new field type of the field
/// * `new_field_type`: the new field type of the field
///
pub async fn switch_to_field_type(&self, field_id: &str, field_type: &FieldType) -> FlowyResult<()> {
let type_option_builder = |field_type: &FieldTypeRevision| -> String {
let field_type: FieldType = field_type.into();
return default_type_option_builder_from_type(&field_type)
.data_format()
pub async fn switch_to_field_type(&self, field_id: &str, new_field_type: &FieldType) -> FlowyResult<()> {
//
let make_default_type_option = || -> String {
return default_type_option_builder_from_type(new_field_type)
.serializer()
.json_str();
};
let type_option_transform =
|prev_field_type: FieldTypeRevision, prev_type_option: Option<String>, current_type_option: String| {
let prev_field_type: FieldType = prev_field_type.into();
let mut type_option_builder = type_option_builder_from_json_str(&current_type_option, new_field_type);
if let Some(prev_type_option) = prev_type_option {
type_option_builder.transform(&prev_field_type, prev_type_option)
}
type_option_builder.serializer().json_str()
};
let _ = self
.modify(|grid| Ok(grid.switch_to_field(field_id, field_type.clone(), type_option_builder)?))
.modify(|grid| {
Ok(grid.switch_to_field(
field_id,
new_field_type.clone(),
make_default_type_option,
type_option_transform,
)?)
})
.await?;
let _ = self.notify_did_update_grid_field(field_id).await?;
@ -258,73 +292,69 @@ impl GridRevisionEditor {
Ok(field_revs)
}
/// Apply the changeset to field. Including the `name`,`field_type`,`width`,`visibility`,and `type_option_data`.
/// Do nothing if the passed-in params doesn't carry any changes.
///
/// # Arguments
///
/// * `params`: contains the changesets that is going to applied to the field.
/// Ignore the change if one of the properties is None.
///
/// * `field_type`: is used by `TypeOptionJsonDeserializer` to deserialize the type_option_data
///
#[tracing::instrument(level = "debug", skip_all, err)]
async fn update_field_rev(&self, params: FieldChangesetParams, field_type: FieldType) -> FlowyResult<()> {
let mut is_type_option_changed = false;
if !params.has_changes() {
return Ok(());
}
let _ = self
.modify(|grid| {
let deserializer = TypeOptionJsonDeserializer(field_type);
let changeset = grid.modify_field(&params.field_id, |field| {
let mut is_changed = None;
if let Some(name) = params.name {
field.name = name;
is_changed = Some(())
}
if let Some(desc) = params.desc {
field.desc = desc;
is_changed = Some(())
}
if let Some(field_type) = params.field_type {
field.ty = field_type;
is_changed = Some(())
}
if let Some(frozen) = params.frozen {
field.frozen = frozen;
is_changed = Some(())
}
if let Some(visibility) = params.visibility {
field.visibility = visibility;
is_changed = Some(())
}
if let Some(width) = params.width {
field.width = width;
is_changed = Some(())
}
if let Some(type_option_data) = params.type_option_data {
let deserializer = TypeOptionJsonDeserializer(field_type);
match deserializer.deserialize(type_option_data) {
Ok(json_str) => {
let field_type = field.ty;
field.insert_type_option_str(&field_type, json_str);
is_type_option_changed = true;
is_changed = Some(())
}
Err(err) => {
tracing::error!("Deserialize data to type option json failed: {}", err);
}
}
}
Ok(is_changed)
Ok(Some(()))
})?;
Ok(changeset)
})
.await?;
let _ = self.view_manager.did_update_view_field(&params.field_id).await?;
if is_type_option_changed {
let _ = self
.view_manager
.did_update_view_field_type_option(&params.field_id)
.await?;
} else {
let _ = self.view_manager.did_update_view_field(&params.field_id).await?;
}
Ok(())
}
@ -835,8 +865,8 @@ impl JsonDeserializer for TypeOptionJsonDeserializer {
fn deserialize(&self, type_option_data: Vec<u8>) -> CollaborateResult<String> {
// The type_option_data sent from Dart is serialized by protobuf.
let builder = type_option_builder_from_bytes(type_option_data, &self.0);
let json = builder.data_format().json_str();
tracing::trace!("Deserialize type option data to: {}", json);
let json = builder.serializer().json_str();
tracing::trace!("Deserialize type-option data to: {}", json);
Ok(json)
}
}

View File

@ -187,9 +187,9 @@ impl GridViewManager {
Ok(())
}
/// Notifies the view's field type option data is changed
/// For the moment, only the groups will be generated after the type option data changed. A
/// [FieldRevision] has a property named type_options contains a list of type option data.
/// Notifies the view's field type-option data is changed
/// For the moment, only the groups will be generated after the type-option data changed. A
/// [FieldRevision] has a property named type_options contains a list of type-option data.
/// # Arguments
///
/// * `field_id`: the id of the field in current view

View File

@ -87,7 +87,7 @@ pub trait GroupControllerActions: Send + Sync {
}
/// C: represents the group configuration that impl [GroupConfigurationSerde]
/// T: the type option data deserializer that impl [TypeOptionDataDeserializer]
/// T: the type-option data deserializer that impl [TypeOptionDataDeserializer]
/// G: the group generator, [GroupGenerator]
/// P: the parser that impl [CellBytesParser] for the CellBytes
pub struct GenericGroupController<C, T, G, P> {

View File

@ -76,7 +76,7 @@ impl GridFieldTest {
} => {
let field_revs = self.editor.get_field_revs(None).await.unwrap();
let field_rev = field_revs[field_index].as_ref();
let type_option_data = field_rev.get_type_option_str(field_rev.ty.clone()).unwrap();
let type_option_data = field_rev.get_type_option_str(field_rev.ty).unwrap();
assert_eq!(type_option_data, expected_type_option_data);
}
}

View File

@ -4,7 +4,7 @@ use crate::grid::field_test::util::*;
use flowy_grid::entities::FieldChangesetParams;
use flowy_grid::services::field::selection_type_option::SelectOptionPB;
use flowy_grid::services::field::SingleSelectTypeOptionPB;
use flowy_grid_data_model::revision::TypeOptionDataFormat;
use flowy_grid_data_model::revision::TypeOptionDataSerializer;
#[tokio::test]
async fn grid_create_field() {
@ -15,7 +15,7 @@ async fn grid_create_field() {
CreateField { params },
AssertFieldTypeOptionEqual {
field_index: test.field_count(),
expected_type_option_data: field_rev.get_type_option_str(field_rev.ty.clone()).unwrap(),
expected_type_option_data: field_rev.get_type_option_str(field_rev.ty).unwrap(),
},
];
test.run_scripts(scripts).await;
@ -25,7 +25,7 @@ async fn grid_create_field() {
CreateField { params },
AssertFieldTypeOptionEqual {
field_index: test.field_count(),
expected_type_option_data: field_rev.get_type_option_str(field_rev.ty.clone()).unwrap(),
expected_type_option_data: field_rev.get_type_option_str(field_rev.ty).unwrap(),
},
];
test.run_scripts(scripts).await;
@ -63,7 +63,7 @@ async fn grid_update_field_with_empty_change() {
UpdateField { changeset },
AssertFieldTypeOptionEqual {
field_index: create_field_index,
expected_type_option_data: field_rev.get_type_option_str(field_rev.ty.clone()).unwrap(),
expected_type_option_data: field_rev.get_type_option_str(field_rev.ty).unwrap(),
},
];
test.run_scripts(scripts).await;
@ -100,9 +100,7 @@ async fn grid_update_field() {
UpdateField { changeset },
AssertFieldTypeOptionEqual {
field_index: create_field_index,
expected_type_option_data: expected_field_rev
.get_type_option_str(expected_field_rev.ty.clone())
.unwrap(),
expected_type_option_data: expected_field_rev.get_type_option_str(expected_field_rev.ty).unwrap(),
},
];
test.run_scripts(scripts).await;

View File

@ -18,7 +18,7 @@ pub fn create_text_field(grid_id: &str) -> (CreateFieldParams, FieldRevision) {
.to_vec();
let type_option_builder = type_option_builder_from_bytes(type_option_data.clone(), &field_rev.ty.into());
field_rev.insert_type_option(type_option_builder.data_format());
field_rev.insert_type_option(type_option_builder.serializer());
let params = CreateFieldParams {
grid_id: grid_id.to_owned(),
@ -42,7 +42,7 @@ pub fn create_single_select_field(grid_id: &str) -> (CreateFieldParams, FieldRev
.to_vec();
let type_option_builder = type_option_builder_from_bytes(type_option_data.clone(), &field_rev.ty.into());
field_rev.insert_type_option(type_option_builder.data_format());
field_rev.insert_type_option(type_option_builder.serializer());
let params = CreateFieldParams {
grid_id: grid_id.to_owned(),

View File

@ -111,7 +111,7 @@ pub enum ErrorCode {
#[display(fmt = "The operation in this field is invalid")]
FieldInvalidOperation = 444,
#[display(fmt = "Field's type option data should not be empty")]
#[display(fmt = "Field's type-option data should not be empty")]
TypeOptionDataIsEmpty = 450,
#[display(fmt = "Group id is empty")]

View File

@ -108,7 +108,7 @@ pub struct FieldRevision {
/// type_options contains key/value pairs
/// key: id of the FieldType
/// value: type option data that can be parsed into specified TypeOptionStruct.
/// value: type-option data that can be parsed into specified TypeOptionStruct.
///
/// For example, CheckboxTypeOption, MultiSelectTypeOption etc.
#[serde(with = "indexmap::serde_seq")]
@ -149,7 +149,7 @@ impl FieldRevision {
pub fn insert_type_option<T>(&mut self, type_option: &T)
where
T: TypeOptionDataFormat + ?Sized,
T: TypeOptionDataSerializer + ?Sized,
{
let id = self.ty.to_string();
self.type_options.insert(id, type_option.json_str());
@ -172,9 +172,9 @@ impl FieldRevision {
}
}
/// The macro [impl_type_option] will implement the [TypeOptionDataEntry] for the type that
/// The macro [impl_type_option] will implement the [TypeOptionDataSerializer] for the type that
/// supports the serde trait and the TryInto<Bytes> trait.
pub trait TypeOptionDataFormat {
pub trait TypeOptionDataSerializer {
fn json_str(&self) -> String;
fn protobuf_bytes(&self) -> Bytes;
}

View File

@ -10,7 +10,6 @@ use lib_infra::util::move_vec_element;
use lib_ot::core::{DeltaBuilder, DeltaOperations, EmptyAttributes, OperationTransform};
use std::collections::HashMap;
use std::sync::Arc;
pub type GridOperations = DeltaOperations<EmptyAttributes>;
pub type GridOperationsBuilder = DeltaBuilder;
@ -139,22 +138,24 @@ impl GridRevisionPad {
///
/// * `field_id`: the id of the field
/// * `field_type`: the new field type of the field
/// * `type_option_builder`: builder for creating the field type's type option data
/// * `make_default_type_option`: create the field type's type-option data
/// * `type_option_transform`: create the field type's type-option data
///
///
pub fn switch_to_field<B, T>(
pub fn switch_to_field<DT, TT, T>(
&mut self,
field_id: &str,
field_type: T,
type_option_builder: B,
new_field_type: T,
make_default_type_option: DT,
type_option_transform: TT,
) -> CollaborateResult<Option<GridRevisionChangeset>>
where
B: FnOnce(&FieldTypeRevision) -> String,
DT: FnOnce() -> String,
TT: FnOnce(FieldTypeRevision, Option<String>, String) -> String,
T: Into<FieldTypeRevision>,
{
let field_type = field_type.into();
let new_field_type = new_field_type.into();
self.modify_grid(|grid_meta| {
//
match grid_meta.fields.iter_mut().find(|field_rev| field_rev.id == field_id) {
None => {
tracing::warn!("Can not find the field with id: {}", field_id);
@ -162,13 +163,25 @@ impl GridRevisionPad {
}
Some(field_rev) => {
let mut_field_rev = Arc::make_mut(field_rev);
// If the type option data isn't exist before, creating the default type option data.
if mut_field_rev.get_type_option_str(field_type).is_none() {
let type_option_json = type_option_builder(&field_type);
mut_field_rev.insert_type_option_str(&field_type, type_option_json);
let old_field_type_rev = mut_field_rev.ty.clone();
let old_field_type_option = mut_field_rev.get_type_option_str(mut_field_rev.ty);
match mut_field_rev.get_type_option_str(new_field_type) {
Some(new_field_type_option) => {
//
let transformed_type_option =
type_option_transform(old_field_type_rev, old_field_type_option, new_field_type_option);
mut_field_rev.insert_type_option_str(&new_field_type, transformed_type_option);
}
None => {
// If the type-option data isn't exist before, creating the default type-option data.
let new_field_type_option = make_default_type_option();
let transformed_type_option =
type_option_transform(old_field_type_rev, old_field_type_option, new_field_type_option);
mut_field_rev.insert_type_option_str(&new_field_type, transformed_type_option);
}
}
mut_field_rev.ty = field_type;
mut_field_rev.ty = new_field_type;
Ok(Some(()))
}
}