mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
refactor: database field type option (#4136)
* refactor: include field type option in FieldPB * refactor: adapt changes on flutter * refactor: adapt changes on new tauri grid * refactor: adapt changes on old tauri grid/board * chore: merge
This commit is contained in:
@ -14,6 +14,7 @@ use flowy_error::ErrorCode;
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::position_entities::OrderObjectPositionPB;
|
||||
use crate::impl_into_field_type;
|
||||
use crate::services::field::{default_type_option_data_from_type, type_option_to_pb};
|
||||
|
||||
/// [FieldPB] defines a Field's attributes. Such as the name, field_type, and width. etc.
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
@ -35,17 +36,25 @@ pub struct FieldPB {
|
||||
|
||||
#[pb(index = 6)]
|
||||
pub is_primary: bool,
|
||||
|
||||
#[pb(index = 7)]
|
||||
pub type_option_data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl std::convert::From<Field> for FieldPB {
|
||||
fn from(field: Field) -> Self {
|
||||
impl FieldPB {
|
||||
pub fn new(field: Field) -> Self {
|
||||
let field_type = field.field_type.into();
|
||||
let type_option = field
|
||||
.get_any_type_option(field_type)
|
||||
.unwrap_or_else(|| default_type_option_data_from_type(&field_type));
|
||||
Self {
|
||||
id: field.id,
|
||||
name: field.name,
|
||||
field_type: FieldType::from(field.field_type),
|
||||
field_type,
|
||||
visibility: field.visibility,
|
||||
width: field.width as i32,
|
||||
is_primary: field.is_primary,
|
||||
type_option_data: type_option_to_pb(type_option, &field_type).to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,15 +148,6 @@ pub struct IndexFieldPB {
|
||||
pub index: i32,
|
||||
}
|
||||
|
||||
impl IndexFieldPB {
|
||||
pub fn from_field(field: Field, index: usize) -> Self {
|
||||
Self {
|
||||
field: FieldPB::from(field),
|
||||
index: index as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct CreateFieldPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
@ -236,50 +236,6 @@ impl TryInto<EditFieldParams> for UpdateFieldTypePayloadPB {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct TypeOptionPathPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub field_type: FieldType,
|
||||
}
|
||||
|
||||
pub struct TypeOptionPathParams {
|
||||
pub view_id: String,
|
||||
pub field_id: String,
|
||||
pub field_type: FieldType,
|
||||
}
|
||||
|
||||
impl TryInto<TypeOptionPathParams> for TypeOptionPathPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<TypeOptionPathParams, Self::Error> {
|
||||
let database_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
|
||||
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
|
||||
Ok(TypeOptionPathParams {
|
||||
view_id: database_id.0,
|
||||
field_id: field_id.0,
|
||||
field_type: self.field_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct TypeOptionPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field: FieldPB,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub type_option_data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Collection of the [FieldPB]
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct RepeatedFieldPB {
|
||||
|
@ -13,7 +13,7 @@ use crate::manager::DatabaseManager;
|
||||
use crate::services::cell::CellBuilder;
|
||||
use crate::services::field::checklist_type_option::ChecklistCellChangeset;
|
||||
use crate::services::field::{
|
||||
type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
|
||||
type_option_data_from_pb, DateCellChangeset, SelectOptionCellChangeset,
|
||||
};
|
||||
use crate::services::field_settings::FieldSettingsChangesetParams;
|
||||
use crate::services::group::GroupChangeset;
|
||||
@ -160,7 +160,7 @@ pub(crate) async fn get_fields_handler(
|
||||
let fields = database_editor
|
||||
.get_fields(¶ms.view_id, params.field_ids)
|
||||
.into_iter()
|
||||
.map(FieldPB::from)
|
||||
.map(FieldPB::new)
|
||||
.collect::<Vec<FieldPB>>()
|
||||
.into();
|
||||
data_result_ok(fields)
|
||||
@ -178,7 +178,7 @@ pub(crate) async fn get_primary_field_handler(
|
||||
.get_fields(&view_id, None)
|
||||
.into_iter()
|
||||
.filter(|field| field.is_primary)
|
||||
.map(FieldPB::from)
|
||||
.map(FieldPB::new)
|
||||
.collect::<Vec<FieldPB>>();
|
||||
|
||||
if fields.is_empty() {
|
||||
@ -217,8 +217,7 @@ pub(crate) async fn update_field_type_option_handler(
|
||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||
if let Some(old_field) = database_editor.get_field(¶ms.field_id) {
|
||||
let field_type = FieldType::from(old_field.field_type);
|
||||
let type_option_data =
|
||||
type_option_data_from_pb_or_default(params.type_option_data, &field_type);
|
||||
let type_option_data = type_option_data_from_pb(params.type_option_data, &field_type)?;
|
||||
database_editor
|
||||
.update_field_type_option(
|
||||
¶ms.view_id,
|
||||
@ -293,46 +292,19 @@ pub(crate) async fn duplicate_field_handler(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the FieldTypeOptionData if the Field exists otherwise return record not found error.
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn get_field_type_option_data_handler(
|
||||
data: AFPluginData<TypeOptionPathPB>,
|
||||
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||
) -> DataResult<TypeOptionPB, FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let params: TypeOptionPathParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||
if let Some((field, data)) = database_editor
|
||||
.get_field_type_option_data(¶ms.field_id)
|
||||
.await
|
||||
{
|
||||
let data = TypeOptionPB {
|
||||
view_id: params.view_id,
|
||||
field: FieldPB::from(field),
|
||||
type_option_data: data.to_vec(),
|
||||
};
|
||||
data_result_ok(data)
|
||||
} else {
|
||||
Err(FlowyError::record_not_found())
|
||||
}
|
||||
}
|
||||
|
||||
/// Create TypeOptionPB and save it. Return the FieldTypeOptionData.
|
||||
/// Create a field and save it. Returns the [FieldPB] in the current view.
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn create_field_handler(
|
||||
data: AFPluginData<CreateFieldPayloadPB>,
|
||||
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||
) -> DataResult<TypeOptionPB, FlowyError> {
|
||||
) -> DataResult<FieldPB, FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let params: CreateFieldParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||
let (field, data) = database_editor.create_field_with_type_option(¶ms).await;
|
||||
let data = database_editor
|
||||
.create_field_with_type_option(params)
|
||||
.await?;
|
||||
|
||||
let data = TypeOptionPB {
|
||||
view_id: params.view_id,
|
||||
field: FieldPB::from(field),
|
||||
type_option_data: data.to_vec(),
|
||||
};
|
||||
data_result_ok(data)
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,6 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
|
||||
.event(DatabaseEvent::UpdateFieldType, switch_to_field_handler)
|
||||
.event(DatabaseEvent::DuplicateField, duplicate_field_handler)
|
||||
.event(DatabaseEvent::MoveField, move_field_handler)
|
||||
.event(DatabaseEvent::GetTypeOption, get_field_type_option_data_handler)
|
||||
.event(DatabaseEvent::CreateField, create_field_handler)
|
||||
// Row
|
||||
.event(DatabaseEvent::CreateRow, create_row_handler)
|
||||
@ -174,18 +173,9 @@ pub enum DatabaseEvent {
|
||||
#[event(input = "MoveFieldPayloadPB")]
|
||||
MoveField = 22,
|
||||
|
||||
/// [TypeOptionPathPB] event is used to get the FieldTypeOption data for a specific field type.
|
||||
///
|
||||
/// Check out the [TypeOptionPB] for more details. If the [FieldTypeOptionData] does exist
|
||||
/// for the target type, the [TypeOptionBuilder] will create the default data for that type.
|
||||
///
|
||||
/// Return the [TypeOptionPB] if there are no errors.
|
||||
#[event(input = "TypeOptionPathPB", output = "TypeOptionPB")]
|
||||
GetTypeOption = 23,
|
||||
|
||||
/// [CreateField] event is used to create a new field with an optional
|
||||
/// TypeOptionData.
|
||||
#[event(input = "CreateFieldPayloadPB", output = "TypeOptionPB")]
|
||||
#[event(input = "CreateFieldPayloadPB", output = "FieldPB")]
|
||||
CreateField = 24,
|
||||
|
||||
#[event(input = "DatabaseViewIdPB", output = "FieldPB")]
|
||||
|
@ -1,7 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bytes::Bytes;
|
||||
use collab_database::database::MutexDatabase;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowDetail, RowId};
|
||||
@ -28,8 +27,8 @@ use crate::services::database_view::{
|
||||
use crate::services::field::checklist_type_option::ChecklistCellChangeset;
|
||||
use crate::services::field::{
|
||||
default_type_option_data_from_type, select_type_option_from_field, transform_type_option,
|
||||
type_option_data_from_pb_or_default, type_option_to_pb, SelectOptionCellChangeset,
|
||||
SelectOptionIds, TimestampCellData, TypeOptionCellDataHandler, TypeOptionCellExt,
|
||||
type_option_data_from_pb, SelectOptionCellChangeset, SelectOptionIds, TimestampCellData,
|
||||
TypeOptionCellDataHandler, TypeOptionCellExt,
|
||||
};
|
||||
use crate::services::field_settings::{
|
||||
default_field_settings_by_layout_map, FieldSettings, FieldSettingsChangesetParams,
|
||||
@ -469,28 +468,20 @@ impl DatabaseEditor {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub async fn get_field_type_option_data(&self, field_id: &str) -> Option<(Field, Bytes)> {
|
||||
let field = self.database.lock().fields.get_field(field_id);
|
||||
field.map(|field| {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
let type_option = field
|
||||
.get_any_type_option(field_type)
|
||||
.unwrap_or_else(|| default_type_option_data_from_type(&field_type));
|
||||
(field, type_option_to_pb(type_option, &field_type))
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn create_field_with_type_option(&self, params: &CreateFieldParams) -> (Field, Bytes) {
|
||||
pub async fn create_field_with_type_option(
|
||||
&self,
|
||||
params: CreateFieldParams,
|
||||
) -> FlowyResult<FieldPB> {
|
||||
let name = params
|
||||
.field_name
|
||||
.clone()
|
||||
.unwrap_or_else(|| params.field_type.default_name());
|
||||
let type_option_data = match ¶ms.type_option_data {
|
||||
None => default_type_option_data_from_type(¶ms.field_type),
|
||||
Some(type_option_data) => {
|
||||
type_option_data_from_pb_or_default(type_option_data.clone(), ¶ms.field_type)
|
||||
},
|
||||
};
|
||||
|
||||
let type_option_data = params
|
||||
.type_option_data
|
||||
.and_then(|data| type_option_data_from_pb(data, ¶ms.field_type).ok())
|
||||
.unwrap_or(default_type_option_data_from_type(¶ms.field_type));
|
||||
|
||||
let (index, field) = self.database.lock().create_field_with_mut(
|
||||
¶ms.view_id,
|
||||
name,
|
||||
@ -499,7 +490,7 @@ impl DatabaseEditor {
|
||||
|field| {
|
||||
field
|
||||
.type_options
|
||||
.insert(params.field_type.to_string(), type_option_data.clone());
|
||||
.insert(params.field_type.to_string(), type_option_data);
|
||||
},
|
||||
default_field_settings_by_layout_map(),
|
||||
);
|
||||
@ -508,10 +499,7 @@ impl DatabaseEditor {
|
||||
.notify_did_insert_database_field(field.clone(), index)
|
||||
.await;
|
||||
|
||||
(
|
||||
field,
|
||||
type_option_to_pb(type_option_data, ¶ms.field_type),
|
||||
)
|
||||
Ok(FieldPB::new(field))
|
||||
}
|
||||
|
||||
pub async fn move_field(&self, params: MoveFieldParams) -> FlowyResult<()> {
|
||||
@ -539,7 +527,10 @@ impl DatabaseEditor {
|
||||
|
||||
if let Some(index) = new_index {
|
||||
let delete_field = FieldIdPB::from(params.from_field_id);
|
||||
let insert_field = IndexFieldPB::from_field(field, index);
|
||||
let insert_field = IndexFieldPB {
|
||||
field: FieldPB::new(field),
|
||||
index: index as i32,
|
||||
};
|
||||
let notified_changeset = DatabaseFieldChangesetPB {
|
||||
view_id: params.view_id,
|
||||
inserted_fields: vec![insert_field],
|
||||
@ -1058,7 +1049,10 @@ impl DatabaseEditor {
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
async fn notify_did_insert_database_field(&self, field: Field, index: usize) -> FlowyResult<()> {
|
||||
let database_id = self.database.lock().get_database_id();
|
||||
let index_field = IndexFieldPB::from_field(field, index);
|
||||
let index_field = IndexFieldPB {
|
||||
field: FieldPB::new(field),
|
||||
index: index as i32,
|
||||
};
|
||||
let notified_changeset = DatabaseFieldChangesetPB::insert(&database_id, vec![index_field]);
|
||||
let _ = self.notify_did_update_database(notified_changeset).await;
|
||||
Ok(())
|
||||
@ -1629,7 +1623,7 @@ fn notify_did_update_database_field(
|
||||
};
|
||||
|
||||
if let Some(field) = field {
|
||||
let updated_field = FieldPB::from(field);
|
||||
let updated_field = FieldPB::new(field);
|
||||
let notified_changeset =
|
||||
DatabaseFieldChangesetPB::update(&database_id, vec![updated_field.clone()]);
|
||||
|
||||
|
@ -175,12 +175,12 @@ pub trait TypeOptionCellDataCompare: TypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_option_data_from_pb_or_default<T: Into<Bytes>>(
|
||||
pub fn type_option_data_from_pb<T: Into<Bytes>>(
|
||||
bytes: T,
|
||||
field_type: &FieldType,
|
||||
) -> TypeOptionData {
|
||||
) -> Result<TypeOptionData, ProtobufError> {
|
||||
let bytes = bytes.into();
|
||||
let result: Result<TypeOptionData, ProtobufError> = match field_type {
|
||||
match field_type {
|
||||
FieldType::RichText => {
|
||||
RichTextTypeOptionPB::try_from(bytes).map(|pb| RichTextTypeOption::from(pb).into())
|
||||
},
|
||||
@ -206,9 +206,7 @@ pub fn type_option_data_from_pb_or_default<T: Into<Bytes>>(
|
||||
FieldType::Checklist => {
|
||||
ChecklistTypeOptionPB::try_from(bytes).map(|pb| ChecklistTypeOption::from(pb).into())
|
||||
},
|
||||
};
|
||||
|
||||
result.unwrap_or_else(|_| default_type_option_data_from_type(field_type))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_option_to_pb(type_option: TypeOptionData, field_type: &FieldType) -> Bytes {
|
||||
|
@ -64,7 +64,7 @@ impl DatabaseFieldTest {
|
||||
match script {
|
||||
FieldScript::CreateField { params } => {
|
||||
self.field_count += 1;
|
||||
self.editor.create_field_with_type_option(¶ms).await;
|
||||
let _ = self.editor.create_field_with_type_option(params).await;
|
||||
let fields = self.editor.get_fields(&self.view_id, None);
|
||||
assert_eq!(self.field_count, fields.len());
|
||||
},
|
||||
|
Reference in New Issue
Block a user