chore: add row test

This commit is contained in:
appflowy 2022-03-13 23:16:52 +08:00
parent 572e38ec1c
commit d101509b32
16 changed files with 191 additions and 164 deletions

View File

@ -24,7 +24,7 @@ pub(crate) async fn get_rows_handler(
) -> DataResult<RepeatedRow, FlowyError> {
let payload: QueryRowPayload = data.into_inner();
let editor = manager.get_grid_editor(&payload.grid_id)?;
let repeated_row: RepeatedRow = editor.get_rows(Some(payload.row_orders)).await?.into();
let repeated_row: RepeatedRow = editor.get_rows(payload.row_orders).await?.into();
data_result(repeated_row)
}
@ -50,10 +50,10 @@ pub(crate) async fn create_row_handler(
Ok(())
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]
#[tracing::instrument(level = "debug", skip_all, err)]
pub(crate) async fn update_cell_handler(
data: Data<Cell>,
manager: AppData<Arc<GridManager>>,
_manager: AppData<Arc<GridManager>>,
) -> Result<(), FlowyError> {
let _cell: Cell = data.into_inner();
// let editor = manager.get_grid_editor(id.as_ref())?;

View File

@ -2,7 +2,7 @@ use crate::services::field::{
CheckboxDescription, DateDescription, DateFormat, MoneySymbol, MultiSelectDescription, NumberDescription,
RichTextDescription, SelectOption, SingleSelectDescription, TimeFormat,
};
use flowy_grid_data_model::entities::{AnyData, Field, FieldType};
use flowy_grid_data_model::entities::{Field, FieldType};
pub struct FieldBuilder {
field: Field,

View File

@ -2,12 +2,12 @@
use crate::impl_from_and_to_type_option;
use crate::services::row::StringifyCellData;
use crate::services::util::*;
use bytes::Bytes;
use chrono::format::strftime::StrftimeItems;
use chrono::NaiveDateTime;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::FlowyError;
use flowy_grid_data_model::entities::{AnyData, Field, FieldType};
use flowy_grid_data_model::entities::{Field, FieldType};
use rust_decimal::Decimal;
use rusty_money::{
iso::{Currency, CNY, EUR, USD},
@ -25,12 +25,12 @@ pub struct RichTextDescription {
impl_from_and_to_type_option!(RichTextDescription, FieldType::RichText);
impl StringifyCellData for RichTextDescription {
fn str_from_cell_data(&self, data: AnyData) -> String {
data.to_string()
fn str_from_cell_data(&self, data: String) -> String {
data
}
fn str_to_cell_data(&self, s: &str) -> Result<AnyData, FlowyError> {
Ok(AnyData::from_str(self.field_type(), s))
fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
Ok(s.to_owned())
}
}
@ -43,16 +43,16 @@ pub struct CheckboxDescription {
impl_from_and_to_type_option!(CheckboxDescription, FieldType::Checkbox);
impl StringifyCellData for CheckboxDescription {
fn str_from_cell_data(&self, data: AnyData) -> String {
data.to_string()
fn str_from_cell_data(&self, data: String) -> String {
data
}
fn str_to_cell_data(&self, s: &str) -> Result<AnyData, FlowyError> {
fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
let s = match string_to_bool(s) {
true => "1",
false => "0",
};
Ok(AnyData::from_str(self.field_type(), s))
Ok(s.to_owned())
}
}
@ -89,30 +89,24 @@ impl DateDescription {
}
impl StringifyCellData for DateDescription {
fn str_from_cell_data(&self, data: AnyData) -> String {
match String::from_utf8(data.value) {
Ok(s) => match s.parse::<i64>() {
Ok(timestamp) => {
let native = NaiveDateTime::from_timestamp(timestamp, 0);
self.today_from_native(native)
}
Err(e) => {
tracing::debug!("DateDescription format {} fail. error: {:?}", s, e);
String::new()
}
},
fn str_from_cell_data(&self, data: String) -> String {
match data.parse::<i64>() {
Ok(timestamp) => {
let native = NaiveDateTime::from_timestamp(timestamp, 0);
self.today_from_native(native)
}
Err(e) => {
tracing::error!("DateDescription stringify any_data failed. {:?}", e);
tracing::debug!("DateDescription format {} fail. error: {:?}", data, e);
String::new()
}
}
}
fn str_to_cell_data(&self, s: &str) -> Result<AnyData, FlowyError> {
fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
let timestamp = s
.parse::<i64>()
.map_err(|e| FlowyError::internal().context(format!("Parse {} to i64 failed: {}", s, e)))?;
Ok(AnyData::from_str(self.field_type(), &format!("{}", timestamp)))
Ok(format!("{}", timestamp))
}
}
@ -210,12 +204,12 @@ pub struct SingleSelectDescription {
impl_from_and_to_type_option!(SingleSelectDescription, FieldType::SingleSelect);
impl StringifyCellData for SingleSelectDescription {
fn str_from_cell_data(&self, data: AnyData) -> String {
data.to_string()
fn str_from_cell_data(&self, data: String) -> String {
data
}
fn str_to_cell_data(&self, s: &str) -> Result<AnyData, FlowyError> {
Ok(AnyData::from_str(self.field_type(), s))
fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
Ok(s.to_owned())
}
}
@ -230,12 +224,12 @@ pub struct MultiSelectDescription {
}
impl_from_and_to_type_option!(MultiSelectDescription, FieldType::MultiSelect);
impl StringifyCellData for MultiSelectDescription {
fn str_from_cell_data(&self, data: AnyData) -> String {
data.to_string()
fn str_from_cell_data(&self, data: String) -> String {
data
}
fn str_to_cell_data(&self, s: &str) -> Result<AnyData, FlowyError> {
Ok(AnyData::from_str(self.field_type(), s))
fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
Ok(s.to_owned())
}
}
@ -322,24 +316,17 @@ impl NumberDescription {
}
impl StringifyCellData for NumberDescription {
fn str_from_cell_data(&self, data: AnyData) -> String {
match String::from_utf8(data.value) {
Ok(s) => match self.money_from_str(&s) {
Some(money_str) => money_str,
None => String::default(),
},
Err(e) => {
tracing::error!("NumberDescription stringify any_data failed. {:?}", e);
String::new()
}
fn str_from_cell_data(&self, data: String) -> String {
match self.money_from_str(&data) {
Some(money_str) => money_str,
None => String::default(),
}
}
fn str_to_cell_data(&self, s: &str) -> Result<AnyData, FlowyError> {
fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
let strip_symbol_money = strip_money_symbol(s);
let decimal = Decimal::from_str(&strip_symbol_money).map_err(|err| FlowyError::internal().context(err))?;
let money_str = decimal.to_string();
Ok(AnyData::from_str(self.field_type(), &money_str))
Ok(decimal.to_string())
}
}

View File

@ -1,7 +1,7 @@
use crate::manager::GridUser;
use crate::services::kv_persistence::{GridKVPersistence, KVTransaction};
use crate::services::grid_meta_editor::{ClientGridBlockMetaEditor, GridBlockMetaEditorManager};
use crate::services::grid_meta_editor::GridBlockMetaEditorManager;
use bytes::Bytes;
use dashmap::DashMap;
use flowy_collaboration::client_grid::{GridChange, GridMetaPad};
@ -9,13 +9,10 @@ use flowy_collaboration::entities::revision::Revision;
use flowy_collaboration::util::make_delta_from_revisions;
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::entities::{
Field, FieldChangeset, Grid, GridBlock, GridBlockChangeset, RepeatedField, RepeatedFieldOrder, RepeatedRow,
RepeatedRowOrder, Row,
};
use flowy_sync::disk::SQLiteGridBlockMetaRevisionPersistence;
use flowy_sync::{
RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder, RevisionPersistence,
Field, FieldChangeset, Grid, GridBlock, GridBlockChangeset, RepeatedFieldOrder, RepeatedRowOrder, Row,
};
use flowy_sync::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
use lib_infra::future::FutureResult;
use lib_ot::core::PlainTextAttributes;
use std::sync::Arc;
@ -83,23 +80,29 @@ impl ClientGridEditor {
pub async fn create_row(&self) -> FlowyResult<()> {
let fields = self.grid_meta_pad.read().await.get_fields(None)?;
match self.grid_meta_pad.read().await.get_blocks().last() {
let grid_block = match self.grid_meta_pad.read().await.get_blocks().last() {
None => Err(FlowyError::internal().context("There is no grid block in this grid")),
Some(grid_block) => {
let row_count = self.block_meta_manager.create_row(fields, grid_block).await?;
let change = GridBlockChangeset::from_row_count(&grid_block.id, row_count);
let _ = self.update_block(change).await?;
Ok(())
}
}
Some(grid_block) => Ok(grid_block.clone()),
}?;
let row_count = self.block_meta_manager.create_row(fields, &grid_block).await?;
let change = GridBlockChangeset::from_row_count(&grid_block.id, row_count);
let _ = self.update_block(change).await?;
Ok(())
}
pub async fn get_rows(&self, row_orders: Option<RepeatedRowOrder>) -> FlowyResult<Vec<Row>> {
pub async fn get_rows(&self, row_orders: RepeatedRowOrder) -> FlowyResult<Vec<Row>> {
let fields = self.grid_meta_pad.read().await.get_fields(None)?;
let rows = self.block_meta_manager.get_rows(fields, row_orders).await?;
Ok(rows)
}
pub async fn get_all_rows(&self) -> FlowyResult<Vec<Row>> {
let fields = self.grid_meta_pad.read().await.get_fields(None)?;
let grid_blocks = self.grid_meta_pad.read().await.get_blocks();
self.block_meta_manager.get_all_rows(grid_blocks, fields).await
}
pub async fn delete_rows(&self, row_orders: Option<RepeatedRowOrder>) -> FlowyResult<()> {
let row_counts = self.block_meta_manager.delete_rows(row_orders).await?;
for (block_id, row_count) in row_counts {

View File

@ -1,22 +1,20 @@
use crate::manager::GridUser;
use crate::services::row::{make_row_ids_per_block, make_rows, sort_rows, RowBuilder};
use crate::services::row::{make_row_by_row_id, make_row_ids_per_block, make_rows, RowBuilder};
use bytes::Bytes;
use dashmap::mapref::one::Ref;
use dashmap::DashMap;
use flowy_collaboration::client_grid::{GridBlockMetaChange, GridBlockMetaPad};
use flowy_collaboration::entities::revision::Revision;
use flowy_collaboration::util::make_delta_from_revisions;
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::entities::{
Field, GridBlock, RepeatedRow, RepeatedRowOrder, Row, RowMeta, RowMetaChangeset,
};
use flowy_grid_data_model::entities::{Field, GridBlock, RepeatedRowOrder, Row, RowMeta, RowMetaChangeset};
use flowy_sync::disk::SQLiteGridBlockMetaRevisionPersistence;
use flowy_sync::{
RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder, RevisionPersistence,
};
use lib_infra::future::FutureResult;
use lib_ot::core::PlainTextAttributes;
use lib_sqlite::ConnectionPool;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
@ -52,32 +50,34 @@ impl GridBlockMetaEditorManager {
editor.create_row(row).await
}
pub(crate) async fn delete_rows(&self, row_orders: Option<RepeatedRowOrder>) -> FlowyResult<Vec<(String, i32)>> {
pub(crate) async fn delete_rows(&self, _row_orders: Option<RepeatedRowOrder>) -> FlowyResult<Vec<(String, i32)>> {
Ok(vec![("".to_owned(), 2)])
}
pub(crate) async fn get_rows(
&self,
fields: Vec<Field>,
row_orders: Option<RepeatedRowOrder>,
) -> FlowyResult<Vec<Row>> {
match row_orders {
None => {
let rows = vec![];
Ok(rows)
}
Some(row_orders) => {
let row_ids_per_blocks = make_row_ids_per_block(&row_orders);
let mut rows = vec![];
for row_ids_per_block in row_ids_per_blocks {
let editor = self.get_editor(&row_ids_per_block.block_id).await?;
let row_metas = editor.get_rows(row_ids_per_block.row_ids).await?;
rows.extend(make_rows(&fields, row_metas));
}
sort_rows(&mut rows, row_orders);
Ok(rows)
}
pub(crate) async fn get_all_rows(&self, grid_blocks: Vec<GridBlock>, fields: Vec<Field>) -> FlowyResult<Vec<Row>> {
let mut rows = vec![];
for grid_block in grid_blocks {
let editor = self.get_editor(&grid_block.id).await?;
let row_metas = editor.get_rows(None).await?;
rows.extend(make_rows(&fields, row_metas));
}
Ok(rows)
}
pub(crate) async fn get_rows(&self, fields: Vec<Field>, row_orders: RepeatedRowOrder) -> FlowyResult<Vec<Row>> {
let row_ids_per_blocks = make_row_ids_per_block(&row_orders);
let mut row_map: HashMap<String, Row> = HashMap::new();
for row_ids_per_block in row_ids_per_blocks {
let editor = self.get_editor(&row_ids_per_block.block_id).await?;
let row_metas = editor.get_rows(Some(row_ids_per_block.row_ids)).await?;
row_map.extend(make_row_by_row_id(&fields, row_metas));
}
let rows = row_orders
.iter()
.flat_map(|row_order| row_map.remove(&row_order.row_id))
.collect::<Vec<_>>();
Ok(rows)
}
}
@ -158,9 +158,14 @@ impl ClientGridBlockMetaEditor {
Ok(())
}
pub async fn get_rows(&self, row_ids: Vec<String>) -> FlowyResult<Vec<RowMeta>> {
let rows = self.meta_pad.read().await.get_rows(row_ids)?;
Ok(rows)
pub async fn get_rows(&self, row_ids: Option<Vec<String>>) -> FlowyResult<Vec<RowMeta>> {
match row_ids {
None => Ok(self.meta_pad.read().await.all_rows()),
Some(row_ids) => {
let rows = self.meta_pad.read().await.get_rows(row_ids)?;
Ok(rows)
}
}
}
async fn modify<F>(&self, f: F) -> FlowyResult<()>

View File

@ -1,15 +1,15 @@
use crate::services::field::*;
use crate::services::util::*;
use flowy_error::FlowyError;
use flowy_grid_data_model::entities::{AnyData, Field, FieldType};
use flowy_grid_data_model::entities::{Field, FieldType};
pub trait StringifyCellData {
fn str_from_cell_data(&self, data: AnyData) -> String;
fn str_to_cell_data(&self, s: &str) -> Result<AnyData, FlowyError>;
fn str_from_cell_data(&self, data: String) -> String;
fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError>;
}
#[allow(dead_code)]
pub fn stringify_serialize(field: &Field, s: &str) -> Result<AnyData, FlowyError> {
pub fn stringify_serialize(field: &Field, s: &str) -> Result<String, FlowyError> {
match field.field_type {
FieldType::RichText => RichTextDescription::from(field).str_to_cell_data(s),
FieldType::Number => NumberDescription::from(field).str_to_cell_data(s),
@ -20,8 +20,8 @@ pub fn stringify_serialize(field: &Field, s: &str) -> Result<AnyData, FlowyError
}
}
pub(crate) fn stringify_deserialize(data: AnyData, field: &Field) -> Result<String, FlowyError> {
let _ = check_type_id(&data, field)?;
pub(crate) fn stringify_deserialize(data: String, field: &Field) -> Result<String, FlowyError> {
// let _ = check_type_id(&data, field)?;
let s = match field.field_type {
FieldType::RichText => RichTextDescription::from(field).str_from_cell_data(data),
FieldType::Number => NumberDescription::from(field).str_from_cell_data(data),

View File

@ -4,4 +4,4 @@ mod row_loader;
pub use cell_stringify::*;
pub use row_builder::*;
pub use row_loader::*;
pub(crate) use row_loader::*;

View File

@ -1,4 +1,6 @@
use flowy_grid_data_model::entities::{Field, RepeatedRowOrder, Row, RowMeta};
use crate::services::row::stringify_deserialize;
use flowy_grid_data_model::entities::{Cell, CellMeta, Field, RepeatedRowOrder, Row, RowMeta};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use std::collections::HashMap;
pub(crate) struct RowIdsPerBlock {
@ -19,50 +21,67 @@ pub(crate) fn make_row_ids_per_block(row_orders: &RepeatedRowOrder) -> Vec<RowId
map.into_values().collect::<Vec<_>>()
}
pub(crate) fn sort_rows(rows: &mut Vec<Row>, row_orders: RepeatedRowOrder) {
todo!()
pub(crate) fn make_rows(fields: &Vec<Field>, row_metas: Vec<RowMeta>) -> Vec<Row> {
let field_map = fields
.iter()
.map(|field| (&field.id, field))
.collect::<HashMap<&String, &Field>>();
let make_row = |row_meta: RowMeta| {
let cell_by_field_id = row_meta
.cell_by_field_id
.into_par_iter()
.flat_map(|(field_id, raw_cell)| make_cell(&field_map, field_id, raw_cell))
.collect::<HashMap<String, Cell>>();
Row {
id: row_meta.id.clone(),
cell_by_field_id,
height: row_meta.height,
}
};
row_metas.into_iter().map(make_row).collect::<Vec<Row>>()
}
pub(crate) fn make_rows(fields: &Vec<Field>, rows: Vec<RowMeta>) -> Vec<Row> {
// let make_cell = |field_id: String, raw_cell: CellMeta| {
// let some_field = self.field_map.get(&field_id);
// if some_field.is_none() {
// tracing::error!("Can't find the field with {}", field_id);
// return None;
// }
// self.cell_map.insert(raw_cell.id.clone(), raw_cell.clone());
//
// let field = some_field.unwrap();
// match stringify_deserialize(raw_cell.data, field.value()) {
// Ok(content) => {
// let cell = Cell {
// id: raw_cell.id,
// field_id: field_id.clone(),
// content,
// };
// Some((field_id, cell))
// }
// Err(_) => None,
// }
// };
//
// let rows = row_metas
// .into_par_iter()
// .map(|row_meta| {
// let mut row = Row {
// id: row_meta.id.clone(),
// cell_by_field_id: Default::default(),
// height: row_meta.height,
// };
// row.cell_by_field_id = row_meta
// .cell_by_field_id
// .into_par_iter()
// .flat_map(|(field_id, raw_cell)| make_cell(field_id, raw_cell))
// .collect::<HashMap<String, Cell>>();
// row
// })
// .collect::<Vec<Row>>();
//
// Ok(rows.into())
todo!()
#[inline(always)]
fn make_cell(field_map: &HashMap<&String, &Field>, field_id: String, raw_cell: CellMeta) -> Option<(String, Cell)> {
let field = field_map.get(&field_id)?;
match stringify_deserialize(raw_cell.data, field) {
Ok(content) => {
let cell = Cell::new(&field_id, content);
Some((field_id, cell))
}
Err(e) => {
tracing::error!("{}", e);
None
}
}
}
pub(crate) fn make_row_by_row_id(fields: &Vec<Field>, row_metas: Vec<RowMeta>) -> HashMap<String, Row> {
let field_map = fields
.iter()
.map(|field| (&field.id, field))
.collect::<HashMap<&String, &Field>>();
let make_row = |row_meta: RowMeta| {
let cell_by_field_id = row_meta
.cell_by_field_id
.into_par_iter()
.flat_map(|(field_id, raw_cell)| make_cell(&field_map, field_id, raw_cell))
.collect::<HashMap<String, Cell>>();
let row = Row {
id: row_meta.id.clone(),
cell_by_field_id,
height: row_meta.height,
};
(row.id.clone(), row)
};
row_metas
.into_par_iter()
.map(make_row)
.collect::<HashMap<String, Row>>()
}

View File

@ -1,6 +1,6 @@
use crate::services::field::*;
use flowy_collaboration::client_grid::{BuildGridInfo, GridBuilder};
use flowy_grid_data_model::entities::{Field, FieldType};
use flowy_grid_data_model::entities::FieldType;
pub fn make_default_grid(grid_id: &str) -> BuildGridInfo {
let text_field = FieldBuilder::new(RichTextTypeOptionsBuilder::new())

View File

@ -169,6 +169,6 @@ async fn grid_update_block() {
#[tokio::test]
async fn grid_create_row() {
let scripts = vec![AssertRowCount(2), CreateRow, CreateRow, CreateRow, AssertRowCount(5)];
let scripts = vec![AssertRowCount(3), CreateRow, CreateRow, AssertRowCount(5)];
GridEditorTest::new().await.run_scripts(scripts).await;
}

View File

@ -1,8 +1,8 @@
use flowy_grid::services::field::*;
use flowy_grid::services::grid_editor::{ClientGridEditor, GridPadBuilder};
use flowy_grid_data_model::entities::{AnyData, Field, FieldChangeset, FieldType, GridBlock, GridBlockChangeset};
use flowy_grid_data_model::entities::{Field, FieldChangeset, FieldType, GridBlock, GridBlockChangeset};
use flowy_sync::REVISION_WRITE_INTERVAL_IN_MILLIS;
use flowy_test::event_builder::FolderEventBuilder;
use flowy_test::helper::ViewTest;
use flowy_test::FlowySDKTest;
use std::sync::Arc;
@ -89,7 +89,7 @@ impl GridEditorTest {
self.editor.create_row().await.unwrap();
}
EditorScript::AssertRowCount(count) => {
assert_eq!(self.editor.get_rows(None).await.unwrap().len(), count);
assert_eq!(self.editor.get_all_rows().await.unwrap().len(), count);
}
EditorScript::AssertGridMetaPad => {
sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await;

View File

@ -1,5 +1,5 @@
use crate::entities::revision::{md5, RepeatedRevision, Revision};
use crate::errors::{internal_error, CollaborateError, CollaborateResult};
use crate::errors::{CollaborateError, CollaborateResult};
use crate::util::{cal_diff, make_delta_from_revisions};
use flowy_grid_data_model::entities::{GridBlockMeta, RowMeta, RowMetaChangeset};
use lib_infra::uuid;
@ -69,6 +69,10 @@ impl GridBlockMetaPad {
.collect::<Vec<RowMeta>>())
}
pub fn all_rows(&self) -> Vec<RowMeta> {
self.rows.iter().map(|row| (**row).clone()).collect::<Vec<_>>()
}
pub fn number_of_rows(&self) -> i32 {
self.rows.len() as i32
}

View File

@ -1,6 +1,6 @@
use crate::client_grid::{make_block_meta_delta, make_grid_delta, GridBlockMetaDelta, GridMetaDelta};
use crate::errors::{CollaborateError, CollaborateResult};
use flowy_grid_data_model::entities::{Field, FieldType, GridBlock, GridBlockMeta, GridMeta, RowMeta};
use flowy_grid_data_model::entities::{Field, GridBlock, GridBlockMeta, GridMeta, RowMeta};
pub struct GridBuilder {
grid_id: String,

View File

@ -2,7 +2,7 @@ use crate::entities::revision::{md5, RepeatedRevision, Revision};
use crate::errors::{internal_error, CollaborateError, CollaborateResult};
use crate::util::{cal_diff, make_delta_from_revisions};
use flowy_grid_data_model::entities::{
Field, FieldChangeset, GridBlock, GridBlockChangeset, GridMeta, RepeatedField, RepeatedFieldOrder,
Field, FieldChangeset, GridBlock, GridBlockChangeset, GridMeta, RepeatedFieldOrder,
};
use lib_infra::uuid;
use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
@ -58,7 +58,7 @@ impl GridMetaPad {
pub fn get_fields(&self, field_orders: Option<RepeatedFieldOrder>) -> CollaborateResult<Vec<Field>> {
match field_orders {
None => Ok(self.grid_meta.fields.clone().into()),
None => Ok(self.grid_meta.fields.clone()),
Some(field_orders) => {
let field_by_field_id = self
.grid_meta
@ -127,7 +127,7 @@ impl GridMetaPad {
pub fn create_block(&mut self, block: GridBlock) -> CollaborateResult<Option<GridChange>> {
self.modify_grid(|grid| {
if grid.blocks.iter().find(|b| b.id == block.id).is_some() {
if grid.blocks.iter().any(|b| b.id == block.id) {
tracing::warn!("Duplicate grid block");
Ok(None)
} else {

View File

@ -123,6 +123,15 @@ pub struct Cell {
pub content: String,
}
impl Cell {
pub fn new(field_id: &str, content: String) -> Self {
Self {
field_id: field_id.to_owned(),
content,
}
}
}
#[derive(ProtoBuf, Default)]
pub struct CreateGridPayload {
#[pb(index = 1)]

View File

@ -175,13 +175,13 @@ impl std::default::Default for FieldType {
impl AsRef<FieldType> for FieldType {
fn as_ref(&self) -> &FieldType {
&self
self
}
}
impl Into<FieldType> for &FieldType {
fn into(self) -> FieldType {
self.clone()
impl From<&FieldType> for FieldType {
fn from(field: &FieldType) -> Self {
field.clone()
}
}