Kaynağa Gözat

chore: config BlockMetaPad

appflowy 3 yıl önce
ebeveyn
işleme
cea7d30a53

+ 109 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pb.dart

@@ -552,6 +552,115 @@ class RowMeta extends $pb.GeneratedMessage {
   void clearVisibility() => clearField(5);
 }
 
+enum RowMetaChangeset_OneOfHeight {
+  height, 
+  notSet
+}
+
+enum RowMetaChangeset_OneOfVisibility {
+  visibility, 
+  notSet
+}
+
+class RowMetaChangeset extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, RowMetaChangeset_OneOfHeight> _RowMetaChangeset_OneOfHeightByTag = {
+    2 : RowMetaChangeset_OneOfHeight.height,
+    0 : RowMetaChangeset_OneOfHeight.notSet
+  };
+  static const $core.Map<$core.int, RowMetaChangeset_OneOfVisibility> _RowMetaChangeset_OneOfVisibilityByTag = {
+    3 : RowMetaChangeset_OneOfVisibility.visibility,
+    0 : RowMetaChangeset_OneOfVisibility.notSet
+  };
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RowMetaChangeset', createEmptyInstance: create)
+    ..oo(0, [2])
+    ..oo(1, [3])
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowId')
+    ..a<$core.int>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'height', $pb.PbFieldType.O3)
+    ..aOB(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'visibility')
+    ..m<$core.String, CellMeta>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'cellByFieldId', entryClassName: 'RowMetaChangeset.CellByFieldIdEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.OM, valueCreator: CellMeta.create)
+    ..hasRequiredFields = false
+  ;
+
+  RowMetaChangeset._() : super();
+  factory RowMetaChangeset({
+    $core.String? rowId,
+    $core.int? height,
+    $core.bool? visibility,
+    $core.Map<$core.String, CellMeta>? cellByFieldId,
+  }) {
+    final _result = create();
+    if (rowId != null) {
+      _result.rowId = rowId;
+    }
+    if (height != null) {
+      _result.height = height;
+    }
+    if (visibility != null) {
+      _result.visibility = visibility;
+    }
+    if (cellByFieldId != null) {
+      _result.cellByFieldId.addAll(cellByFieldId);
+    }
+    return _result;
+  }
+  factory RowMetaChangeset.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RowMetaChangeset.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  RowMetaChangeset clone() => RowMetaChangeset()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  RowMetaChangeset copyWith(void Function(RowMetaChangeset) updates) => super.copyWith((message) => updates(message as RowMetaChangeset)) as RowMetaChangeset; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static RowMetaChangeset create() => RowMetaChangeset._();
+  RowMetaChangeset createEmptyInstance() => create();
+  static $pb.PbList<RowMetaChangeset> createRepeated() => $pb.PbList<RowMetaChangeset>();
+  @$core.pragma('dart2js:noInline')
+  static RowMetaChangeset getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RowMetaChangeset>(create);
+  static RowMetaChangeset? _defaultInstance;
+
+  RowMetaChangeset_OneOfHeight whichOneOfHeight() => _RowMetaChangeset_OneOfHeightByTag[$_whichOneof(0)]!;
+  void clearOneOfHeight() => clearField($_whichOneof(0));
+
+  RowMetaChangeset_OneOfVisibility whichOneOfVisibility() => _RowMetaChangeset_OneOfVisibilityByTag[$_whichOneof(1)]!;
+  void clearOneOfVisibility() => clearField($_whichOneof(1));
+
+  @$pb.TagNumber(1)
+  $core.String get rowId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set rowId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasRowId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearRowId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.int get height => $_getIZ(1);
+  @$pb.TagNumber(2)
+  set height($core.int v) { $_setSignedInt32(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasHeight() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearHeight() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.bool get visibility => $_getBF(2);
+  @$pb.TagNumber(3)
+  set visibility($core.bool v) { $_setBool(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasVisibility() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearVisibility() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $core.Map<$core.String, CellMeta> get cellByFieldId => $_getMap(3);
+}
+
 class CellMeta extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CellMeta', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')

+ 28 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pbjson.dart

@@ -121,6 +121,34 @@ const RowMeta_CellByFieldIdEntry$json = const {
 
 /// Descriptor for `RowMeta`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List rowMetaDescriptor = $convert.base64Decode('CgdSb3dNZXRhEg4KAmlkGAEgASgJUgJpZBIXCgdncmlkX2lkGAIgASgJUgZncmlkSWQSRAoQY2VsbF9ieV9maWVsZF9pZBgDIAMoCzIbLlJvd01ldGEuQ2VsbEJ5RmllbGRJZEVudHJ5Ug1jZWxsQnlGaWVsZElkEhYKBmhlaWdodBgEIAEoBVIGaGVpZ2h0Eh4KCnZpc2liaWxpdHkYBSABKAhSCnZpc2liaWxpdHkaSwoSQ2VsbEJ5RmllbGRJZEVudHJ5EhAKA2tleRgBIAEoCVIDa2V5Eh8KBXZhbHVlGAIgASgLMgkuQ2VsbE1ldGFSBXZhbHVlOgI4AQ==');
+@$core.Deprecated('Use rowMetaChangesetDescriptor instead')
+const RowMetaChangeset$json = const {
+  '1': 'RowMetaChangeset',
+  '2': const [
+    const {'1': 'row_id', '3': 1, '4': 1, '5': 9, '10': 'rowId'},
+    const {'1': 'height', '3': 2, '4': 1, '5': 5, '9': 0, '10': 'height'},
+    const {'1': 'visibility', '3': 3, '4': 1, '5': 8, '9': 1, '10': 'visibility'},
+    const {'1': 'cell_by_field_id', '3': 4, '4': 3, '5': 11, '6': '.RowMetaChangeset.CellByFieldIdEntry', '10': 'cellByFieldId'},
+  ],
+  '3': const [RowMetaChangeset_CellByFieldIdEntry$json],
+  '8': const [
+    const {'1': 'one_of_height'},
+    const {'1': 'one_of_visibility'},
+  ],
+};
+
+@$core.Deprecated('Use rowMetaChangesetDescriptor instead')
+const RowMetaChangeset_CellByFieldIdEntry$json = const {
+  '1': 'CellByFieldIdEntry',
+  '2': const [
+    const {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'},
+    const {'1': 'value', '3': 2, '4': 1, '5': 11, '6': '.CellMeta', '10': 'value'},
+  ],
+  '7': const {'7': true},
+};
+
+/// Descriptor for `RowMetaChangeset`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List rowMetaChangesetDescriptor = $convert.base64Decode('ChBSb3dNZXRhQ2hhbmdlc2V0EhUKBnJvd19pZBgBIAEoCVIFcm93SWQSGAoGaGVpZ2h0GAIgASgFSABSBmhlaWdodBIgCgp2aXNpYmlsaXR5GAMgASgISAFSCnZpc2liaWxpdHkSTQoQY2VsbF9ieV9maWVsZF9pZBgEIAMoCzIkLlJvd01ldGFDaGFuZ2VzZXQuQ2VsbEJ5RmllbGRJZEVudHJ5Ug1jZWxsQnlGaWVsZElkGksKEkNlbGxCeUZpZWxkSWRFbnRyeRIQCgNrZXkYASABKAlSA2tleRIfCgV2YWx1ZRgCIAEoCzIJLkNlbGxNZXRhUgV2YWx1ZToCOAFCDwoNb25lX29mX2hlaWdodEITChFvbmVfb2ZfdmlzaWJpbGl0eQ==');
 @$core.Deprecated('Use cellMetaDescriptor instead')
 const CellMeta$json = const {
   '1': 'CellMeta',

+ 2 - 2
frontend/rust-lib/dart-ffi/Cargo.toml

@@ -7,8 +7,8 @@ edition = "2018"
 [lib]
 name = "dart_ffi"
 # this value will change depending on the target os
-# default staticlib
-crate-type = ["staticlib"]
+# default cdylib
+crate-type = ["cdylib"]
 
 
 [dependencies]

+ 5 - 5
frontend/rust-lib/flowy-block/tests/editor/attribute_test.rs

@@ -762,12 +762,12 @@ fn attributes_preserve_list_format_on_merge() {
 
 #[test]
 fn delta_compose() {
-    let mut delta = RichTextDelta::from_json(r#"[{"insert":"\n"}]"#).unwrap();
+    let mut delta = RichTextDelta::from_delta_str(r#"[{"insert":"\n"}]"#).unwrap();
     let deltas = vec![
-        RichTextDelta::from_json(r#"[{"retain":1,"attributes":{"list":"unchecked"}}]"#).unwrap(),
-        RichTextDelta::from_json(r#"[{"insert":"a"}]"#).unwrap(),
-        RichTextDelta::from_json(r#"[{"retain":1},{"insert":"\n","attributes":{"list":"unchecked"}}]"#).unwrap(),
-        RichTextDelta::from_json(r#"[{"retain":2},{"retain":1,"attributes":{"list":""}}]"#).unwrap(),
+        RichTextDelta::from_delta_str(r#"[{"retain":1,"attributes":{"list":"unchecked"}}]"#).unwrap(),
+        RichTextDelta::from_delta_str(r#"[{"insert":"a"}]"#).unwrap(),
+        RichTextDelta::from_delta_str(r#"[{"retain":1},{"insert":"\n","attributes":{"list":"unchecked"}}]"#).unwrap(),
+        RichTextDelta::from_delta_str(r#"[{"retain":2},{"retain":1,"attributes":{"list":""}}]"#).unwrap(),
     ];
 
     for d in deltas {

+ 3 - 3
frontend/rust-lib/flowy-block/tests/editor/serde_test.rs

@@ -65,7 +65,7 @@ fn delta_serialize_multi_attribute_test() {
     let json = serde_json::to_string(&delta).unwrap();
     eprintln!("{}", json);
 
-    let delta_from_json = Delta::from_json(&json).unwrap();
+    let delta_from_json = Delta::from_delta_str(&json).unwrap();
     assert_eq!(delta_from_json, delta);
 }
 
@@ -77,7 +77,7 @@ fn delta_deserialize_test() {
         {"retain":2,"attributes":{"italic":"true","bold":"true"}},
         {"retain":2,"attributes":{"italic":true,"bold":true}}
      ]"#;
-    let delta = RichTextDelta::from_json(json).unwrap();
+    let delta = RichTextDelta::from_delta_str(json).unwrap();
     eprintln!("{}", delta);
 }
 
@@ -86,7 +86,7 @@ fn delta_deserialize_null_test() {
     let json = r#"[
         {"retain":7,"attributes":{"bold":null}}
      ]"#;
-    let delta1 = RichTextDelta::from_json(json).unwrap();
+    let delta1 = RichTextDelta::from_delta_str(json).unwrap();
 
     let mut attribute = RichTextAttribute::Bold(true);
     attribute.value = RichTextAttributeValue(None);

+ 1 - 1
shared-lib/flowy-collaboration/src/client_document/default/mod.rs

@@ -13,7 +13,7 @@ pub fn initial_quill_delta_string() -> String {
 #[inline]
 pub fn initial_read_me() -> RichTextDelta {
     let json = include_str!("READ_ME.json");
-    RichTextDelta::from_json(json).unwrap()
+    RichTextDelta::from_delta_str(json).unwrap()
 }
 
 #[cfg(test)]

+ 1 - 1
shared-lib/flowy-collaboration/src/client_document/document_pad.rs

@@ -54,7 +54,7 @@ impl ClientDocument {
     }
 
     pub fn from_json(json: &str) -> Result<Self, CollaborateError> {
-        let delta = RichTextDelta::from_json(json)?;
+        let delta = RichTextDelta::from_delta_str(json)?;
         Ok(Self::from_delta(delta))
     }
 

+ 3 - 3
shared-lib/flowy-collaboration/src/client_folder/folder_pad.rs

@@ -307,7 +307,7 @@ impl FolderPad {
             if let Some(workspace) = workspaces.iter_mut().find(|workspace| workspace_id == workspace.id) {
                 f(Arc::make_mut(workspace))
             } else {
-                tracing::warn!("[RootFolder]: Can't find any workspace with id: {}", workspace_id);
+                tracing::warn!("[FolderPad]: Can't find any workspace with id: {}", workspace_id);
                 Ok(None)
             }
         })
@@ -344,7 +344,7 @@ impl FolderPad {
             .find(|workspace| workspace.apps.iter().any(|app| app.id == app_id))
         {
             None => {
-                tracing::warn!("[RootFolder]: Can't find any app with id: {}", app_id);
+                tracing::warn!("[FolderPad]: Can't find any app with id: {}", app_id);
                 return Ok(None);
             }
             Some(workspace) => workspace.id.clone(),
@@ -363,7 +363,7 @@ impl FolderPad {
         self.with_app(belong_to_id, |app| {
             match app.belongings.iter_mut().find(|view| view_id == view.id) {
                 None => {
-                    tracing::warn!("[RootFolder]: Can't find any view with id: {}", view_id);
+                    tracing::warn!("[FolderPad]: Can't find any view with id: {}", view_id);
                     Ok(None)
                 }
                 Some(view) => f(view),

+ 157 - 24
shared-lib/flowy-collaboration/src/client_grid/block_pad.rs

@@ -1,30 +1,33 @@
 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::{BlockMeta, RowMeta, RowOrder};
+use flowy_grid_data_model::entities::{BlockMeta, RowMeta, RowMetaChangeset, RowOrder};
 use lib_infra::uuid;
 use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
+use serde::{Deserialize, Serialize};
 use std::sync::Arc;
 
 pub type BlockMetaDelta = PlainTextDelta;
 pub type BlockDeltaBuilder = PlainTextDeltaBuilder;
 
+#[derive(Debug, Deserialize, Serialize, Clone)]
 pub struct BlockMetaPad {
-    pub(crate) block_meta: Arc<BlockMeta>,
+    block_id: String,
+    rows: Vec<Arc<RowMeta>>,
+
+    #[serde(skip)]
     pub(crate) delta: BlockMetaDelta,
 }
 
 impl BlockMetaPad {
     pub fn from_delta(delta: BlockMetaDelta) -> CollaborateResult<Self> {
         let s = delta.to_str()?;
-        let block_delta: BlockMeta = serde_json::from_str(&s).map_err(|e| {
+        let block_meta: BlockMeta = serde_json::from_str(&s).map_err(|e| {
             CollaborateError::internal().context(format!("Deserialize delta to block meta failed: {}", e))
         })?;
-
-        Ok(Self {
-            block_meta: Arc::new(block_delta),
-            delta,
-        })
+        let block_id = block_meta.block_id;
+        let rows = block_meta.rows.into_iter().map(Arc::new).collect::<Vec<Arc<RowMeta>>>();
+        Ok(Self { block_id, rows, delta })
     }
 
     pub fn from_revisions(_grid_id: &str, revisions: Vec<Revision>) -> CollaborateResult<Self> {
@@ -32,38 +35,55 @@ impl BlockMetaPad {
         Self::from_delta(block_delta)
     }
 
-    pub fn create_row(&mut self, row: RowMeta) -> CollaborateResult<Option<BlockMetaChange>> {
-        self.modify(|grid| {
-            grid.rows.push(row);
+    pub fn add_row(&mut self, row: RowMeta) -> CollaborateResult<Option<BlockMetaChange>> {
+        self.modify(|rows| {
+            rows.push(Arc::new(row));
             Ok(Some(()))
         })
     }
 
     pub fn delete_rows(&mut self, row_ids: &[String]) -> CollaborateResult<Option<BlockMetaChange>> {
-        self.modify(|grid| {
-            grid.rows.retain(|row| !row_ids.contains(&row.id));
+        self.modify(|rows| {
+            rows.retain(|row| !row_ids.contains(&row.id));
             Ok(Some(()))
         })
     }
 
-    pub fn md5(&self) -> String {
-        md5(&self.delta.to_bytes())
-    }
+    pub fn update_row(&mut self, changeset: RowMetaChangeset) -> CollaborateResult<Option<BlockMetaChange>> {
+        let row_id = changeset.row_id.clone();
+        self.modify_row(&row_id, |row| {
+            let mut is_changed = None;
+            if let Some(height) = changeset.height {
+                row.height = height;
+                is_changed = Some(());
+            }
 
-    pub fn delta_str(&self) -> String {
-        self.delta.to_delta_str()
+            if let Some(visibility) = changeset.visibility {
+                row.visibility = visibility;
+                is_changed = Some(());
+            }
+
+            if !changeset.cell_by_field_id.is_empty() {
+                is_changed = Some(());
+                changeset.cell_by_field_id.into_iter().for_each(|(field_id, cell)| {
+                    row.cell_by_field_id.insert(field_id, cell);
+                })
+            }
+
+            Ok(is_changed)
+        })
     }
 
     pub fn modify<F>(&mut self, f: F) -> CollaborateResult<Option<BlockMetaChange>>
     where
-        F: FnOnce(&mut BlockMeta) -> CollaborateResult<Option<()>>,
+        F: for<'a> FnOnce(&'a mut Vec<Arc<RowMeta>>) -> CollaborateResult<Option<()>>,
     {
-        let cloned_meta = self.block_meta.clone();
-        match f(Arc::make_mut(&mut self.block_meta))? {
+        let cloned_self = self.clone();
+        match f(&mut self.rows)? {
             None => Ok(None),
             Some(_) => {
-                let old = json_from_grid(&cloned_meta)?;
-                let new = json_from_grid(&self.block_meta)?;
+                let old = cloned_self.to_json()?;
+                let new = self.to_json()?;
                 match cal_diff::<PlainTextAttributes>(old, new) {
                     None => Ok(None),
                     Some(delta) => {
@@ -74,6 +94,33 @@ impl BlockMetaPad {
             }
         }
     }
+
+    fn modify_row<F>(&mut self, row_id: &str, f: F) -> CollaborateResult<Option<BlockMetaChange>>
+    where
+        F: FnOnce(&mut RowMeta) -> CollaborateResult<Option<()>>,
+    {
+        self.modify(|rows| {
+            if let Some(row_meta) = rows.iter_mut().find(|row_meta| row_id == row_meta.id) {
+                f(Arc::make_mut(row_meta))
+            } else {
+                tracing::warn!("[BlockMetaPad]: Can't find any row with id: {}", row_id);
+                Ok(None)
+            }
+        })
+    }
+
+    pub fn to_json(&self) -> CollaborateResult<String> {
+        serde_json::to_string(self)
+            .map_err(|e| CollaborateError::internal().context(format!("serial trash to json failed: {}", e)))
+    }
+
+    pub fn md5(&self) -> String {
+        md5(&self.delta.to_bytes())
+    }
+
+    pub fn delta_str(&self) -> String {
+        self.delta.to_delta_str()
+    }
 }
 
 fn json_from_grid(block_meta: &Arc<BlockMeta>) -> CollaborateResult<String> {
@@ -106,10 +153,96 @@ impl std::default::Default for BlockMetaPad {
             block_id: uuid(),
             rows: vec![],
         };
+
         let delta = make_block_meta_delta(&block_meta);
         BlockMetaPad {
-            block_meta: Arc::new(block_meta),
+            block_id: block_meta.block_id,
+            rows: block_meta.rows.into_iter().map(Arc::new).collect::<Vec<_>>(),
             delta,
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::client_grid::{BlockMetaDelta, BlockMetaPad};
+    use flowy_grid_data_model::entities::{RowMeta, RowMetaChangeset};
+    use std::str::FromStr;
+
+    #[test]
+    fn block_meta_add_row() {
+        let mut pad = test_pad();
+        let row = RowMeta {
+            id: "1".to_string(),
+            block_id: pad.block_id.clone(),
+            cell_by_field_id: Default::default(),
+            height: 0,
+            visibility: false,
+        };
+
+        let change = pad.add_row(row).unwrap().unwrap();
+        assert_eq!(
+            change.delta.to_delta_str(),
+            r#"[{"retain":24},{"insert":"{\"id\":\"1\",\"block_id\":\"1\",\"cell_by_field_id\":{},\"height\":0,\"visibility\":false}"},{"retain":2}]"#
+        );
+    }
+
+    #[test]
+    fn block_meta_delete_row() {
+        let mut pad = test_pad();
+        let pre_delta_str = pad.delta_str();
+        let row = RowMeta {
+            id: "1".to_string(),
+            block_id: pad.block_id.clone(),
+            cell_by_field_id: Default::default(),
+            height: 0,
+            visibility: false,
+        };
+
+        let _ = pad.add_row(row.clone()).unwrap().unwrap();
+        let change = pad.delete_rows(&[row.id]).unwrap().unwrap();
+        assert_eq!(
+            change.delta.to_delta_str(),
+            r#"[{"retain":24},{"delete":77},{"retain":2}]"#
+        );
+
+        assert_eq!(pad.delta_str(), pre_delta_str);
+    }
+
+    #[test]
+    fn block_meta_update_row() {
+        let mut pad = test_pad();
+        let row = RowMeta {
+            id: "1".to_string(),
+            block_id: pad.block_id.clone(),
+            cell_by_field_id: Default::default(),
+            height: 0,
+            visibility: false,
+        };
+
+        let changeset = RowMetaChangeset {
+            row_id: row.id.clone(),
+            height: Some(100),
+            visibility: Some(true),
+            cell_by_field_id: Default::default(),
+        };
+
+        let _ = pad.add_row(row.clone()).unwrap().unwrap();
+        let change = pad.update_row(changeset).unwrap().unwrap();
+
+        assert_eq!(
+            change.delta.to_delta_str(),
+            r#"[{"retain":80},{"insert":"10"},{"retain":15},{"insert":"tru"},{"delete":4},{"retain":4}]"#
+        );
+
+        assert_eq!(
+            pad.to_json().unwrap(),
+            r#"{"block_id":"1","rows":[{"id":"1","block_id":"1","cell_by_field_id":{},"height":100,"visibility":true}]}"#
+        );
+    }
+
+    fn test_pad() -> BlockMetaPad {
+        let delta = BlockMetaDelta::from_delta_str(r#"[{"insert":"{\"block_id\":\"1\",\"rows\":[]}"}]"#).unwrap();
+        BlockMetaPad::from_delta(delta).unwrap()
+    }
+}

+ 17 - 2
shared-lib/flowy-grid-data-model/src/entities/meta.rs

@@ -178,7 +178,7 @@ pub struct RowMeta {
     pub id: String,
 
     #[pb(index = 2)]
-    pub grid_id: String,
+    pub block_id: String,
 
     #[pb(index = 3)]
     pub cell_by_field_id: HashMap<String, CellMeta>,
@@ -199,7 +199,7 @@ impl RowMeta {
 
         Self {
             id: id.to_owned(),
-            grid_id: grid_id.to_owned(),
+            block_id: grid_id.to_owned(),
             cell_by_field_id,
             height: DEFAULT_ROW_HEIGHT,
             visibility: true,
@@ -207,6 +207,21 @@ impl RowMeta {
     }
 }
 
+#[derive(Debug, Clone, Default, ProtoBuf)]
+pub struct RowMetaChangeset {
+    #[pb(index = 1)]
+    pub row_id: String,
+
+    #[pb(index = 2, one_of)]
+    pub height: Option<i32>,
+
+    #[pb(index = 3, one_of)]
+    pub visibility: Option<bool>,
+
+    #[pb(index = 4)]
+    pub cell_by_field_id: HashMap<String, CellMeta>,
+}
+
 #[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
 pub struct CellMeta {
     #[pb(index = 1)]

+ 349 - 31
shared-lib/flowy-grid-data-model/src/protobuf/model/meta.rs

@@ -1524,7 +1524,7 @@ impl ::protobuf::reflect::ProtobufValue for AnyData {
 pub struct RowMeta {
     // message fields
     pub id: ::std::string::String,
-    pub grid_id: ::std::string::String,
+    pub block_id: ::std::string::String,
     pub cell_by_field_id: ::std::collections::HashMap<::std::string::String, CellMeta>,
     pub height: i32,
     pub visibility: bool,
@@ -1570,30 +1570,30 @@ impl RowMeta {
         ::std::mem::replace(&mut self.id, ::std::string::String::new())
     }
 
-    // string grid_id = 2;
+    // string block_id = 2;
 
 
-    pub fn get_grid_id(&self) -> &str {
-        &self.grid_id
+    pub fn get_block_id(&self) -> &str {
+        &self.block_id
     }
-    pub fn clear_grid_id(&mut self) {
-        self.grid_id.clear();
+    pub fn clear_block_id(&mut self) {
+        self.block_id.clear();
     }
 
     // Param is passed by value, moved
-    pub fn set_grid_id(&mut self, v: ::std::string::String) {
-        self.grid_id = v;
+    pub fn set_block_id(&mut self, v: ::std::string::String) {
+        self.block_id = v;
     }
 
     // Mutable pointer to the field.
     // If field is not initialized, it is initialized with default value first.
-    pub fn mut_grid_id(&mut self) -> &mut ::std::string::String {
-        &mut self.grid_id
+    pub fn mut_block_id(&mut self) -> &mut ::std::string::String {
+        &mut self.block_id
     }
 
     // Take field
-    pub fn take_grid_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.grid_id, ::std::string::String::new())
+    pub fn take_block_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.block_id, ::std::string::String::new())
     }
 
     // repeated .RowMeta.CellByFieldIdEntry cell_by_field_id = 3;
@@ -1665,7 +1665,7 @@ impl ::protobuf::Message for RowMeta {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
                 },
                 2 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.block_id)?;
                 },
                 3 => {
                     ::protobuf::rt::read_map_into::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeMessage<CellMeta>>(wire_type, is, &mut self.cell_by_field_id)?;
@@ -1699,8 +1699,8 @@ impl ::protobuf::Message for RowMeta {
         if !self.id.is_empty() {
             my_size += ::protobuf::rt::string_size(1, &self.id);
         }
-        if !self.grid_id.is_empty() {
-            my_size += ::protobuf::rt::string_size(2, &self.grid_id);
+        if !self.block_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.block_id);
         }
         my_size += ::protobuf::rt::compute_map_size::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeMessage<CellMeta>>(3, &self.cell_by_field_id);
         if self.height != 0 {
@@ -1718,8 +1718,8 @@ impl ::protobuf::Message for RowMeta {
         if !self.id.is_empty() {
             os.write_string(1, &self.id)?;
         }
-        if !self.grid_id.is_empty() {
-            os.write_string(2, &self.grid_id)?;
+        if !self.block_id.is_empty() {
+            os.write_string(2, &self.block_id)?;
         }
         ::protobuf::rt::write_map_with_cached_sizes::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeMessage<CellMeta>>(3, &self.cell_by_field_id, os)?;
         if self.height != 0 {
@@ -1772,9 +1772,9 @@ impl ::protobuf::Message for RowMeta {
                 |m: &mut RowMeta| { &mut m.id },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "grid_id",
-                |m: &RowMeta| { &m.grid_id },
-                |m: &mut RowMeta| { &mut m.grid_id },
+                "block_id",
+                |m: &RowMeta| { &m.block_id },
+                |m: &mut RowMeta| { &mut m.block_id },
             ));
             fields.push(::protobuf::reflect::accessor::make_map_accessor::<_, ::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeMessage<CellMeta>>(
                 "cell_by_field_id",
@@ -1808,7 +1808,7 @@ impl ::protobuf::Message for RowMeta {
 impl ::protobuf::Clear for RowMeta {
     fn clear(&mut self) {
         self.id.clear();
-        self.grid_id.clear();
+        self.block_id.clear();
         self.cell_by_field_id.clear();
         self.height = 0;
         self.visibility = false;
@@ -1828,6 +1828,317 @@ impl ::protobuf::reflect::ProtobufValue for RowMeta {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct RowMetaChangeset {
+    // message fields
+    pub row_id: ::std::string::String,
+    pub cell_by_field_id: ::std::collections::HashMap<::std::string::String, CellMeta>,
+    // message oneof groups
+    pub one_of_height: ::std::option::Option<RowMetaChangeset_oneof_one_of_height>,
+    pub one_of_visibility: ::std::option::Option<RowMetaChangeset_oneof_one_of_visibility>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a RowMetaChangeset {
+    fn default() -> &'a RowMetaChangeset {
+        <RowMetaChangeset as ::protobuf::Message>::default_instance()
+    }
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum RowMetaChangeset_oneof_one_of_height {
+    height(i32),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum RowMetaChangeset_oneof_one_of_visibility {
+    visibility(bool),
+}
+
+impl RowMetaChangeset {
+    pub fn new() -> RowMetaChangeset {
+        ::std::default::Default::default()
+    }
+
+    // string row_id = 1;
+
+
+    pub fn get_row_id(&self) -> &str {
+        &self.row_id
+    }
+    pub fn clear_row_id(&mut self) {
+        self.row_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_row_id(&mut self, v: ::std::string::String) {
+        self.row_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_row_id(&mut self) -> &mut ::std::string::String {
+        &mut self.row_id
+    }
+
+    // Take field
+    pub fn take_row_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.row_id, ::std::string::String::new())
+    }
+
+    // int32 height = 2;
+
+
+    pub fn get_height(&self) -> i32 {
+        match self.one_of_height {
+            ::std::option::Option::Some(RowMetaChangeset_oneof_one_of_height::height(v)) => v,
+            _ => 0,
+        }
+    }
+    pub fn clear_height(&mut self) {
+        self.one_of_height = ::std::option::Option::None;
+    }
+
+    pub fn has_height(&self) -> bool {
+        match self.one_of_height {
+            ::std::option::Option::Some(RowMetaChangeset_oneof_one_of_height::height(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_height(&mut self, v: i32) {
+        self.one_of_height = ::std::option::Option::Some(RowMetaChangeset_oneof_one_of_height::height(v))
+    }
+
+    // bool visibility = 3;
+
+
+    pub fn get_visibility(&self) -> bool {
+        match self.one_of_visibility {
+            ::std::option::Option::Some(RowMetaChangeset_oneof_one_of_visibility::visibility(v)) => v,
+            _ => false,
+        }
+    }
+    pub fn clear_visibility(&mut self) {
+        self.one_of_visibility = ::std::option::Option::None;
+    }
+
+    pub fn has_visibility(&self) -> bool {
+        match self.one_of_visibility {
+            ::std::option::Option::Some(RowMetaChangeset_oneof_one_of_visibility::visibility(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_visibility(&mut self, v: bool) {
+        self.one_of_visibility = ::std::option::Option::Some(RowMetaChangeset_oneof_one_of_visibility::visibility(v))
+    }
+
+    // repeated .RowMetaChangeset.CellByFieldIdEntry cell_by_field_id = 4;
+
+
+    pub fn get_cell_by_field_id(&self) -> &::std::collections::HashMap<::std::string::String, CellMeta> {
+        &self.cell_by_field_id
+    }
+    pub fn clear_cell_by_field_id(&mut self) {
+        self.cell_by_field_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_cell_by_field_id(&mut self, v: ::std::collections::HashMap<::std::string::String, CellMeta>) {
+        self.cell_by_field_id = v;
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_cell_by_field_id(&mut self) -> &mut ::std::collections::HashMap<::std::string::String, CellMeta> {
+        &mut self.cell_by_field_id
+    }
+
+    // Take field
+    pub fn take_cell_by_field_id(&mut self) -> ::std::collections::HashMap<::std::string::String, CellMeta> {
+        ::std::mem::replace(&mut self.cell_by_field_id, ::std::collections::HashMap::new())
+    }
+}
+
+impl ::protobuf::Message for RowMetaChangeset {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.row_id)?;
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_height = ::std::option::Option::Some(RowMetaChangeset_oneof_one_of_height::height(is.read_int32()?));
+                },
+                3 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_visibility = ::std::option::Option::Some(RowMetaChangeset_oneof_one_of_visibility::visibility(is.read_bool()?));
+                },
+                4 => {
+                    ::protobuf::rt::read_map_into::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeMessage<CellMeta>>(wire_type, is, &mut self.cell_by_field_id)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.row_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.row_id);
+        }
+        my_size += ::protobuf::rt::compute_map_size::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeMessage<CellMeta>>(4, &self.cell_by_field_id);
+        if let ::std::option::Option::Some(ref v) = self.one_of_height {
+            match v {
+                &RowMetaChangeset_oneof_one_of_height::height(v) => {
+                    my_size += ::protobuf::rt::value_size(2, v, ::protobuf::wire_format::WireTypeVarint);
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_visibility {
+            match v {
+                &RowMetaChangeset_oneof_one_of_visibility::visibility(v) => {
+                    my_size += 2;
+                },
+            };
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.row_id.is_empty() {
+            os.write_string(1, &self.row_id)?;
+        }
+        ::protobuf::rt::write_map_with_cached_sizes::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeMessage<CellMeta>>(4, &self.cell_by_field_id, os)?;
+        if let ::std::option::Option::Some(ref v) = self.one_of_height {
+            match v {
+                &RowMetaChangeset_oneof_one_of_height::height(v) => {
+                    os.write_int32(2, v)?;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_visibility {
+            match v {
+                &RowMetaChangeset_oneof_one_of_visibility::visibility(v) => {
+                    os.write_bool(3, v)?;
+                },
+            };
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> RowMetaChangeset {
+        RowMetaChangeset::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "row_id",
+                |m: &RowMetaChangeset| { &m.row_id },
+                |m: &mut RowMetaChangeset| { &mut m.row_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_i32_accessor::<_>(
+                "height",
+                RowMetaChangeset::has_height,
+                RowMetaChangeset::get_height,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_bool_accessor::<_>(
+                "visibility",
+                RowMetaChangeset::has_visibility,
+                RowMetaChangeset::get_visibility,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_map_accessor::<_, ::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeMessage<CellMeta>>(
+                "cell_by_field_id",
+                |m: &RowMetaChangeset| { &m.cell_by_field_id },
+                |m: &mut RowMetaChangeset| { &mut m.cell_by_field_id },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<RowMetaChangeset>(
+                "RowMetaChangeset",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static RowMetaChangeset {
+        static instance: ::protobuf::rt::LazyV2<RowMetaChangeset> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(RowMetaChangeset::new)
+    }
+}
+
+impl ::protobuf::Clear for RowMetaChangeset {
+    fn clear(&mut self) {
+        self.row_id.clear();
+        self.one_of_height = ::std::option::Option::None;
+        self.one_of_visibility = ::std::option::Option::None;
+        self.cell_by_field_id.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for RowMetaChangeset {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for RowMetaChangeset {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(PartialEq,Clone,Default)]
 pub struct CellMeta {
     // message fields
@@ -2242,20 +2553,27 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x01(\x0b2\x08.AnyDataR\x0btypeOptions\"-\n\rRepeatedField\x12\x1c\n\x05\
     items\x18\x01\x20\x03(\x0b2\x06.FieldR\x05items\"8\n\x07AnyData\x12\x17\
     \n\x07type_id\x18\x01\x20\x01(\tR\x06typeId\x12\x14\n\x05value\x18\x02\
-    \x20\x01(\x0cR\x05value\"\xfd\x01\n\x07RowMeta\x12\x0e\n\x02id\x18\x01\
-    \x20\x01(\tR\x02id\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\x06gridId\
+    \x20\x01(\x0cR\x05value\"\xff\x01\n\x07RowMeta\x12\x0e\n\x02id\x18\x01\
+    \x20\x01(\tR\x02id\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07blockId\
     \x12D\n\x10cell_by_field_id\x18\x03\x20\x03(\x0b2\x1b.RowMeta.CellByFiel\
     dIdEntryR\rcellByFieldId\x12\x16\n\x06height\x18\x04\x20\x01(\x05R\x06he\
     ight\x12\x1e\n\nvisibility\x18\x05\x20\x01(\x08R\nvisibility\x1aK\n\x12C\
     ellByFieldIdEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x1f\n\
-    \x05value\x18\x02\x20\x01(\x0b2\t.CellMetaR\x05value:\x028\x01\"\x82\x01\
-    \n\x08CellMeta\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x15\n\x06ro\
-    w_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08field_id\x18\x03\x20\x01(\
-    \tR\x07fieldId\x12\x1c\n\x04data\x18\x04\x20\x01(\x0b2\x08.AnyDataR\x04d\
-    ata\x12\x16\n\x06height\x18\x05\x20\x01(\x05R\x06height*d\n\tFieldType\
-    \x12\x0c\n\x08RichText\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08Date\
-    Time\x10\x02\x12\x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\
-    \x10\x04\x12\x0c\n\x08Checkbox\x10\x05b\x06proto3\
+    \x05value\x18\x02\x20\x01(\x0b2\t.CellMetaR\x05value:\x028\x01\"\xa7\x02\
+    \n\x10RowMetaChangeset\x12\x15\n\x06row_id\x18\x01\x20\x01(\tR\x05rowId\
+    \x12\x18\n\x06height\x18\x02\x20\x01(\x05H\0R\x06height\x12\x20\n\nvisib\
+    ility\x18\x03\x20\x01(\x08H\x01R\nvisibility\x12M\n\x10cell_by_field_id\
+    \x18\x04\x20\x03(\x0b2$.RowMetaChangeset.CellByFieldIdEntryR\rcellByFiel\
+    dId\x1aK\n\x12CellByFieldIdEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\
+    \x03key\x12\x1f\n\x05value\x18\x02\x20\x01(\x0b2\t.CellMetaR\x05value:\
+    \x028\x01B\x0f\n\rone_of_heightB\x13\n\x11one_of_visibility\"\x82\x01\n\
+    \x08CellMeta\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x15\n\x06row_\
+    id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08field_id\x18\x03\x20\x01(\t\
+    R\x07fieldId\x12\x1c\n\x04data\x18\x04\x20\x01(\x0b2\x08.AnyDataR\x04dat\
+    a\x12\x16\n\x06height\x18\x05\x20\x01(\x05R\x06height*d\n\tFieldType\x12\
+    \x0c\n\x08RichText\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\
+    \x10\x02\x12\x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\
+    \x04\x12\x0c\n\x08Checkbox\x10\x05b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 7 - 1
shared-lib/flowy-grid-data-model/src/protobuf/proto/meta.proto

@@ -33,11 +33,17 @@ message AnyData {
 }
 message RowMeta {
     string id = 1;
-    string grid_id = 2;
+    string block_id = 2;
     map<string, CellMeta> cell_by_field_id = 3;
     int32 height = 4;
     bool visibility = 5;
 }
+message RowMetaChangeset {
+    string row_id = 1;
+    oneof one_of_height { int32 height = 2; };
+    oneof one_of_visibility { bool visibility = 3; };
+    map<string, CellMeta> cell_by_field_id = 4;
+}
 message CellMeta {
     string id = 1;
     string row_id = 2;

+ 2 - 2
shared-lib/lib-ot/src/core/delta/delta.rs

@@ -501,7 +501,7 @@ impl<T> Delta<T>
 where
     T: Attributes + DeserializeOwned,
 {
-    pub fn from_json(json: &str) -> Result<Self, OTError> {
+    pub fn from_delta_str(json: &str) -> Result<Self, OTError> {
         let delta = serde_json::from_str(json).map_err(|e| {
             tracing::trace!("Deserialize failed: {:?}", e);
             tracing::trace!("{:?}", json);
@@ -512,7 +512,7 @@ where
 
     pub fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> Result<Self, OTError> {
         let json = str::from_utf8(bytes.as_ref())?.to_owned();
-        let val = Self::from_json(&json)?;
+        let val = Self::from_delta_str(&json)?;
         Ok(val)
     }
 }