mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: insert a new field to the left or right of an existing one (#4022)
* feat: allow inserting fields before or after a certain field * fix: tauri build * chore: implement frontend * test: rust-lib tests * test: integration test * chore: point to temp collab rev * chore: bump collab rev * chore: fix tauri build * chore: fix the tauri build, for real this time * fix: new field editor show detail not general --------- Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
@ -4,7 +4,7 @@ use std::fmt::{Display, Formatter};
|
||||
use std::sync::Arc;
|
||||
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::views::FieldOrder;
|
||||
use collab_database::views::{FieldOrder, OrderObjectPosition};
|
||||
use serde_repr::*;
|
||||
use strum_macros::{EnumCount as EnumCountMacro, EnumIter};
|
||||
|
||||
@ -155,30 +155,94 @@ pub struct CreateFieldPayloadPB {
|
||||
#[pb(index = 2)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub field_name: Option<String>,
|
||||
|
||||
/// If the type_option_data is not empty, it will be used to create the field.
|
||||
/// Otherwise, the default value will be used.
|
||||
#[pb(index = 3, one_of)]
|
||||
#[pb(index = 4, one_of)]
|
||||
pub type_option_data: Option<Vec<u8>>,
|
||||
|
||||
#[pb(index = 5)]
|
||||
pub field_position: CreateFieldPosition,
|
||||
|
||||
#[pb(index = 6, one_of)]
|
||||
pub target_field_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf_Enum)]
|
||||
#[repr(u8)]
|
||||
pub enum CreateFieldPosition {
|
||||
#[default]
|
||||
End = 0,
|
||||
Start = 1,
|
||||
Before = 2,
|
||||
After = 3,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CreateFieldParams {
|
||||
pub view_id: String,
|
||||
pub field_name: Option<String>,
|
||||
pub field_type: FieldType,
|
||||
/// If the type_option_data is not empty, it will be used to create the field.
|
||||
/// Otherwise, the default value will be used.
|
||||
pub type_option_data: Option<Vec<u8>>,
|
||||
pub position: OrderObjectPosition,
|
||||
}
|
||||
|
||||
impl TryInto<CreateFieldParams> for CreateFieldPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<CreateFieldParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
|
||||
|
||||
let field_name = match self.field_name {
|
||||
Some(name) => Some(
|
||||
NotEmptyStr::parse(name)
|
||||
.map_err(|_| ErrorCode::InvalidParams)?
|
||||
.0,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let position = match &self.field_position {
|
||||
CreateFieldPosition::Start => {
|
||||
if self.target_field_id.is_some() {
|
||||
return Err(ErrorCode::InvalidParams);
|
||||
}
|
||||
OrderObjectPosition::Start
|
||||
},
|
||||
CreateFieldPosition::End => {
|
||||
if self.target_field_id.is_some() {
|
||||
return Err(ErrorCode::InvalidParams);
|
||||
}
|
||||
OrderObjectPosition::End
|
||||
},
|
||||
CreateFieldPosition::Before => {
|
||||
let field_id = self
|
||||
.target_field_id
|
||||
.ok_or_else(|| ErrorCode::InvalidParams)?;
|
||||
let field_id = NotEmptyStr::parse(field_id)
|
||||
.map_err(|_| ErrorCode::InvalidParams)?
|
||||
.0;
|
||||
OrderObjectPosition::Before(field_id)
|
||||
},
|
||||
CreateFieldPosition::After => {
|
||||
let field_id = self
|
||||
.target_field_id
|
||||
.ok_or_else(|| ErrorCode::InvalidParams)?;
|
||||
let field_id = NotEmptyStr::parse(field_id)
|
||||
.map_err(|_| ErrorCode::InvalidParams)?
|
||||
.0;
|
||||
OrderObjectPosition::After(field_id)
|
||||
},
|
||||
};
|
||||
|
||||
Ok(CreateFieldParams {
|
||||
view_id: view_id.0,
|
||||
field_name,
|
||||
field_type: self.field_type,
|
||||
type_option_data: self.type_option_data,
|
||||
position,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -471,6 +535,7 @@ pub struct FieldChangesetParams {
|
||||
/// it would be better to append it to the end of the list.
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Hash,
|
||||
|
@ -2,6 +2,7 @@ use std::sync::{Arc, Weak};
|
||||
|
||||
use collab_database::database::gen_row_id;
|
||||
use collab_database::rows::RowId;
|
||||
use collab_database::views::OrderObjectPosition;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
@ -319,16 +320,14 @@ pub(crate) async fn get_field_type_option_data_handler(
|
||||
|
||||
/// Create TypeOptionPB and save it. Return the FieldTypeOptionData.
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn create_field_type_option_data_handler(
|
||||
pub(crate) async fn create_field_handler(
|
||||
data: AFPluginData<CreateFieldPayloadPB>,
|
||||
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||
) -> DataResult<TypeOptionPB, 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.view_id, ¶ms.field_type, params.type_option_data)
|
||||
.await;
|
||||
let (field, data) = database_editor.create_field_with_type_option(¶ms).await;
|
||||
|
||||
let data = TypeOptionPB {
|
||||
view_id: params.view_id,
|
||||
@ -449,12 +448,16 @@ pub(crate) async fn create_row_handler(
|
||||
CellBuilder::with_cells(params.cell_data_by_field_id.unwrap_or_default(), &fields).build();
|
||||
let view_id = params.view_id;
|
||||
let group_id = params.group_id;
|
||||
let position = match params.start_row_id {
|
||||
Some(row_id) => OrderObjectPosition::After(row_id.into()),
|
||||
None => OrderObjectPosition::Start,
|
||||
};
|
||||
let params = collab_database::rows::CreateRowParams {
|
||||
id: gen_row_id(),
|
||||
cells,
|
||||
height: 60,
|
||||
visibility: true,
|
||||
prev_row_id: params.start_row_id,
|
||||
row_position: position,
|
||||
timestamp: timestamp(),
|
||||
};
|
||||
match database_editor
|
||||
|
@ -31,7 +31,7 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
|
||||
.event(DatabaseEvent::DuplicateField, duplicate_field_handler)
|
||||
.event(DatabaseEvent::MoveField, move_field_handler)
|
||||
.event(DatabaseEvent::GetTypeOption, get_field_type_option_data_handler)
|
||||
.event(DatabaseEvent::CreateTypeOption, create_field_type_option_data_handler)
|
||||
.event(DatabaseEvent::CreateField, create_field_handler)
|
||||
// Row
|
||||
.event(DatabaseEvent::CreateRow, create_row_handler)
|
||||
.event(DatabaseEvent::GetRow, get_row_handler)
|
||||
@ -182,9 +182,10 @@ pub enum DatabaseEvent {
|
||||
#[event(input = "TypeOptionPathPB", output = "TypeOptionPB")]
|
||||
GetTypeOption = 23,
|
||||
|
||||
/// [CreateTypeOption] event is used to create a new FieldTypeOptionData.
|
||||
/// [CreateField] event is used to create a new field with an optional
|
||||
/// TypeOptionData.
|
||||
#[event(input = "CreateFieldPayloadPB", output = "TypeOptionPB")]
|
||||
CreateTypeOption = 24,
|
||||
CreateField = 24,
|
||||
|
||||
#[event(input = "DatabaseViewIdPB", output = "FieldPB")]
|
||||
GetPrimaryField = 25,
|
||||
|
@ -5,7 +5,7 @@ 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};
|
||||
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
|
||||
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting, OrderObjectPosition};
|
||||
use futures::StreamExt;
|
||||
use tokio::sync::{broadcast, RwLock};
|
||||
use tracing::{event, warn};
|
||||
@ -470,25 +470,26 @@ impl DatabaseEditor {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn create_field_with_type_option(
|
||||
&self,
|
||||
view_id: &str,
|
||||
field_type: &FieldType,
|
||||
type_option_data: Option<Vec<u8>>,
|
||||
) -> (Field, Bytes) {
|
||||
let name = field_type.default_name();
|
||||
let type_option_data = match type_option_data {
|
||||
None => default_type_option_data_from_type(field_type),
|
||||
Some(type_option_data) => type_option_data_from_pb_or_default(type_option_data, field_type),
|
||||
pub async fn create_field_with_type_option(&self, params: &CreateFieldParams) -> (Field, Bytes) {
|
||||
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 (index, field) = self.database.lock().create_field_with_mut(
|
||||
view_id,
|
||||
¶ms.view_id,
|
||||
name,
|
||||
field_type.into(),
|
||||
params.field_type.into(),
|
||||
¶ms.position,
|
||||
|field| {
|
||||
field
|
||||
.type_options
|
||||
.insert(field_type.to_string(), type_option_data.clone());
|
||||
.insert(params.field_type.to_string(), type_option_data.clone());
|
||||
},
|
||||
default_field_settings_by_layout_map(),
|
||||
);
|
||||
@ -497,7 +498,10 @@ impl DatabaseEditor {
|
||||
.notify_did_insert_database_field(field.clone(), index)
|
||||
.await;
|
||||
|
||||
(field, type_option_to_pb(type_option_data, field_type))
|
||||
(
|
||||
field,
|
||||
type_option_to_pb(type_option_data, ¶ms.field_type),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn move_field(
|
||||
@ -1223,6 +1227,7 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl {
|
||||
view_id,
|
||||
name.to_string(),
|
||||
field_type.clone().into(),
|
||||
&OrderObjectPosition::default(),
|
||||
|field| {
|
||||
field
|
||||
.type_options
|
||||
|
@ -1,6 +1,6 @@
|
||||
use collab_database::database::{gen_field_id, MutexDatabase};
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::views::{DatabaseLayout, LayoutSetting};
|
||||
use collab_database::views::{DatabaseLayout, LayoutSetting, OrderObjectPosition};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::entities::FieldType;
|
||||
@ -87,10 +87,12 @@ impl DatabaseLayoutDepsResolver {
|
||||
tracing::trace!("Create a new date field after layout type change");
|
||||
let field = self.create_date_field();
|
||||
let field_id = field.id.clone();
|
||||
self
|
||||
.database
|
||||
.lock()
|
||||
.create_field(field, default_field_settings_by_layout_map());
|
||||
self.database.lock().create_field(
|
||||
None,
|
||||
field,
|
||||
&OrderObjectPosition::End,
|
||||
default_field_settings_by_layout_map(),
|
||||
);
|
||||
field_id
|
||||
},
|
||||
Some(date_field) => date_field.id,
|
||||
|
Reference in New Issue
Block a user