mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
parent
0bc0a72d8a
commit
1adf6530fe
@ -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.
|
||||
|
@ -142,6 +142,7 @@ pub(crate) async fn switch_to_field_handler(
|
||||
.await
|
||||
.unwrap_or(Arc::new(editor.next_field_rev(¶ms.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, ¶ms.field_type).await?;
|
||||
let _ = editor
|
||||
.update_field_type_option(¶ms.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),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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?;
|
||||
|
@ -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> {
|
||||
|
@ -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)]
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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)]
|
||||
|
@ -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)]
|
||||
|
@ -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(¤t_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(¶ms.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(¶ms.field_id).await?;
|
||||
if is_type_option_changed {
|
||||
let _ = self
|
||||
.view_manager
|
||||
.did_update_view_field_type_option(¶ms.field_id)
|
||||
.await?;
|
||||
} else {
|
||||
let _ = self.view_manager.did_update_view_field(¶ms.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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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> {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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(),
|
||||
|
@ -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")]
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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(()))
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user