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:
Richard Shiue
2023-11-29 04:42:53 +08:00
committed by GitHub
parent 38e3947b2d
commit 20b485bcfe
26 changed files with 478 additions and 256 deletions

View File

@ -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,

View File

@ -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(&params.view_id).await?;
let (field, data) = database_editor
.create_field_with_type_option(&params.view_id, &params.field_type, params.type_option_data)
.await;
let (field, data) = database_editor.create_field_with_type_option(&params).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

View File

@ -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,

View File

@ -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 &params.type_option_data {
None => default_type_option_data_from_type(&params.field_type),
Some(type_option_data) => {
type_option_data_from_pb_or_default(type_option_data.clone(), &params.field_type)
},
};
let (index, field) = self.database.lock().create_field_with_mut(
view_id,
&params.view_id,
name,
field_type.into(),
params.field_type.into(),
&params.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, &params.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

View File

@ -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,