mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: some bugs (#2639)
* fix: invalid index when insert row * fix: auto fill 0 in front of number start with . * fix: #2591 * chore: split the update at and create at test * chore: fix tauri build
This commit is contained in:
@ -142,11 +142,15 @@ pub struct RowIdPB {
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub row_id: String,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub group_id: Option<String>,
|
||||
}
|
||||
|
||||
pub struct RowIdParams {
|
||||
pub view_id: String,
|
||||
pub row_id: RowId,
|
||||
pub group_id: Option<String>,
|
||||
}
|
||||
|
||||
impl TryInto<RowIdParams> for RowIdPB {
|
||||
@ -154,10 +158,19 @@ impl TryInto<RowIdParams> for RowIdPB {
|
||||
|
||||
fn try_into(self) -> Result<RowIdParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
|
||||
let group_id = match self.group_id {
|
||||
Some(group_id) => Some(
|
||||
NotEmptyStr::parse(group_id)
|
||||
.map_err(|_| ErrorCode::GroupIdIsEmpty)?
|
||||
.0,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(RowIdParams {
|
||||
view_id: view_id.0,
|
||||
row_id: RowId::from(self.row_id),
|
||||
group_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use flowy_derive::ProtoBuf;
|
||||
use crate::entities::{InsertedRowPB, UpdatedRowPB};
|
||||
|
||||
#[derive(Debug, Default, Clone, ProtoBuf)]
|
||||
pub struct RowsVisibilityChangesetPB {
|
||||
pub struct RowsVisibilityChangePB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
@ -15,7 +15,7 @@ pub struct RowsVisibilityChangesetPB {
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, ProtoBuf)]
|
||||
pub struct RowsChangesetPB {
|
||||
pub struct RowsChangePB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
@ -29,27 +29,27 @@ pub struct RowsChangesetPB {
|
||||
pub updated_rows: Vec<UpdatedRowPB>,
|
||||
}
|
||||
|
||||
impl RowsChangesetPB {
|
||||
pub fn from_insert(view_id: String, inserted_rows: Vec<InsertedRowPB>) -> Self {
|
||||
impl RowsChangePB {
|
||||
pub fn from_insert(view_id: String, inserted_row: InsertedRowPB) -> Self {
|
||||
Self {
|
||||
view_id,
|
||||
inserted_rows,
|
||||
inserted_rows: vec![inserted_row],
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_delete(view_id: String, deleted_rows: Vec<String>) -> Self {
|
||||
pub fn from_delete(view_id: String, deleted_row: String) -> Self {
|
||||
Self {
|
||||
view_id,
|
||||
deleted_rows,
|
||||
deleted_rows: vec![deleted_row],
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_update(view_id: String, updated_rows: Vec<UpdatedRowPB>) -> Self {
|
||||
pub fn from_update(view_id: String, updated_row: UpdatedRowPB) -> Self {
|
||||
Self {
|
||||
view_id,
|
||||
updated_rows,
|
||||
updated_rows: vec![updated_row],
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
use collab_database::database::gen_row_id;
|
||||
use std::sync::Arc;
|
||||
|
||||
use collab_database::rows::RowId;
|
||||
use collab_database::views::DatabaseLayout;
|
||||
use lib_infra::util::timestamp;
|
||||
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
|
||||
|
||||
use crate::entities::*;
|
||||
use crate::manager::DatabaseManager2;
|
||||
use crate::services::cell::CellBuilder;
|
||||
|
||||
use crate::services::field::{
|
||||
type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
|
||||
@ -304,7 +307,7 @@ pub(crate) async fn duplicate_row_handler(
|
||||
let params: RowIdParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||
database_editor
|
||||
.duplicate_row(¶ms.view_id, ¶ms.row_id)
|
||||
.duplicate_row(¶ms.view_id, params.group_id, ¶ms.row_id)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
@ -329,7 +332,23 @@ pub(crate) async fn create_row_handler(
|
||||
) -> DataResult<RowPB, FlowyError> {
|
||||
let params: CreateRowParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||
match database_editor.create_row(params).await? {
|
||||
let fields = database_editor.get_fields(¶ms.view_id, None);
|
||||
let cells =
|
||||
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 params = collab_database::rows::CreateRowParams {
|
||||
id: gen_row_id(),
|
||||
cells,
|
||||
height: 60,
|
||||
visibility: true,
|
||||
prev_row_id: params.start_row_id,
|
||||
timestamp: timestamp(),
|
||||
};
|
||||
match database_editor
|
||||
.create_row(&view_id, group_id, params)
|
||||
.await?
|
||||
{
|
||||
None => Err(FlowyError::internal().context("Create row fail")),
|
||||
Some(row) => data_result_ok(RowPB::from(row)),
|
||||
}
|
||||
|
@ -3,9 +3,9 @@ use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bytes::Bytes;
|
||||
use collab_database::database::{gen_row_id, timestamp, Database as InnerDatabase};
|
||||
use collab_database::database::{timestamp, Database as InnerDatabase};
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{Cell, Cells, Row, RowCell, RowId};
|
||||
use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowId};
|
||||
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
|
||||
use parking_lot::Mutex;
|
||||
use tokio::sync::{broadcast, RwLock};
|
||||
@ -16,14 +16,14 @@ use lib_infra::future::{to_fut, Fut};
|
||||
|
||||
use crate::entities::{
|
||||
AlterFilterParams, AlterSortParams, CalendarEventPB, CellChangesetNotifyPB, CellPB,
|
||||
CreateRowParams, DatabaseFieldChangesetPB, DatabasePB, DatabaseViewSettingPB, DeleteFilterParams,
|
||||
DatabaseFieldChangesetPB, DatabasePB, DatabaseViewSettingPB, DeleteFilterParams,
|
||||
DeleteGroupParams, DeleteSortParams, FieldChangesetParams, FieldIdPB, FieldPB, FieldType,
|
||||
GroupPB, IndexFieldPB, InsertGroupParams, InsertedRowPB, LayoutSettingParams, RepeatedFilterPB,
|
||||
RepeatedGroupPB, RepeatedSortPB, RowPB, RowsChangesetPB, SelectOptionCellDataPB, SelectOptionPB,
|
||||
RepeatedGroupPB, RepeatedSortPB, RowPB, RowsChangePB, SelectOptionCellDataPB, SelectOptionPB,
|
||||
};
|
||||
use crate::notification::{send_notification, DatabaseNotification};
|
||||
use crate::services::cell::{
|
||||
apply_cell_changeset, get_cell_protobuf, insert_date_cell, AnyTypeCache, CellBuilder, CellCache,
|
||||
apply_cell_changeset, get_cell_protobuf, insert_date_cell, AnyTypeCache, CellCache,
|
||||
ToCellChangeset,
|
||||
};
|
||||
use crate::services::database::util::database_view_setting_pb_from_view;
|
||||
@ -274,8 +274,17 @@ impl DatabaseEditor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn duplicate_row(&self, view_id: &str, row_id: &RowId) {
|
||||
let _ = self.database.lock().duplicate_row(view_id, row_id);
|
||||
// consider returning a result. But most of the time, it should be fine to just ignore the error.
|
||||
pub async fn duplicate_row(&self, view_id: &str, group_id: Option<String>, row_id: &RowId) {
|
||||
let params = self.database.lock().duplicate_row(row_id);
|
||||
match params {
|
||||
None => {
|
||||
tracing::warn!("Failed to duplicate row: {}", row_id);
|
||||
},
|
||||
Some(params) => {
|
||||
let _ = self.create_row(view_id, group_id, params).await;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn move_row(&self, view_id: &str, from: RowId, to: RowId) {
|
||||
@ -292,39 +301,30 @@ impl DatabaseEditor {
|
||||
|
||||
let delete_row_id = from.into_inner();
|
||||
let insert_row = InsertedRowPB::from(&row).with_index(to_index as i32);
|
||||
let changeset =
|
||||
RowsChangesetPB::from_move(view_id.to_string(), vec![delete_row_id], vec![insert_row]);
|
||||
let changes =
|
||||
RowsChangePB::from_move(view_id.to_string(), vec![delete_row_id], vec![insert_row]);
|
||||
send_notification(view_id, DatabaseNotification::DidUpdateViewRows)
|
||||
.payload(changeset)
|
||||
.payload(changes)
|
||||
.send();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_row(&self, params: CreateRowParams) -> FlowyResult<Option<Row>> {
|
||||
let fields = self.database.lock().get_fields(¶ms.view_id, None);
|
||||
let mut cells =
|
||||
CellBuilder::with_cells(params.cell_data_by_field_id.unwrap_or_default(), &fields).build();
|
||||
pub async fn create_row(
|
||||
&self,
|
||||
view_id: &str,
|
||||
group_id: Option<String>,
|
||||
mut params: CreateRowParams,
|
||||
) -> FlowyResult<Option<Row>> {
|
||||
for view in self.database_views.editors().await {
|
||||
view.v_will_create_row(&mut cells, ¶ms.group_id).await;
|
||||
view.v_will_create_row(&mut params.cells, &group_id).await;
|
||||
}
|
||||
|
||||
let result = self.database.lock().create_row_in_view(
|
||||
¶ms.view_id,
|
||||
collab_database::rows::CreateRowParams {
|
||||
id: gen_row_id(),
|
||||
cells,
|
||||
height: 60,
|
||||
visibility: true,
|
||||
prev_row_id: params.start_row_id,
|
||||
timestamp: timestamp(),
|
||||
},
|
||||
);
|
||||
|
||||
let result = self.database.lock().create_row_in_view(view_id, params);
|
||||
if let Some((index, row_order)) = result {
|
||||
tracing::trace!("create row: {:?} at {}", row_order, index);
|
||||
let row = self.database.lock().get_row(&row_order.id);
|
||||
if let Some(row) = row {
|
||||
for view in self.database_views.editors().await {
|
||||
view.v_did_create_row(&row, ¶ms.group_id, index).await;
|
||||
view.v_did_create_row(&row, &group_id, index).await;
|
||||
}
|
||||
return Ok(Some(row));
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
#![allow(clippy::while_let_loop)]
|
||||
use crate::entities::{
|
||||
DatabaseViewSettingPB, FilterChangesetNotificationPB, GroupChangesetPB, GroupRowsNotificationPB,
|
||||
ReorderAllRowsPB, ReorderSingleRowPB, RowsVisibilityChangesetPB, SortChangesetNotificationPB,
|
||||
ReorderAllRowsPB, ReorderSingleRowPB, RowsVisibilityChangePB, SortChangesetNotificationPB,
|
||||
};
|
||||
use crate::notification::{send_notification, DatabaseNotification};
|
||||
use crate::services::filter::FilterResultNotification;
|
||||
@ -38,7 +38,7 @@ impl DatabaseViewChangedReceiverRunner {
|
||||
.for_each(|changed| async {
|
||||
match changed {
|
||||
DatabaseViewChanged::FilterNotification(notification) => {
|
||||
let changeset = RowsVisibilityChangesetPB {
|
||||
let changeset = RowsVisibilityChangePB {
|
||||
view_id: notification.view_id,
|
||||
visible_rows: notification.visible_rows,
|
||||
invisible_rows: notification
|
||||
|
@ -16,7 +16,7 @@ use crate::entities::{
|
||||
AlterFilterParams, AlterSortParams, CalendarEventPB, DeleteFilterParams, DeleteGroupParams,
|
||||
DeleteSortParams, FieldType, GroupChangesetPB, GroupPB, GroupRowsNotificationPB,
|
||||
InsertGroupParams, InsertedGroupPB, InsertedRowPB, LayoutSettingPB, LayoutSettingParams, RowPB,
|
||||
RowsChangesetPB, SortChangesetNotificationPB, SortPB,
|
||||
RowsChangePB, SortChangesetNotificationPB, SortPB,
|
||||
};
|
||||
use crate::notification::{send_notification, DatabaseNotification};
|
||||
use crate::services::cell::CellCache;
|
||||
@ -182,9 +182,10 @@ impl DatabaseViewEditor {
|
||||
// Send the group notification if the current view has groups
|
||||
match group_id.as_ref() {
|
||||
None => {
|
||||
let changeset = RowsChangesetPB::from_insert(self.view_id.clone(), vec![row.into()]);
|
||||
let row = InsertedRowPB::from(row).with_index(index as i32);
|
||||
let changes = RowsChangePB::from_insert(self.view_id.clone(), row);
|
||||
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
|
||||
.payload(changeset)
|
||||
.payload(changes)
|
||||
.send();
|
||||
},
|
||||
Some(group_id) => {
|
||||
@ -219,16 +220,15 @@ impl DatabaseViewEditor {
|
||||
notify_did_update_group_rows(changeset).await;
|
||||
}
|
||||
}
|
||||
let changeset =
|
||||
RowsChangesetPB::from_delete(self.view_id.clone(), vec![row.id.clone().into_inner()]);
|
||||
let changes = RowsChangePB::from_delete(self.view_id.clone(), row.id.clone().into_inner());
|
||||
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
|
||||
.payload(changeset)
|
||||
.payload(changes)
|
||||
.send();
|
||||
}
|
||||
|
||||
/// Notify the view that the row has been updated. If the view has groups,
|
||||
/// send the group notification with [GroupRowsNotificationPB]. Otherwise,
|
||||
/// send the view notification with [RowsChangesetPB]
|
||||
/// send the view notification with [RowsChangePB]
|
||||
pub async fn v_did_update_row(&self, old_row: &Option<Row>, row: &Row, field_id: &str) {
|
||||
let result = self
|
||||
.mut_group_controller(|group_controller, field| {
|
||||
@ -263,7 +263,7 @@ impl DatabaseViewEditor {
|
||||
row: RowOrder::from(row),
|
||||
field_ids: vec![field_id.to_string()],
|
||||
};
|
||||
let changeset = RowsChangesetPB::from_update(self.view_id.clone(), vec![update_row.into()]);
|
||||
let changeset = RowsChangePB::from_update(self.view_id.clone(), update_row.into());
|
||||
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
|
||||
.payload(changeset)
|
||||
.send();
|
||||
@ -784,18 +784,18 @@ impl DatabaseViewEditor {
|
||||
pub async fn handle_row_event(&self, event: Cow<'_, DatabaseRowEvent>) {
|
||||
let changeset = match event.into_owned() {
|
||||
DatabaseRowEvent::InsertRow(row) => {
|
||||
RowsChangesetPB::from_insert(self.view_id.clone(), vec![row.into()])
|
||||
RowsChangePB::from_insert(self.view_id.clone(), row.into())
|
||||
},
|
||||
DatabaseRowEvent::UpdateRow(row) => {
|
||||
RowsChangesetPB::from_update(self.view_id.clone(), vec![row.into()])
|
||||
RowsChangePB::from_update(self.view_id.clone(), row.into())
|
||||
},
|
||||
DatabaseRowEvent::DeleteRow(row_id) => {
|
||||
RowsChangesetPB::from_delete(self.view_id.clone(), vec![row_id.into_inner()])
|
||||
RowsChangePB::from_delete(self.view_id.clone(), row_id.into_inner())
|
||||
},
|
||||
DatabaseRowEvent::Move {
|
||||
deleted_row_id,
|
||||
inserted_row,
|
||||
} => RowsChangesetPB::from_move(
|
||||
} => RowsChangePB::from_move(
|
||||
self.view_id.clone(),
|
||||
vec![deleted_row_id.into_inner()],
|
||||
vec![inserted_row.into()],
|
||||
|
@ -16,8 +16,6 @@ mod tests {
|
||||
|
||||
// Input is empty String
|
||||
assert_number(&type_option, "", "", &field_type, &field);
|
||||
|
||||
// Input is letter
|
||||
assert_number(&type_option, "abc", "", &field_type, &field);
|
||||
assert_number(&type_option, "-123", "-123", &field_type, &field);
|
||||
assert_number(&type_option, "abc-123", "-123", &field_type, &field);
|
||||
@ -25,6 +23,7 @@ mod tests {
|
||||
assert_number(&type_option, "0.2", "0.2", &field_type, &field);
|
||||
assert_number(&type_option, "-0.2", "-0.2", &field_type, &field);
|
||||
assert_number(&type_option, "-$0.2", "0.2", &field_type, &field);
|
||||
assert_number(&type_option, ".2", "0.2", &field_type, &field);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -42,6 +41,7 @@ mod tests {
|
||||
assert_number(&type_option, "-0.2", "-$0.2", &field_type, &field);
|
||||
assert_number(&type_option, "-$0.2", "-$0.2", &field_type, &field);
|
||||
assert_number(&type_option, "-€0.2", "-$0.2", &field_type, &field);
|
||||
assert_number(&type_option, ".2", "$0.2", &field_type, &field);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -128,12 +128,25 @@ impl NumberTypeOption {
|
||||
Err(_) => Ok(NumberCellFormat::new()),
|
||||
}
|
||||
} else {
|
||||
let num_str = match EXTRACT_NUM_REGEX.captures(&num_cell_data.0) {
|
||||
Ok(Some(captures)) => captures
|
||||
.get(0)
|
||||
.map(|m| m.as_str().to_string())
|
||||
.unwrap_or_default(),
|
||||
_ => "".to_string(),
|
||||
// Test the input string is start with dot and only contains number.
|
||||
// If it is, add a 0 before the dot. For example, ".123" -> "0.123"
|
||||
let num_str = match START_WITH_DOT_NUM_REGEX.captures(&num_cell_data.0) {
|
||||
Ok(Some(captures)) => match captures.get(0).map(|m| m.as_str().to_string()) {
|
||||
Some(s) => {
|
||||
format!("0{}", s)
|
||||
},
|
||||
None => "".to_string(),
|
||||
},
|
||||
// Extract the number from the string.
|
||||
// For example, "123abc" -> "123". check out the number_type_option_input_test test for
|
||||
// more examples.
|
||||
_ => match EXTRACT_NUM_REGEX.captures(&num_cell_data.0) {
|
||||
Ok(Some(captures)) => captures
|
||||
.get(0)
|
||||
.map(|m| m.as_str().to_string())
|
||||
.unwrap_or_default(),
|
||||
_ => "".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
match Decimal::from_str(&num_str) {
|
||||
@ -142,7 +155,10 @@ impl NumberTypeOption {
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => NumberCellFormat::from_format_str(&num_cell_data.0, &self.format),
|
||||
_ => {
|
||||
// If the format is not number, use the format string to format the number.
|
||||
NumberCellFormat::from_format_str(&num_cell_data.0, &self.format)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -261,4 +277,5 @@ impl std::default::Default for NumberTypeOption {
|
||||
lazy_static! {
|
||||
static ref SCIENTIFIC_NOTATION_REGEX: Regex = Regex::new(r"([+-]?\d*\.?\d+)e([+-]?\d+)").unwrap();
|
||||
pub(crate) static ref EXTRACT_NUM_REGEX: Regex = Regex::new(r"-?\d+(\.\d+)?").unwrap();
|
||||
pub(crate) static ref START_WITH_DOT_NUM_REGEX: Regex = Regex::new(r"^\.\d+").unwrap();
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::services::cell::{CellBytesCustomParser, CellProtobufBlobParser, DecodedCellData};
|
||||
use crate::services::field::number_currency::Currency;
|
||||
use crate::services::field::{NumberFormat, EXTRACT_NUM_REGEX};
|
||||
use crate::services::field::{NumberFormat, EXTRACT_NUM_REGEX, START_WITH_DOT_NUM_REGEX};
|
||||
use bytes::Bytes;
|
||||
use flowy_error::FlowyResult;
|
||||
use rust_decimal::Decimal;
|
||||
@ -32,8 +32,8 @@ impl NumberCellFormat {
|
||||
Some(offset) => offset != 0,
|
||||
};
|
||||
|
||||
// Extract number from string.
|
||||
let num_str = extract_number(num_str);
|
||||
let num_str = auto_fill_zero_at_start_if_need(num_str);
|
||||
let num_str = extract_number(&num_str);
|
||||
match Decimal::from_str(&num_str) {
|
||||
Ok(mut decimal) => {
|
||||
decimal.set_sign_positive(sign_positive);
|
||||
@ -70,6 +70,16 @@ impl NumberCellFormat {
|
||||
}
|
||||
}
|
||||
|
||||
fn auto_fill_zero_at_start_if_need(num_str: &str) -> String {
|
||||
match START_WITH_DOT_NUM_REGEX.captures(num_str) {
|
||||
Ok(Some(captures)) => match captures.get(0).map(|m| m.as_str().to_string()) {
|
||||
Some(s) => format!("0{}", s),
|
||||
None => num_str.to_string(),
|
||||
},
|
||||
_ => num_str.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_number(num_str: &str) -> String {
|
||||
let mut matches = EXTRACT_NUM_REGEX.find_iter(num_str);
|
||||
let mut values = vec![];
|
||||
|
Reference in New Issue
Block a user