Browse Source

chore: grid block meta editor

appflowy 3 years ago
parent
commit
e11176436d
32 changed files with 1885 additions and 527 deletions
  1. 267 29
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pb.dart
  2. 38 12
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pbjson.dart
  3. 2 2
      frontend/rust-lib/dart-ffi/Cargo.toml
  4. 2 4
      frontend/rust-lib/flowy-block/src/editor.rs
  5. 8 6
      frontend/rust-lib/flowy-block/src/manager.rs
  6. 8 20
      frontend/rust-lib/flowy-block/src/queue.rs
  7. 1 2
      frontend/rust-lib/flowy-database/migrations/2022-03-04-101530_flowy-grid/down.sql
  8. 0 9
      frontend/rust-lib/flowy-database/migrations/2022-03-04-101530_flowy-grid/up.sql
  9. 3 0
      frontend/rust-lib/flowy-database/migrations/2022-03-11-025536_flowy-grid/down.sql
  10. 18 0
      frontend/rust-lib/flowy-database/migrations/2022-03-11-025536_flowy-grid/up.sql
  11. 36 0
      frontend/rust-lib/flowy-database/src/macros.rs
  12. 12 0
      frontend/rust-lib/flowy-database/src/schema.rs
  13. 8 2
      frontend/rust-lib/flowy-folder/src/manager.rs
  14. 11 29
      frontend/rust-lib/flowy-folder/src/services/folder_editor.rs
  15. 3 1
      frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs
  16. 4 1
      frontend/rust-lib/flowy-grid/src/manager.rs
  17. 96 113
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  18. 125 0
      frontend/rust-lib/flowy-grid/src/services/grid_meta_editor.rs
  19. 1 0
      frontend/rust-lib/flowy-grid/src/services/mod.rs
  20. 6 6
      frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs
  21. 235 0
      frontend/rust-lib/flowy-sync/src/cache/disk/grid_meta_rev_impl.rs
  22. 14 52
      frontend/rust-lib/flowy-sync/src/cache/disk/grid_rev_impl.rs
  23. 4 5
      frontend/rust-lib/flowy-sync/src/cache/disk/mod.rs
  24. 9 52
      frontend/rust-lib/flowy-sync/src/cache/disk/text_rev_impl.rs
  25. 30 10
      frontend/rust-lib/flowy-sync/src/rev_manager.rs
  26. 19 15
      frontend/rust-lib/flowy-sync/src/rev_persistence.rs
  27. 27 27
      shared-lib/flowy-collaboration/src/client_grid/block_pad.rs
  28. 116 37
      shared-lib/flowy-collaboration/src/client_grid/grid_pad.rs
  29. 0 3
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  30. 38 5
      shared-lib/flowy-grid-data-model/src/entities/meta.rs
  31. 731 82
      shared-lib/flowy-grid-data-model/src/protobuf/model/meta.rs
  32. 13 3
      shared-lib/flowy-grid-data-model/src/protobuf/proto/meta.proto

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

@@ -17,7 +17,7 @@ class GridMeta extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridMeta', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
     ..pc<Field>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fields', $pb.PbFieldType.PM, subBuilder: Field.create)
-    ..pc<Block>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blocks', $pb.PbFieldType.PM, subBuilder: Block.create)
+    ..pc<GridBlock>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blocks', $pb.PbFieldType.PM, subBuilder: GridBlock.create)
     ..hasRequiredFields = false
   ;
 
@@ -25,7 +25,7 @@ class GridMeta extends $pb.GeneratedMessage {
   factory GridMeta({
     $core.String? gridId,
     $core.Iterable<Field>? fields,
-    $core.Iterable<Block>? blocks,
+    $core.Iterable<GridBlock>? blocks,
   }) {
     final _result = create();
     if (gridId != null) {
@@ -73,19 +73,19 @@ class GridMeta extends $pb.GeneratedMessage {
   $core.List<Field> get fields => $_getList(1);
 
   @$pb.TagNumber(3)
-  $core.List<Block> get blocks => $_getList(2);
+  $core.List<GridBlock> get blocks => $_getList(2);
 }
 
-class Block extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Block', createEmptyInstance: create)
+class GridBlock extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridBlock', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
     ..a<$core.int>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'startRowIndex', $pb.PbFieldType.O3)
     ..a<$core.int>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowCount', $pb.PbFieldType.O3)
     ..hasRequiredFields = false
   ;
 
-  Block._() : super();
-  factory Block({
+  GridBlock._() : super();
+  factory GridBlock({
     $core.String? id,
     $core.int? startRowIndex,
     $core.int? rowCount,
@@ -102,26 +102,26 @@ class Block extends $pb.GeneratedMessage {
     }
     return _result;
   }
-  factory Block.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory Block.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory GridBlock.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory GridBlock.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')
-  Block clone() => Block()..mergeFromMessage(this);
+  GridBlock clone() => GridBlock()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  Block copyWith(void Function(Block) updates) => super.copyWith((message) => updates(message as Block)) as Block; // ignore: deprecated_member_use
+  GridBlock copyWith(void Function(GridBlock) updates) => super.copyWith((message) => updates(message as GridBlock)) as GridBlock; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static Block create() => Block._();
-  Block createEmptyInstance() => create();
-  static $pb.PbList<Block> createRepeated() => $pb.PbList<Block>();
+  static GridBlock create() => GridBlock._();
+  GridBlock createEmptyInstance() => create();
+  static $pb.PbList<GridBlock> createRepeated() => $pb.PbList<GridBlock>();
   @$core.pragma('dart2js:noInline')
-  static Block getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Block>(create);
-  static Block? _defaultInstance;
+  static GridBlock getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<GridBlock>(create);
+  static GridBlock? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.String get id => $_getSZ(0);
@@ -151,15 +151,15 @@ class Block extends $pb.GeneratedMessage {
   void clearRowCount() => clearField(3);
 }
 
-class BlockMeta extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'BlockMeta', createEmptyInstance: create)
+class GridBlockMeta extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridBlockMeta', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blockId')
     ..pc<RowMeta>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rows', $pb.PbFieldType.PM, subBuilder: RowMeta.create)
     ..hasRequiredFields = false
   ;
 
-  BlockMeta._() : super();
-  factory BlockMeta({
+  GridBlockMeta._() : super();
+  factory GridBlockMeta({
     $core.String? blockId,
     $core.Iterable<RowMeta>? rows,
   }) {
@@ -172,26 +172,26 @@ class BlockMeta extends $pb.GeneratedMessage {
     }
     return _result;
   }
-  factory BlockMeta.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory BlockMeta.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory GridBlockMeta.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory GridBlockMeta.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')
-  BlockMeta clone() => BlockMeta()..mergeFromMessage(this);
+  GridBlockMeta clone() => GridBlockMeta()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  BlockMeta copyWith(void Function(BlockMeta) updates) => super.copyWith((message) => updates(message as BlockMeta)) as BlockMeta; // ignore: deprecated_member_use
+  GridBlockMeta copyWith(void Function(GridBlockMeta) updates) => super.copyWith((message) => updates(message as GridBlockMeta)) as GridBlockMeta; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static BlockMeta create() => BlockMeta._();
-  BlockMeta createEmptyInstance() => create();
-  static $pb.PbList<BlockMeta> createRepeated() => $pb.PbList<BlockMeta>();
+  static GridBlockMeta create() => GridBlockMeta._();
+  GridBlockMeta createEmptyInstance() => create();
+  static $pb.PbList<GridBlockMeta> createRepeated() => $pb.PbList<GridBlockMeta>();
   @$core.pragma('dart2js:noInline')
-  static BlockMeta getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<BlockMeta>(create);
-  static BlockMeta? _defaultInstance;
+  static GridBlockMeta getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<GridBlockMeta>(create);
+  static GridBlockMeta? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.String get blockId => $_getSZ(0);
@@ -394,6 +394,244 @@ class RepeatedField extends $pb.GeneratedMessage {
   $core.List<Field> get items => $_getList(0);
 }
 
+enum FieldChangeset_OneOfName {
+  name, 
+  notSet
+}
+
+enum FieldChangeset_OneOfDesc {
+  desc, 
+  notSet
+}
+
+enum FieldChangeset_OneOfFieldType {
+  fieldType, 
+  notSet
+}
+
+enum FieldChangeset_OneOfFrozen {
+  frozen, 
+  notSet
+}
+
+enum FieldChangeset_OneOfVisibility {
+  visibility, 
+  notSet
+}
+
+enum FieldChangeset_OneOfWidth {
+  width, 
+  notSet
+}
+
+enum FieldChangeset_OneOfTypeOptions {
+  typeOptions, 
+  notSet
+}
+
+class FieldChangeset extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, FieldChangeset_OneOfName> _FieldChangeset_OneOfNameByTag = {
+    2 : FieldChangeset_OneOfName.name,
+    0 : FieldChangeset_OneOfName.notSet
+  };
+  static const $core.Map<$core.int, FieldChangeset_OneOfDesc> _FieldChangeset_OneOfDescByTag = {
+    3 : FieldChangeset_OneOfDesc.desc,
+    0 : FieldChangeset_OneOfDesc.notSet
+  };
+  static const $core.Map<$core.int, FieldChangeset_OneOfFieldType> _FieldChangeset_OneOfFieldTypeByTag = {
+    4 : FieldChangeset_OneOfFieldType.fieldType,
+    0 : FieldChangeset_OneOfFieldType.notSet
+  };
+  static const $core.Map<$core.int, FieldChangeset_OneOfFrozen> _FieldChangeset_OneOfFrozenByTag = {
+    5 : FieldChangeset_OneOfFrozen.frozen,
+    0 : FieldChangeset_OneOfFrozen.notSet
+  };
+  static const $core.Map<$core.int, FieldChangeset_OneOfVisibility> _FieldChangeset_OneOfVisibilityByTag = {
+    6 : FieldChangeset_OneOfVisibility.visibility,
+    0 : FieldChangeset_OneOfVisibility.notSet
+  };
+  static const $core.Map<$core.int, FieldChangeset_OneOfWidth> _FieldChangeset_OneOfWidthByTag = {
+    7 : FieldChangeset_OneOfWidth.width,
+    0 : FieldChangeset_OneOfWidth.notSet
+  };
+  static const $core.Map<$core.int, FieldChangeset_OneOfTypeOptions> _FieldChangeset_OneOfTypeOptionsByTag = {
+    8 : FieldChangeset_OneOfTypeOptions.typeOptions,
+    0 : FieldChangeset_OneOfTypeOptions.notSet
+  };
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'FieldChangeset', createEmptyInstance: create)
+    ..oo(0, [2])
+    ..oo(1, [3])
+    ..oo(2, [4])
+    ..oo(3, [5])
+    ..oo(4, [6])
+    ..oo(5, [7])
+    ..oo(6, [8])
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
+    ..e<FieldType>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldType', $pb.PbFieldType.OE, defaultOrMaker: FieldType.RichText, valueOf: FieldType.valueOf, enumValues: FieldType.values)
+    ..aOB(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'frozen')
+    ..aOB(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'visibility')
+    ..a<$core.int>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'width', $pb.PbFieldType.O3)
+    ..aOM<AnyData>(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptions', subBuilder: AnyData.create)
+    ..hasRequiredFields = false
+  ;
+
+  FieldChangeset._() : super();
+  factory FieldChangeset({
+    $core.String? fieldId,
+    $core.String? name,
+    $core.String? desc,
+    FieldType? fieldType,
+    $core.bool? frozen,
+    $core.bool? visibility,
+    $core.int? width,
+    AnyData? typeOptions,
+  }) {
+    final _result = create();
+    if (fieldId != null) {
+      _result.fieldId = fieldId;
+    }
+    if (name != null) {
+      _result.name = name;
+    }
+    if (desc != null) {
+      _result.desc = desc;
+    }
+    if (fieldType != null) {
+      _result.fieldType = fieldType;
+    }
+    if (frozen != null) {
+      _result.frozen = frozen;
+    }
+    if (visibility != null) {
+      _result.visibility = visibility;
+    }
+    if (width != null) {
+      _result.width = width;
+    }
+    if (typeOptions != null) {
+      _result.typeOptions = typeOptions;
+    }
+    return _result;
+  }
+  factory FieldChangeset.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory FieldChangeset.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')
+  FieldChangeset clone() => FieldChangeset()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  FieldChangeset copyWith(void Function(FieldChangeset) updates) => super.copyWith((message) => updates(message as FieldChangeset)) as FieldChangeset; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static FieldChangeset create() => FieldChangeset._();
+  FieldChangeset createEmptyInstance() => create();
+  static $pb.PbList<FieldChangeset> createRepeated() => $pb.PbList<FieldChangeset>();
+  @$core.pragma('dart2js:noInline')
+  static FieldChangeset getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FieldChangeset>(create);
+  static FieldChangeset? _defaultInstance;
+
+  FieldChangeset_OneOfName whichOneOfName() => _FieldChangeset_OneOfNameByTag[$_whichOneof(0)]!;
+  void clearOneOfName() => clearField($_whichOneof(0));
+
+  FieldChangeset_OneOfDesc whichOneOfDesc() => _FieldChangeset_OneOfDescByTag[$_whichOneof(1)]!;
+  void clearOneOfDesc() => clearField($_whichOneof(1));
+
+  FieldChangeset_OneOfFieldType whichOneOfFieldType() => _FieldChangeset_OneOfFieldTypeByTag[$_whichOneof(2)]!;
+  void clearOneOfFieldType() => clearField($_whichOneof(2));
+
+  FieldChangeset_OneOfFrozen whichOneOfFrozen() => _FieldChangeset_OneOfFrozenByTag[$_whichOneof(3)]!;
+  void clearOneOfFrozen() => clearField($_whichOneof(3));
+
+  FieldChangeset_OneOfVisibility whichOneOfVisibility() => _FieldChangeset_OneOfVisibilityByTag[$_whichOneof(4)]!;
+  void clearOneOfVisibility() => clearField($_whichOneof(4));
+
+  FieldChangeset_OneOfWidth whichOneOfWidth() => _FieldChangeset_OneOfWidthByTag[$_whichOneof(5)]!;
+  void clearOneOfWidth() => clearField($_whichOneof(5));
+
+  FieldChangeset_OneOfTypeOptions whichOneOfTypeOptions() => _FieldChangeset_OneOfTypeOptionsByTag[$_whichOneof(6)]!;
+  void clearOneOfTypeOptions() => clearField($_whichOneof(6));
+
+  @$pb.TagNumber(1)
+  $core.String get fieldId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set fieldId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasFieldId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearFieldId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get name => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set name($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasName() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearName() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.String get desc => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set desc($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasDesc() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearDesc() => clearField(3);
+
+  @$pb.TagNumber(4)
+  FieldType get fieldType => $_getN(3);
+  @$pb.TagNumber(4)
+  set fieldType(FieldType v) { setField(4, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasFieldType() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearFieldType() => clearField(4);
+
+  @$pb.TagNumber(5)
+  $core.bool get frozen => $_getBF(4);
+  @$pb.TagNumber(5)
+  set frozen($core.bool v) { $_setBool(4, v); }
+  @$pb.TagNumber(5)
+  $core.bool hasFrozen() => $_has(4);
+  @$pb.TagNumber(5)
+  void clearFrozen() => clearField(5);
+
+  @$pb.TagNumber(6)
+  $core.bool get visibility => $_getBF(5);
+  @$pb.TagNumber(6)
+  set visibility($core.bool v) { $_setBool(5, v); }
+  @$pb.TagNumber(6)
+  $core.bool hasVisibility() => $_has(5);
+  @$pb.TagNumber(6)
+  void clearVisibility() => clearField(6);
+
+  @$pb.TagNumber(7)
+  $core.int get width => $_getIZ(6);
+  @$pb.TagNumber(7)
+  set width($core.int v) { $_setSignedInt32(6, v); }
+  @$pb.TagNumber(7)
+  $core.bool hasWidth() => $_has(6);
+  @$pb.TagNumber(7)
+  void clearWidth() => clearField(7);
+
+  @$pb.TagNumber(8)
+  AnyData get typeOptions => $_getN(7);
+  @$pb.TagNumber(8)
+  set typeOptions(AnyData v) { setField(8, v); }
+  @$pb.TagNumber(8)
+  $core.bool hasTypeOptions() => $_has(7);
+  @$pb.TagNumber(8)
+  void clearTypeOptions() => clearField(8);
+  @$pb.TagNumber(8)
+  AnyData ensureTypeOptions() => $_ensure(7);
+}
+
 class AnyData extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'AnyData', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeId')

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

@@ -29,15 +29,15 @@ const GridMeta$json = const {
   '2': const [
     const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
     const {'1': 'fields', '3': 2, '4': 3, '5': 11, '6': '.Field', '10': 'fields'},
-    const {'1': 'blocks', '3': 3, '4': 3, '5': 11, '6': '.Block', '10': 'blocks'},
+    const {'1': 'blocks', '3': 3, '4': 3, '5': 11, '6': '.GridBlock', '10': 'blocks'},
   ],
 };
 
 /// Descriptor for `GridMeta`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List gridMetaDescriptor = $convert.base64Decode('CghHcmlkTWV0YRIXCgdncmlkX2lkGAEgASgJUgZncmlkSWQSHgoGZmllbGRzGAIgAygLMgYuRmllbGRSBmZpZWxkcxIeCgZibG9ja3MYAyADKAsyBi5CbG9ja1IGYmxvY2tz');
-@$core.Deprecated('Use blockDescriptor instead')
-const Block$json = const {
-  '1': 'Block',
+final $typed_data.Uint8List gridMetaDescriptor = $convert.base64Decode('CghHcmlkTWV0YRIXCgdncmlkX2lkGAEgASgJUgZncmlkSWQSHgoGZmllbGRzGAIgAygLMgYuRmllbGRSBmZpZWxkcxIiCgZibG9ja3MYAyADKAsyCi5HcmlkQmxvY2tSBmJsb2Nrcw==');
+@$core.Deprecated('Use gridBlockDescriptor instead')
+const GridBlock$json = const {
+  '1': 'GridBlock',
   '2': const [
     const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
     const {'1': 'start_row_index', '3': 2, '4': 1, '5': 5, '10': 'startRowIndex'},
@@ -45,19 +45,19 @@ const Block$json = const {
   ],
 };
 
-/// Descriptor for `Block`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List blockDescriptor = $convert.base64Decode('CgVCbG9jaxIOCgJpZBgBIAEoCVICaWQSJgoPc3RhcnRfcm93X2luZGV4GAIgASgFUg1zdGFydFJvd0luZGV4EhsKCXJvd19jb3VudBgDIAEoBVIIcm93Q291bnQ=');
-@$core.Deprecated('Use blockMetaDescriptor instead')
-const BlockMeta$json = const {
-  '1': 'BlockMeta',
+/// Descriptor for `GridBlock`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List gridBlockDescriptor = $convert.base64Decode('CglHcmlkQmxvY2sSDgoCaWQYASABKAlSAmlkEiYKD3N0YXJ0X3Jvd19pbmRleBgCIAEoBVINc3RhcnRSb3dJbmRleBIbCglyb3dfY291bnQYAyABKAVSCHJvd0NvdW50');
+@$core.Deprecated('Use gridBlockMetaDescriptor instead')
+const GridBlockMeta$json = const {
+  '1': 'GridBlockMeta',
   '2': const [
     const {'1': 'block_id', '3': 1, '4': 1, '5': 9, '10': 'blockId'},
     const {'1': 'rows', '3': 2, '4': 3, '5': 11, '6': '.RowMeta', '10': 'rows'},
   ],
 };
 
-/// Descriptor for `BlockMeta`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List blockMetaDescriptor = $convert.base64Decode('CglCbG9ja01ldGESGQoIYmxvY2tfaWQYASABKAlSB2Jsb2NrSWQSHAoEcm93cxgCIAMoCzIILlJvd01ldGFSBHJvd3M=');
+/// Descriptor for `GridBlockMeta`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List gridBlockMetaDescriptor = $convert.base64Decode('Cg1HcmlkQmxvY2tNZXRhEhkKCGJsb2NrX2lkGAEgASgJUgdibG9ja0lkEhwKBHJvd3MYAiADKAsyCC5Sb3dNZXRhUgRyb3dz');
 @$core.Deprecated('Use fieldDescriptor instead')
 const Field$json = const {
   '1': 'Field',
@@ -85,6 +85,32 @@ const RepeatedField$json = const {
 
 /// Descriptor for `RepeatedField`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List repeatedFieldDescriptor = $convert.base64Decode('Cg1SZXBlYXRlZEZpZWxkEhwKBWl0ZW1zGAEgAygLMgYuRmllbGRSBWl0ZW1z');
+@$core.Deprecated('Use fieldChangesetDescriptor instead')
+const FieldChangeset$json = const {
+  '1': 'FieldChangeset',
+  '2': const [
+    const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'},
+    const {'1': 'name', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'name'},
+    const {'1': 'desc', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'desc'},
+    const {'1': 'field_type', '3': 4, '4': 1, '5': 14, '6': '.FieldType', '9': 2, '10': 'fieldType'},
+    const {'1': 'frozen', '3': 5, '4': 1, '5': 8, '9': 3, '10': 'frozen'},
+    const {'1': 'visibility', '3': 6, '4': 1, '5': 8, '9': 4, '10': 'visibility'},
+    const {'1': 'width', '3': 7, '4': 1, '5': 5, '9': 5, '10': 'width'},
+    const {'1': 'type_options', '3': 8, '4': 1, '5': 11, '6': '.AnyData', '9': 6, '10': 'typeOptions'},
+  ],
+  '8': const [
+    const {'1': 'one_of_name'},
+    const {'1': 'one_of_desc'},
+    const {'1': 'one_of_field_type'},
+    const {'1': 'one_of_frozen'},
+    const {'1': 'one_of_visibility'},
+    const {'1': 'one_of_width'},
+    const {'1': 'one_of_type_options'},
+  ],
+};
+
+/// Descriptor for `FieldChangeset`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List fieldChangesetDescriptor = $convert.base64Decode('Cg5GaWVsZENoYW5nZXNldBIZCghmaWVsZF9pZBgBIAEoCVIHZmllbGRJZBIUCgRuYW1lGAIgASgJSABSBG5hbWUSFAoEZGVzYxgDIAEoCUgBUgRkZXNjEisKCmZpZWxkX3R5cGUYBCABKA4yCi5GaWVsZFR5cGVIAlIJZmllbGRUeXBlEhgKBmZyb3plbhgFIAEoCEgDUgZmcm96ZW4SIAoKdmlzaWJpbGl0eRgGIAEoCEgEUgp2aXNpYmlsaXR5EhYKBXdpZHRoGAcgASgFSAVSBXdpZHRoEi0KDHR5cGVfb3B0aW9ucxgIIAEoCzIILkFueURhdGFIBlILdHlwZU9wdGlvbnNCDQoLb25lX29mX25hbWVCDQoLb25lX29mX2Rlc2NCEwoRb25lX29mX2ZpZWxkX3R5cGVCDwoNb25lX29mX2Zyb3plbkITChFvbmVfb2ZfdmlzaWJpbGl0eUIOCgxvbmVfb2Zfd2lkdGhCFQoTb25lX29mX3R5cGVfb3B0aW9ucw==');
 @$core.Deprecated('Use anyDataDescriptor instead')
 const AnyData$json = const {
   '1': 'AnyData',

+ 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]

+ 2 - 4
frontend/rust-lib/flowy-block/src/editor.rs

@@ -1,4 +1,4 @@
-use crate::queue::BlockRevisionCompact;
+use crate::queue::TextBlockRevisionCompactor;
 use crate::web_socket::{make_block_ws_manager, EditorCommandSender};
 use crate::{
     errors::FlowyError,
@@ -40,9 +40,7 @@ impl ClientTextBlockEditor {
         rev_web_socket: Arc<dyn RevisionWebSocket>,
         cloud_service: Arc<dyn RevisionCloudService>,
     ) -> FlowyResult<Arc<Self>> {
-        let document_info = rev_manager
-            .load::<BlockInfoBuilder, BlockRevisionCompact>(cloud_service)
-            .await?;
+        let document_info = rev_manager.load::<BlockInfoBuilder>(cloud_service).await?;
         let delta = document_info.delta()?;
         let rev_manager = Arc::new(rev_manager);
         let doc_id = doc_id.to_string();

+ 8 - 6
frontend/rust-lib/flowy-block/src/manager.rs

@@ -8,6 +8,7 @@ use flowy_collaboration::entities::{
 };
 use flowy_database::ConnectionPool;
 use flowy_error::FlowyResult;
+use flowy_sync::disk::SQLiteTextBlockRevisionPersistence;
 use flowy_sync::{RevisionCloudService, RevisionManager, RevisionPersistence, RevisionWebSocket};
 use lib_infra::future::FutureResult;
 use std::{convert::TryInto, sync::Arc};
@@ -84,7 +85,7 @@ impl TextBlockManager {
         let doc_id = doc_id.as_ref().to_owned();
         let db_pool = self.user.db_pool()?;
         // Maybe we could save the block to disk without creating the RevisionManager
-        let rev_manager = self.make_block_rev_manager(&doc_id, db_pool)?;
+        let rev_manager = self.make_rev_manager(&doc_id, db_pool)?;
         let _ = rev_manager.reset_object(revisions).await?;
         Ok(())
     }
@@ -111,20 +112,20 @@ impl TextBlockManager {
         match self.editor_map.get(block_id) {
             None => {
                 let db_pool = self.user.db_pool()?;
-                self.make_block_editor(block_id, db_pool).await
+                self.make_text_block_editor(block_id, db_pool).await
             }
             Some(editor) => Ok(editor),
         }
     }
 
-    async fn make_block_editor(
+    async fn make_text_block_editor(
         &self,
         block_id: &str,
         pool: Arc<ConnectionPool>,
     ) -> Result<Arc<ClientTextBlockEditor>, FlowyError> {
         let user = self.user.clone();
         let token = self.user.token()?;
-        let rev_manager = self.make_block_rev_manager(block_id, pool.clone())?;
+        let rev_manager = self.make_rev_manager(block_id, pool.clone())?;
         let cloud_service = Arc::new(TextBlockRevisionCloudService {
             token,
             server: self.cloud_service.clone(),
@@ -135,9 +136,10 @@ impl TextBlockManager {
         Ok(doc_editor)
     }
 
-    fn make_block_rev_manager(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<RevisionManager, FlowyError> {
+    fn make_rev_manager(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<RevisionManager, FlowyError> {
         let user_id = self.user.user_id()?;
-        let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, doc_id, pool));
+        let disk_cache = Arc::new(SQLiteTextBlockRevisionPersistence::new(&user_id, pool));
+        let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, doc_id, disk_cache));
         Ok(RevisionManager::new(&user_id, doc_id, rev_persistence))
     }
 }

+ 8 - 20
frontend/rust-lib/flowy-block/src/queue.rs

@@ -1,6 +1,7 @@
 use crate::web_socket::EditorCommandReceiver;
 use crate::TextBlockUser;
 use async_stream::stream;
+use bytes::Bytes;
 use flowy_collaboration::util::make_delta_from_revisions;
 use flowy_collaboration::{
     client_document::{history::UndoResult, ClientDocument},
@@ -8,8 +9,9 @@ use flowy_collaboration::{
     errors::CollaborateError,
 };
 use flowy_error::{FlowyError, FlowyResult};
-use flowy_sync::{DeltaMD5, RevisionCompact, RevisionManager, RichTextTransformDeltas, TransformDeltas};
+use flowy_sync::{DeltaMD5, RevisionCompactor, RevisionManager, RichTextTransformDeltas, TransformDeltas};
 use futures::stream::StreamExt;
+use lib_ot::core::{Attributes, Delta};
 use lib_ot::{
     core::{Interval, OperationTransformable},
     rich_text::{RichTextAttribute, RichTextAttributes, RichTextDelta},
@@ -187,31 +189,17 @@ impl EditBlockQueue {
         );
         let _ = self
             .rev_manager
-            .add_local_revision::<BlockRevisionCompact>(&revision)
+            .add_local_revision(&revision, Box::new(TextBlockRevisionCompactor()))
             .await?;
         Ok(rev_id.into())
     }
 }
 
-pub(crate) struct BlockRevisionCompact();
-impl RevisionCompact for BlockRevisionCompact {
-    fn compact_revisions(user_id: &str, object_id: &str, mut revisions: Vec<Revision>) -> FlowyResult<Revision> {
-        if revisions.is_empty() {
-            return Err(FlowyError::internal().context("Can't compact the empty block's revisions"));
-        }
-
-        if revisions.len() == 1 {
-            return Ok(revisions.pop().unwrap());
-        }
-
-        let first_revision = revisions.first().unwrap();
-        let last_revision = revisions.last().unwrap();
-
-        let (base_rev_id, rev_id) = first_revision.pair_rev_id();
-        let md5 = last_revision.md5.clone();
+pub(crate) struct TextBlockRevisionCompactor();
+impl RevisionCompactor for TextBlockRevisionCompactor {
+    fn bytes_from_revisions(&self, revisions: Vec<Revision>) -> FlowyResult<Bytes> {
         let delta = make_delta_from_revisions::<RichTextAttributes>(revisions)?;
-        let delta_data = delta.to_bytes();
-        Ok(Revision::new(object_id, base_rev_id, rev_id, delta_data, user_id, md5))
+        Ok(delta.to_bytes())
     }
 }
 

+ 1 - 2
frontend/rust-lib/flowy-database/migrations/2022-03-04-101530_flowy-grid/down.sql

@@ -1,3 +1,2 @@
 -- This file should undo anything in `up.sql`
-DROP TABLE kv_table;
-DROP TABLE grid_rev_table;
+DROP TABLE kv_table;

+ 0 - 9
frontend/rust-lib/flowy-database/migrations/2022-03-04-101530_flowy-grid/up.sql

@@ -2,13 +2,4 @@
 CREATE TABLE kv_table (
    key TEXT NOT NULL PRIMARY KEY,
    value BLOB NOT NULL DEFAULT (x'')
-);
-
-CREATE TABLE grid_rev_table (
-   id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
-   object_id TEXT NOT NULL DEFAULT '',
-   base_rev_id BIGINT NOT NULL DEFAULT 0,
-   rev_id BIGINT NOT NULL DEFAULT 0,
-   data BLOB NOT NULL DEFAULT (x''),
-   state INTEGER NOT NULL DEFAULT 0
 );

+ 3 - 0
frontend/rust-lib/flowy-database/migrations/2022-03-11-025536_flowy-grid/down.sql

@@ -0,0 +1,3 @@
+-- This file should undo anything in `up.sql`
+DROP TABLE grid_rev_table;
+DROP TABLE grid_meta_rev_table;

+ 18 - 0
frontend/rust-lib/flowy-database/migrations/2022-03-11-025536_flowy-grid/up.sql

@@ -0,0 +1,18 @@
+-- Your SQL goes here
+CREATE TABLE grid_rev_table (
+    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+    object_id TEXT NOT NULL DEFAULT '',
+    base_rev_id BIGINT NOT NULL DEFAULT 0,
+    rev_id BIGINT NOT NULL DEFAULT 0,
+    data BLOB NOT NULL DEFAULT (x''),
+    state INTEGER NOT NULL DEFAULT 0
+);
+
+CREATE TABLE grid_meta_rev_table (
+    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+    object_id TEXT NOT NULL DEFAULT '',
+    base_rev_id BIGINT NOT NULL DEFAULT 0,
+    rev_id BIGINT NOT NULL DEFAULT 0,
+    data BLOB NOT NULL DEFAULT (x''),
+    state INTEGER NOT NULL DEFAULT 0
+);

+ 36 - 0
frontend/rust-lib/flowy-database/src/macros.rs

@@ -160,3 +160,39 @@ macro_rules! impl_sql_integer_expression {
         }
     };
 }
+
+#[macro_export]
+macro_rules! impl_rev_state_map {
+    ($target:ident) => {
+        impl std::convert::From<i32> for $target {
+            fn from(value: i32) -> Self {
+                match value {
+                    0 => $target::Sync,
+                    1 => $target::Ack,
+                    o => {
+                        tracing::error!("Unsupported rev state {}, fallback to RevState::Local", o);
+                        $target::Sync
+                    }
+                }
+            }
+        }
+
+        impl std::convert::From<$target> for RevisionState {
+            fn from(s: $target) -> Self {
+                match s {
+                    $target::Sync => RevisionState::Sync,
+                    $target::Ack => RevisionState::Ack,
+                }
+            }
+        }
+
+        impl std::convert::From<RevisionState> for $target {
+            fn from(s: RevisionState) -> Self {
+                match s {
+                    RevisionState::Sync => $target::Sync,
+                    RevisionState::Ack => $target::Ack,
+                }
+            }
+        }
+    };
+}

+ 12 - 0
frontend/rust-lib/flowy-database/src/schema.rs

@@ -21,6 +21,17 @@ table! {
     }
 }
 
+table! {
+    grid_meta_rev_table (id) {
+        id -> Integer,
+        object_id -> Text,
+        base_rev_id -> BigInt,
+        rev_id -> BigInt,
+        data -> Binary,
+        state -> Integer,
+    }
+}
+
 table! {
     grid_rev_table (id) {
         id -> Integer,
@@ -102,6 +113,7 @@ table! {
 allow_tables_to_appear_in_same_query!(
     app_table,
     doc_table,
+    grid_meta_rev_table,
     grid_rev_table,
     kv_table,
     rev_table,

+ 8 - 2
frontend/rust-lib/flowy-folder/src/manager.rs

@@ -17,7 +17,8 @@ use flowy_collaboration::{client_folder::FolderPad, entities::ws_data::ServerRev
 use flowy_error::FlowyError;
 use flowy_folder_data_model::entities::view::ViewDataType;
 use flowy_folder_data_model::user_default;
-use flowy_sync::RevisionWebSocket;
+use flowy_sync::disk::SQLiteTextBlockRevisionPersistence;
+use flowy_sync::{RevisionManager, RevisionPersistence, RevisionWebSocket};
 use lazy_static::lazy_static;
 use lib_infra::future::FutureResult;
 use std::{collections::HashMap, convert::TryInto, fmt::Formatter, sync::Arc};
@@ -163,7 +164,12 @@ impl FolderManager {
         let _ = self.persistence.initialize(user_id, &folder_id).await?;
 
         let pool = self.persistence.db_pool()?;
-        let folder_editor = ClientFolderEditor::new(user_id, &folder_id, token, pool, self.web_socket.clone()).await?;
+        let disk_cache = Arc::new(SQLiteTextBlockRevisionPersistence::new(&user_id, pool));
+        let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), disk_cache));
+        let rev_manager = RevisionManager::new(user_id, folder_id.as_ref(), rev_persistence);
+
+        let folder_editor =
+            ClientFolderEditor::new(user_id, &folder_id, token, rev_manager, self.web_socket.clone()).await?;
         *self.folder_editor.write().await = Some(Arc::new(folder_editor));
 
         let _ = self.app_controller.initialize()?;

+ 11 - 29
frontend/rust-lib/flowy-folder/src/services/folder_editor.rs

@@ -5,14 +5,16 @@ use flowy_collaboration::{
 };
 
 use crate::manager::FolderId;
+use bytes::Bytes;
 use flowy_collaboration::util::make_delta_from_revisions;
 use flowy_error::{FlowyError, FlowyResult};
+use flowy_sync::disk::RevisionDiskCache;
 use flowy_sync::{
-    RevisionCloudService, RevisionCompact, RevisionManager, RevisionObjectBuilder, RevisionPersistence,
+    RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder, RevisionPersistence,
     RevisionWebSocket, RevisionWebSocketManager,
 };
 use lib_infra::future::FutureResult;
-use lib_ot::core::PlainTextAttributes;
+use lib_ot::core::{Delta, PlainTextAttributes};
 use lib_sqlite::ConnectionPool;
 use parking_lot::RwLock;
 use std::sync::Arc;
@@ -30,19 +32,13 @@ impl ClientFolderEditor {
         user_id: &str,
         folder_id: &FolderId,
         token: &str,
-        pool: Arc<ConnectionPool>,
+        mut rev_manager: RevisionManager,
         web_socket: Arc<dyn RevisionWebSocket>,
     ) -> FlowyResult<Self> {
-        let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), pool));
-        let mut rev_manager = RevisionManager::new(user_id, folder_id.as_ref(), rev_persistence);
         let cloud = Arc::new(FolderRevisionCloudService {
             token: token.to_string(),
         });
-        let folder = Arc::new(RwLock::new(
-            rev_manager
-                .load::<FolderPadBuilder, FolderRevisionCompact>(cloud)
-                .await?,
-        ));
+        let folder = Arc::new(RwLock::new(rev_manager.load::<FolderPadBuilder>(cloud).await?));
         let rev_manager = Arc::new(rev_manager);
         let ws_manager = make_folder_ws_manager(
             user_id,
@@ -86,7 +82,7 @@ impl ClientFolderEditor {
         );
         let _ = futures::executor::block_on(async {
             self.rev_manager
-                .add_local_revision::<FolderRevisionCompact>(&revision)
+                .add_local_revision(&revision, Box::new(FolderRevisionCompactor()))
                 .await
         })?;
         Ok(())
@@ -128,24 +124,10 @@ impl ClientFolderEditor {
     }
 }
 
-struct FolderRevisionCompact();
-impl RevisionCompact for FolderRevisionCompact {
-    fn compact_revisions(user_id: &str, object_id: &str, mut revisions: Vec<Revision>) -> FlowyResult<Revision> {
-        if revisions.is_empty() {
-            return Err(FlowyError::internal().context("Can't compact the empty folder's revisions"));
-        }
-
-        if revisions.len() == 1 {
-            return Ok(revisions.pop().unwrap());
-        }
-
-        let first_revision = revisions.first().unwrap();
-        let last_revision = revisions.last().unwrap();
-
-        let (base_rev_id, rev_id) = first_revision.pair_rev_id();
-        let md5 = last_revision.md5.clone();
+struct FolderRevisionCompactor();
+impl RevisionCompactor for FolderRevisionCompactor {
+    fn bytes_from_revisions(&self, revisions: Vec<Revision>) -> FlowyResult<Bytes> {
         let delta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
-        let delta_data = delta.to_bytes();
-        Ok(Revision::new(object_id, base_rev_id, rev_id, delta_data, user_id, md5))
+        Ok(delta.to_bytes())
     }
 }

+ 3 - 1
frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs

@@ -11,6 +11,7 @@ use flowy_folder_data_model::entities::{
     view::{RepeatedView, View},
     workspace::Workspace,
 };
+use flowy_sync::disk::SQLiteTextBlockRevisionPersistence;
 use flowy_sync::{RevisionLoader, RevisionPersistence};
 use std::sync::Arc;
 
@@ -87,7 +88,8 @@ impl FolderMigration {
             return Ok(None);
         }
         let pool = self.database.db_pool()?;
-        let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), pool.clone()));
+        let disk_cache = Arc::new(SQLiteTextBlockRevisionPersistence::new(&user_id, pool));
+        let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), disk_cache));
         let (revisions, _) = RevisionLoader {
             object_id: folder_id.as_ref().to_owned(),
             user_id: self.user_id.clone(),

+ 4 - 1
frontend/rust-lib/flowy-grid/src/manager.rs

@@ -7,6 +7,7 @@ use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{Field, RowMeta};
 use flowy_sync::{RevisionManager, RevisionPersistence, RevisionWebSocket};
 
+use flowy_sync::disk::SQLiteGridRevisionPersistence;
 use lib_sqlite::ConnectionPool;
 use parking_lot::RwLock;
 use std::sync::Arc;
@@ -104,7 +105,9 @@ impl GridManager {
 
     fn make_grid_rev_manager(&self, grid_id: &str, pool: Arc<ConnectionPool>) -> FlowyResult<RevisionManager> {
         let user_id = self.grid_user.user_id()?;
-        let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, grid_id, pool));
+
+        let disk_cache = Arc::new(SQLiteGridRevisionPersistence::new(&user_id, pool));
+        let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, grid_id, disk_cache));
         let rev_manager = RevisionManager::new(&user_id, grid_id, rev_persistence);
         Ok(rev_manager)
     }

+ 96 - 113
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -2,6 +2,8 @@ use crate::manager::GridUser;
 use crate::services::kv_persistence::{GridKVPersistence, KVTransaction};
 use crate::services::stringify::stringify_deserialize;
 
+use crate::services::grid_meta_editor::ClientGridBlockMetaEditor;
+use bytes::Bytes;
 use dashmap::DashMap;
 use flowy_collaboration::client_grid::{GridChange, GridMetaPad};
 use flowy_collaboration::entities::revision::Revision;
@@ -10,10 +12,10 @@ use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
     Cell, CellMeta, Field, Grid, RepeatedField, RepeatedFieldOrder, RepeatedRow, RepeatedRowOrder, Row, RowMeta,
 };
-use flowy_sync::{RevisionCloudService, RevisionCompact, RevisionManager, RevisionObjectBuilder};
+use flowy_sync::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
 use lib_infra::future::FutureResult;
 use lib_infra::uuid;
-use lib_ot::core::PlainTextAttributes;
+use lib_ot::core::{Delta, PlainTextAttributes};
 use rayon::iter::{IntoParallelIterator, ParallelIterator};
 use std::collections::HashMap;
 use std::sync::Arc;
@@ -24,10 +26,8 @@ pub struct ClientGridEditor {
     user: Arc<dyn GridUser>,
     grid_meta_pad: Arc<RwLock<GridMetaPad>>,
     rev_manager: Arc<RevisionManager>,
+    block_meta_manager: Arc<GridBlockMetaEditorManager>,
     kv_persistence: Arc<GridKVPersistence>,
-
-    field_map: DashMap<String, Field>,
-    cell_map: DashMap<String, CellMeta>,
 }
 
 impl ClientGridEditor {
@@ -39,50 +39,22 @@ impl ClientGridEditor {
     ) -> FlowyResult<Arc<Self>> {
         let token = user.token()?;
         let cloud = Arc::new(GridRevisionCloudService { token });
-        let grid_pad = rev_manager.load::<GridPadBuilder, GridRevisionCompact>(cloud).await?;
+        let grid_pad = rev_manager.load::<GridPadBuilder>(cloud).await?;
 
         let rev_manager = Arc::new(rev_manager);
-        let field_map = load_all_fields(&grid_pad, &kv_persistence).await?;
         let grid_meta_pad = Arc::new(RwLock::new(grid_pad));
-        let cell_map = DashMap::new();
+        let block_meta_manager = Arc::new(GridBlockMetaEditorManager::new());
 
         Ok(Arc::new(Self {
             grid_id: grid_id.to_owned(),
             user,
             grid_meta_pad,
             rev_manager,
+            block_meta_manager,
             kv_persistence,
-            field_map,
-            cell_map,
         }))
     }
 
-    pub async fn create_empty_row(&self) -> FlowyResult<()> {
-        let row = RowMeta::new(&uuid(), &self.grid_id, vec![]);
-        self.create_row(row).await?;
-        Ok(())
-    }
-
-    async fn create_row(&self, row: RowMeta) -> FlowyResult<()> {
-        let _ = self.modify(|grid| Ok(grid.create_row(row)?)).await?;
-        // self.cell_map.insert(row.id.clone(), row.clone());
-        // let _ = self.kv_persistence.set(row)?;
-        Ok(())
-    }
-
-    pub async fn delete_rows(&self, ids: Vec<String>) -> FlowyResult<()> {
-        let _ = self.modify(|grid| Ok(grid.delete_rows(&ids)?)).await?;
-        // let _ = self.kv.batch_delete(ids)?;
-        Ok(())
-    }
-
-    // pub async fn update_row(&self, cell: Cell) -> FlowyResult<()> {
-    //     match self.cell_map.get(&cell.id) {
-    //         None => Err(FlowyError::internal().context(format!("Can't find cell with id: {}", cell.id))),
-    //         Some(raw_cell) => {}
-    //     }
-    // }
-
     pub async fn create_field(&mut self, field: Field) -> FlowyResult<()> {
         let _ = self.modify(|grid| Ok(grid.create_field(field)?)).await?;
         Ok(())
@@ -90,76 +62,38 @@ impl ClientGridEditor {
 
     pub async fn delete_field(&mut self, field_id: &str) -> FlowyResult<()> {
         let _ = self.modify(|grid| Ok(grid.delete_field(field_id)?)).await?;
-        // let _ = self.kv.remove(field_id)?;
         Ok(())
     }
 
-    pub async fn get_rows(&self, row_orders: RepeatedRowOrder) -> FlowyResult<RepeatedRow> {
-        let ids = row_orders
-            .items
-            .into_iter()
-            .map(|row_order| row_order.row_id)
-            .collect::<Vec<_>>();
-        let row_metas: Vec<RowMeta> = self.kv_persistence.batch_get(ids)?;
-
-        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,
-            }
-        };
+    pub async fn create_empty_row(&self) -> FlowyResult<()> {
+        // let _ = self.modify(|grid| {
+        //
+        //
+        //     grid.blocks
+        //
+        // }).await?;
+        todo!()
+    }
 
-        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>>();
+    async fn create_row(&self, row: RowMeta) -> FlowyResult<()> {
+        todo!()
+    }
 
-        Ok(rows.into())
+    pub async fn get_rows(&self, row_orders: RepeatedRowOrder) -> FlowyResult<RepeatedRow> {
+        todo!()
     }
 
-    pub async fn get_fields(&self, field_orders: RepeatedFieldOrder) -> FlowyResult<RepeatedField> {
-        let fields = field_orders
-            .iter()
-            .flat_map(|field_order| match self.field_map.get(&field_order.field_id) {
-                None => {
-                    tracing::error!("Can't find the field with {}", field_order.field_id);
-                    None
-                }
-                Some(field) => Some(field.value().clone()),
-            })
-            .collect::<Vec<Field>>();
-        Ok(fields.into())
+    pub async fn delete_rows(&self, ids: Vec<String>) -> FlowyResult<()> {
+        todo!()
     }
 
     pub async fn grid_data(&self) -> Grid {
-        self.grid_meta_pad.read().await.grid_data()
+        todo!()
+    }
+
+    pub async fn get_fields(&self, field_orders: RepeatedFieldOrder) -> FlowyResult<RepeatedField> {
+        let fields = self.grid_meta_pad.read().await.get_fields(field_orders)?;
+        Ok(fields)
     }
 
     pub async fn delta_str(&self) -> String {
@@ -195,7 +129,7 @@ impl ClientGridEditor {
         );
         let _ = self
             .rev_manager
-            .add_local_revision::<GridRevisionCompact>(&revision)
+            .add_local_revision(&revision, Box::new(GridRevisionCompactor()))
             .await?;
         Ok(())
     }
@@ -241,24 +175,73 @@ impl RevisionCloudService for GridRevisionCloudService {
     }
 }
 
-struct GridRevisionCompact();
-impl RevisionCompact for GridRevisionCompact {
-    fn compact_revisions(user_id: &str, object_id: &str, mut revisions: Vec<Revision>) -> FlowyResult<Revision> {
-        if revisions.is_empty() {
-            return Err(FlowyError::internal().context("Can't compact the empty folder's revisions"));
-        }
+struct GridRevisionCompactor();
+impl RevisionCompactor for GridRevisionCompactor {
+    fn bytes_from_revisions(&self, revisions: Vec<Revision>) -> FlowyResult<Bytes> {
+        let delta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
+        Ok(delta.to_bytes())
+    }
+}
 
-        if revisions.len() == 1 {
-            return Ok(revisions.pop().unwrap());
-        }
+struct GridBlockMetaEditorManager {
+    editor_map: DashMap<String, Arc<ClientGridBlockMetaEditor>>,
+}
 
-        let first_revision = revisions.first().unwrap();
-        let last_revision = revisions.last().unwrap();
+impl GridBlockMetaEditorManager {
+    fn new() -> Self {
+        Self {
+            editor_map: DashMap::new(),
+        }
+    }
 
-        let (base_rev_id, rev_id) = first_revision.pair_rev_id();
-        let md5 = last_revision.md5.clone();
-        let delta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
-        let delta_data = delta.to_bytes();
-        Ok(Revision::new(object_id, base_rev_id, rev_id, delta_data, user_id, md5))
+    pub async fn get_rows(&self, row_orders: RepeatedRowOrder) -> FlowyResult<RepeatedRow> {
+        // let ids = row_orders
+        //     .items
+        //     .into_iter()
+        //     .map(|row_order| row_order.row_id)
+        //     .collect::<Vec<_>>();
+        // let row_metas: Vec<RowMeta> = self.kv_persistence.batch_get(ids)?;
+        //
+        // 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!()
     }
 }

+ 125 - 0
frontend/rust-lib/flowy-grid/src/services/grid_meta_editor.rs

@@ -0,0 +1,125 @@
+use bytes::Bytes;
+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::RowMeta;
+use flowy_sync::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
+use lib_infra::future::FutureResult;
+use lib_infra::uuid;
+use lib_ot::core::PlainTextAttributes;
+use std::sync::Arc;
+use tokio::sync::RwLock;
+
+pub struct ClientGridBlockMetaEditor {
+    user_id: String,
+    block_id: String,
+    meta_pad: Arc<RwLock<GridBlockMetaPad>>,
+    rev_manager: Arc<RevisionManager>,
+}
+
+impl ClientGridBlockMetaEditor {
+    pub async fn new(
+        user_id: &str,
+        token: &str,
+        block_id: String,
+        mut rev_manager: RevisionManager,
+    ) -> FlowyResult<Self> {
+        let cloud = Arc::new(GridBlockMetaRevisionCloudService {
+            token: token.to_owned(),
+        });
+        let block_meta_pad = rev_manager.load::<GridBlockMetaPadBuilder>(cloud).await?;
+        let meta_pad = Arc::new(RwLock::new(block_meta_pad));
+        let rev_manager = Arc::new(rev_manager);
+        let user_id = user_id.to_owned();
+        Ok(Self {
+            user_id,
+            block_id,
+            meta_pad,
+            rev_manager,
+        })
+    }
+
+    pub async fn create_empty_row(&self) -> FlowyResult<()> {
+        let row = RowMeta::new(&uuid(), &self.block_id, vec![]);
+        self.create_row(row).await?;
+        Ok(())
+    }
+
+    async fn create_row(&self, row: RowMeta) -> FlowyResult<()> {
+        // let _ = self.modify(|grid| Ok(grid.create_row(row)?)).await?;
+        // self.cell_map.insert(row.id.clone(), row.clone());
+        // let _ = self.kv_persistence.set(row)?;
+        Ok(())
+    }
+
+    pub async fn delete_rows(&self, ids: Vec<String>) -> FlowyResult<()> {
+        // let _ = self.modify(|grid| Ok(grid.delete_rows(&ids)?)).await?;
+        // let _ = self.kv.batch_delete(ids)?;
+        Ok(())
+    }
+
+    async fn modify<F>(&self, f: F) -> FlowyResult<()>
+    where
+        F: for<'a> FnOnce(&'a mut GridBlockMetaPad) -> FlowyResult<Option<GridBlockMetaChange>>,
+    {
+        let mut write_guard = self.meta_pad.write().await;
+        match f(&mut *write_guard)? {
+            None => {}
+            Some(change) => {
+                let _ = self.apply_change(change).await?;
+            }
+        }
+        Ok(())
+    }
+
+    async fn apply_change(&self, change: GridBlockMetaChange) -> FlowyResult<()> {
+        let GridBlockMetaChange { delta, md5 } = change;
+        let user_id = self.user_id.clone();
+        let (base_rev_id, rev_id) = self.rev_manager.next_rev_id_pair();
+        let delta_data = delta.to_bytes();
+        let revision = Revision::new(
+            &self.rev_manager.object_id,
+            base_rev_id,
+            rev_id,
+            delta_data,
+            &user_id,
+            md5,
+        );
+        let _ = self
+            .rev_manager
+            .add_local_revision(&revision, Box::new(GridBlockMetaRevisionCompactor()))
+            .await?;
+        Ok(())
+    }
+}
+
+struct GridBlockMetaRevisionCloudService {
+    #[allow(dead_code)]
+    token: String,
+}
+
+impl RevisionCloudService for GridBlockMetaRevisionCloudService {
+    #[tracing::instrument(level = "trace", skip(self))]
+    fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult<Vec<Revision>, FlowyError> {
+        FutureResult::new(async move { Ok(vec![]) })
+    }
+}
+
+struct GridBlockMetaPadBuilder();
+impl RevisionObjectBuilder for GridBlockMetaPadBuilder {
+    type Output = GridBlockMetaPad;
+
+    fn build_object(object_id: &str, revisions: Vec<Revision>) -> FlowyResult<Self::Output> {
+        let pad = GridBlockMetaPad::from_revisions(object_id, revisions)?;
+        Ok(pad)
+    }
+}
+
+struct GridBlockMetaRevisionCompactor();
+impl RevisionCompactor for GridBlockMetaRevisionCompactor {
+    fn bytes_from_revisions(&self, revisions: Vec<Revision>) -> FlowyResult<Bytes> {
+        let delta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
+        Ok(delta.to_bytes())
+    }
+}

+ 1 - 0
frontend/rust-lib/flowy-grid/src/services/mod.rs

@@ -3,5 +3,6 @@ mod util;
 pub mod cell_data;
 pub mod grid_builder;
 pub mod grid_editor;
+pub mod grid_meta_editor;
 pub mod kv_persistence;
 pub mod stringify;

+ 6 - 6
frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs

@@ -66,10 +66,10 @@ fn make_view_data_processor(
 ) -> ViewDataProcessorMap {
     let mut map: HashMap<ViewDataType, Arc<dyn ViewDataProcessor + Send + Sync>> = HashMap::new();
 
-    let block_data_impl = BlockManagerViewDataImpl(text_block_manager);
+    let block_data_impl = TextBlockViewDataProcessor(text_block_manager);
     map.insert(block_data_impl.data_type(), Arc::new(block_data_impl));
 
-    let grid_data_impl = GridManagerViewDataImpl(grid_manager);
+    let grid_data_impl = GridViewDataProcessor(grid_manager);
     map.insert(grid_data_impl.data_type(), Arc::new(grid_data_impl));
 
     Arc::new(map)
@@ -133,8 +133,8 @@ impl WSMessageReceiver for FolderWSMessageReceiverImpl {
     }
 }
 
-struct BlockManagerViewDataImpl(Arc<TextBlockManager>);
-impl ViewDataProcessor for BlockManagerViewDataImpl {
+struct TextBlockViewDataProcessor(Arc<TextBlockManager>);
+impl ViewDataProcessor for TextBlockViewDataProcessor {
     fn initialize(&self) -> FutureResult<(), FlowyError> {
         let manager = self.0.clone();
         FutureResult::new(async move { manager.init() })
@@ -186,8 +186,8 @@ impl ViewDataProcessor for BlockManagerViewDataImpl {
     }
 }
 
-struct GridManagerViewDataImpl(Arc<GridManager>);
-impl ViewDataProcessor for GridManagerViewDataImpl {
+struct GridViewDataProcessor(Arc<GridManager>);
+impl ViewDataProcessor for GridViewDataProcessor {
     fn initialize(&self) -> FutureResult<(), FlowyError> {
         FutureResult::new(async { Ok(()) })
     }

+ 235 - 0
frontend/rust-lib/flowy-sync/src/cache/disk/grid_meta_rev_impl.rs

@@ -0,0 +1,235 @@
+use crate::cache::disk::RevisionDiskCache;
+use crate::disk::{RevisionChangeset, RevisionRecord, RevisionState};
+use crate::memory::RevisionMemoryCacheDelegate;
+use bytes::Bytes;
+use diesel::{sql_types::Integer, update, SqliteConnection};
+use flowy_collaboration::{
+    entities::revision::{RevId, RevType, Revision, RevisionRange},
+    util::md5,
+};
+use flowy_database::{
+    impl_sql_integer_expression, insert_or_ignore_into,
+    prelude::*,
+    schema::{grid_meta_rev_table, grid_meta_rev_table::dsl},
+    ConnectionPool,
+};
+use flowy_error::{internal_error, FlowyError, FlowyResult};
+use std::sync::Arc;
+
+pub struct SQLiteGridBlockMetaRevisionPersistence {
+    user_id: String,
+    pub(crate) pool: Arc<ConnectionPool>,
+}
+
+impl RevisionDiskCache for SQLiteGridBlockMetaRevisionPersistence {
+    type Error = FlowyError;
+
+    fn create_revision_records(&self, revision_records: Vec<RevisionRecord>) -> Result<(), Self::Error> {
+        let conn = self.pool.get().map_err(internal_error)?;
+        let _ = GridMetaRevisionSql::create(revision_records, &*conn)?;
+        Ok(())
+    }
+
+    fn read_revision_records(
+        &self,
+        object_id: &str,
+        rev_ids: Option<Vec<i64>>,
+    ) -> Result<Vec<RevisionRecord>, Self::Error> {
+        let conn = self.pool.get().map_err(internal_error)?;
+        let records = GridMetaRevisionSql::read(&self.user_id, object_id, rev_ids, &*conn)?;
+        Ok(records)
+    }
+
+    fn read_revision_records_with_range(
+        &self,
+        object_id: &str,
+        range: &RevisionRange,
+    ) -> Result<Vec<RevisionRecord>, Self::Error> {
+        let conn = &*self.pool.get().map_err(internal_error)?;
+        let revisions = GridMetaRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?;
+        Ok(revisions)
+    }
+
+    fn update_revision_record(&self, changesets: Vec<RevisionChangeset>) -> FlowyResult<()> {
+        let conn = &*self.pool.get().map_err(internal_error)?;
+        let _ = conn.immediate_transaction::<_, FlowyError, _>(|| {
+            for changeset in changesets {
+                let _ = GridMetaRevisionSql::update(changeset, conn)?;
+            }
+            Ok(())
+        })?;
+        Ok(())
+    }
+
+    fn delete_revision_records(&self, object_id: &str, rev_ids: Option<Vec<i64>>) -> Result<(), Self::Error> {
+        let conn = &*self.pool.get().map_err(internal_error)?;
+        let _ = GridMetaRevisionSql::delete(object_id, rev_ids, conn)?;
+        Ok(())
+    }
+
+    fn delete_and_insert_records(
+        &self,
+        object_id: &str,
+        deleted_rev_ids: Option<Vec<i64>>,
+        inserted_records: Vec<RevisionRecord>,
+    ) -> Result<(), Self::Error> {
+        let conn = self.pool.get().map_err(internal_error)?;
+        conn.immediate_transaction::<_, FlowyError, _>(|| {
+            let _ = GridMetaRevisionSql::delete(object_id, deleted_rev_ids, &*conn)?;
+            let _ = GridMetaRevisionSql::create(inserted_records, &*conn)?;
+            Ok(())
+        })
+    }
+}
+
+impl SQLiteGridBlockMetaRevisionPersistence {
+    pub fn new(user_id: &str, pool: Arc<ConnectionPool>) -> Self {
+        Self {
+            user_id: user_id.to_owned(),
+            pool,
+        }
+    }
+}
+
+struct GridMetaRevisionSql();
+impl GridMetaRevisionSql {
+    fn create(revision_records: Vec<RevisionRecord>, conn: &SqliteConnection) -> Result<(), FlowyError> {
+        // Batch insert: https://diesel.rs/guides/all-about-inserts.html
+
+        let records = revision_records
+            .into_iter()
+            .map(|record| {
+                tracing::trace!(
+                    "[GridMetaRevisionSql] create revision: {}:{:?}",
+                    record.revision.object_id,
+                    record.revision.rev_id
+                );
+                let rev_state: GridMetaRevisionState = record.state.into();
+                (
+                    dsl::object_id.eq(record.revision.object_id),
+                    dsl::base_rev_id.eq(record.revision.base_rev_id),
+                    dsl::rev_id.eq(record.revision.rev_id),
+                    dsl::data.eq(record.revision.delta_data),
+                    dsl::state.eq(rev_state),
+                )
+            })
+            .collect::<Vec<_>>();
+
+        let _ = insert_or_ignore_into(dsl::grid_meta_rev_table)
+            .values(&records)
+            .execute(conn)?;
+        Ok(())
+    }
+
+    fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> {
+        let state: GridMetaRevisionState = changeset.state.clone().into();
+        let filter = dsl::grid_meta_rev_table
+            .filter(dsl::rev_id.eq(changeset.rev_id.as_ref()))
+            .filter(dsl::object_id.eq(changeset.object_id));
+        let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?;
+        tracing::debug!(
+            "[GridMetaRevisionSql] update revision:{} state:to {:?}",
+            changeset.rev_id,
+            changeset.state
+        );
+        Ok(())
+    }
+
+    fn read(
+        user_id: &str,
+        object_id: &str,
+        rev_ids: Option<Vec<i64>>,
+        conn: &SqliteConnection,
+    ) -> Result<Vec<RevisionRecord>, FlowyError> {
+        let mut sql = dsl::grid_meta_rev_table
+            .filter(dsl::object_id.eq(object_id))
+            .into_boxed();
+        if let Some(rev_ids) = rev_ids {
+            sql = sql.filter(dsl::rev_id.eq_any(rev_ids));
+        }
+        let rows = sql.order(dsl::rev_id.asc()).load::<GridMetaRevisionTable>(conn)?;
+        let records = rows
+            .into_iter()
+            .map(|row| mk_revision_record_from_table(user_id, row))
+            .collect::<Vec<_>>();
+
+        Ok(records)
+    }
+
+    fn read_with_range(
+        user_id: &str,
+        object_id: &str,
+        range: RevisionRange,
+        conn: &SqliteConnection,
+    ) -> Result<Vec<RevisionRecord>, FlowyError> {
+        let rev_tables = dsl::grid_meta_rev_table
+            .filter(dsl::rev_id.ge(range.start))
+            .filter(dsl::rev_id.le(range.end))
+            .filter(dsl::object_id.eq(object_id))
+            .order(dsl::rev_id.asc())
+            .load::<GridMetaRevisionTable>(conn)?;
+
+        let revisions = rev_tables
+            .into_iter()
+            .map(|table| mk_revision_record_from_table(user_id, table))
+            .collect::<Vec<_>>();
+        Ok(revisions)
+    }
+
+    fn delete(object_id: &str, rev_ids: Option<Vec<i64>>, conn: &SqliteConnection) -> Result<(), FlowyError> {
+        let mut sql = diesel::delete(dsl::grid_meta_rev_table).into_boxed();
+        sql = sql.filter(dsl::object_id.eq(object_id));
+
+        if let Some(rev_ids) = rev_ids {
+            tracing::trace!("[GridMetaRevisionSql] Delete revision: {}:{:?}", object_id, rev_ids);
+            sql = sql.filter(dsl::rev_id.eq_any(rev_ids));
+        }
+
+        let affected_row = sql.execute(conn)?;
+        tracing::trace!("[GridMetaRevisionSql] Delete {} rows", affected_row);
+        Ok(())
+    }
+}
+
+#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
+#[table_name = "grid_meta_rev_table"]
+struct GridMetaRevisionTable {
+    id: i32,
+    object_id: String,
+    base_rev_id: i64,
+    rev_id: i64,
+    data: Vec<u8>,
+    state: GridMetaRevisionState,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)]
+#[repr(i32)]
+#[sql_type = "Integer"]
+pub enum GridMetaRevisionState {
+    Sync = 0,
+    Ack = 1,
+}
+impl_sql_integer_expression!(GridMetaRevisionState);
+impl_rev_state_map!(GridMetaRevisionState);
+impl std::default::Default for GridMetaRevisionState {
+    fn default() -> Self {
+        GridMetaRevisionState::Sync
+    }
+}
+
+fn mk_revision_record_from_table(user_id: &str, table: GridMetaRevisionTable) -> RevisionRecord {
+    let md5 = md5(&table.data);
+    let revision = Revision::new(
+        &table.object_id,
+        table.base_rev_id,
+        table.rev_id,
+        Bytes::from(table.data),
+        user_id,
+        md5,
+    );
+    RevisionRecord {
+        revision,
+        state: table.state.into(),
+        write_to_disk: false,
+    }
+}

+ 14 - 52
frontend/rust-lib/flowy-sync/src/cache/disk/grid_rev_impl.rs

@@ -1,5 +1,6 @@
 use crate::cache::disk::RevisionDiskCache;
 use crate::disk::{RevisionChangeset, RevisionRecord, RevisionState};
+use crate::memory::RevisionMemoryCacheDelegate;
 use bytes::Bytes;
 use diesel::{sql_types::Integer, update, SqliteConnection};
 use flowy_collaboration::{
@@ -23,12 +24,9 @@ pub struct SQLiteGridRevisionPersistence {
 impl RevisionDiskCache for SQLiteGridRevisionPersistence {
     type Error = FlowyError;
 
-    fn create_revision_records(
-        &self,
-        revision_records: Vec<RevisionRecord>,
-        conn: &SqliteConnection,
-    ) -> Result<(), Self::Error> {
-        let _ = GridRevisionSql::create(revision_records, conn)?;
+    fn create_revision_records(&self, revision_records: Vec<RevisionRecord>) -> Result<(), Self::Error> {
+        let conn = self.pool.get().map_err(internal_error)?;
+        let _ = GridRevisionSql::create(revision_records, &*conn)?;
         Ok(())
     }
 
@@ -78,14 +76,14 @@ impl RevisionDiskCache for SQLiteGridRevisionPersistence {
         let conn = self.pool.get().map_err(internal_error)?;
         conn.immediate_transaction::<_, FlowyError, _>(|| {
             let _ = GridRevisionSql::delete(object_id, deleted_rev_ids, &*conn)?;
-            let _ = self.create_revision_records(inserted_records, &*conn)?;
+            let _ = GridRevisionSql::create(inserted_records, &*conn)?;
             Ok(())
         })
     }
 }
 
 impl SQLiteGridRevisionPersistence {
-    pub(crate) fn new(user_id: &str, pool: Arc<ConnectionPool>) -> Self {
+    pub fn new(user_id: &str, pool: Arc<ConnectionPool>) -> Self {
         Self {
             user_id: user_id.to_owned(),
             pool,
@@ -193,13 +191,13 @@ impl GridRevisionSql {
 
 #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
 #[table_name = "grid_rev_table"]
-pub(crate) struct GridRevisionTable {
+struct GridRevisionTable {
     id: i32,
-    pub(crate) object_id: String,
-    pub(crate) base_rev_id: i64,
-    pub(crate) rev_id: i64,
-    pub(crate) data: Vec<u8>,
-    pub(crate) state: GridRevisionState,
+    object_id: String,
+    base_rev_id: i64,
+    rev_id: i64,
+    data: Vec<u8>,
+    state: GridRevisionState,
 }
 
 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)]
@@ -209,6 +207,8 @@ pub enum GridRevisionState {
     Sync = 0,
     Ack = 1,
 }
+impl_sql_integer_expression!(GridRevisionState);
+impl_rev_state_map!(GridRevisionState);
 
 impl std::default::Default for GridRevisionState {
     fn default() -> Self {
@@ -216,44 +216,6 @@ impl std::default::Default for GridRevisionState {
     }
 }
 
-impl std::convert::From<i32> for GridRevisionState {
-    fn from(value: i32) -> Self {
-        match value {
-            0 => GridRevisionState::Sync,
-            1 => GridRevisionState::Ack,
-            o => {
-                tracing::error!("Unsupported rev state {}, fallback to RevState::Local", o);
-                GridRevisionState::Sync
-            }
-        }
-    }
-}
-
-impl GridRevisionState {
-    pub fn value(&self) -> i32 {
-        *self as i32
-    }
-}
-impl_sql_integer_expression!(GridRevisionState);
-
-impl std::convert::From<GridRevisionState> for RevisionState {
-    fn from(s: GridRevisionState) -> Self {
-        match s {
-            GridRevisionState::Sync => RevisionState::Sync,
-            GridRevisionState::Ack => RevisionState::Ack,
-        }
-    }
-}
-
-impl std::convert::From<RevisionState> for GridRevisionState {
-    fn from(s: RevisionState) -> Self {
-        match s {
-            RevisionState::Sync => GridRevisionState::Sync,
-            RevisionState::Ack => GridRevisionState::Ack,
-        }
-    }
-}
-
 fn mk_revision_record_from_table(user_id: &str, table: GridRevisionTable) -> RevisionRecord {
     let md5 = md5(&table.data);
     let revision = Revision::new(

+ 4 - 5
frontend/rust-lib/flowy-sync/src/cache/disk/mod.rs

@@ -1,11 +1,14 @@
 mod folder_rev_impl;
+mod grid_meta_rev_impl;
 mod grid_rev_impl;
 mod text_rev_impl;
 
 pub use folder_rev_impl::*;
+pub use grid_meta_rev_impl::*;
 pub use grid_rev_impl::*;
 pub use text_rev_impl::*;
 
+use crate::memory::RevisionMemoryCacheDelegate;
 use diesel::SqliteConnection;
 use flowy_collaboration::entities::revision::{RevId, Revision, RevisionRange};
 use flowy_error::FlowyResult;
@@ -13,11 +16,7 @@ use std::fmt::Debug;
 
 pub trait RevisionDiskCache: Sync + Send {
     type Error: Debug;
-    fn create_revision_records(
-        &self,
-        revision_records: Vec<RevisionRecord>,
-        conn: &SqliteConnection,
-    ) -> Result<(), Self::Error>;
+    fn create_revision_records(&self, revision_records: Vec<RevisionRecord>) -> Result<(), Self::Error>;
 
     // Read all the records if the rev_ids is None
     fn read_revision_records(

+ 9 - 52
frontend/rust-lib/flowy-sync/src/cache/disk/text_rev_impl.rs

@@ -1,5 +1,6 @@
 use crate::cache::disk::RevisionDiskCache;
 use crate::disk::{RevisionChangeset, RevisionRecord, RevisionState};
+use crate::memory::RevisionMemoryCacheDelegate;
 use bytes::Bytes;
 use diesel::{sql_types::Integer, update, SqliteConnection};
 use flowy_collaboration::{
@@ -23,12 +24,9 @@ pub struct SQLiteTextBlockRevisionPersistence {
 impl RevisionDiskCache for SQLiteTextBlockRevisionPersistence {
     type Error = FlowyError;
 
-    fn create_revision_records(
-        &self,
-        revision_records: Vec<RevisionRecord>,
-        conn: &SqliteConnection,
-    ) -> Result<(), Self::Error> {
-        let _ = TextRevisionSql::create(revision_records, conn)?;
+    fn create_revision_records(&self, revision_records: Vec<RevisionRecord>) -> Result<(), Self::Error> {
+        let conn = self.pool.get().map_err(internal_error)?;
+        let _ = TextRevisionSql::create(revision_records, &*conn)?;
         Ok(())
     }
 
@@ -78,14 +76,14 @@ impl RevisionDiskCache for SQLiteTextBlockRevisionPersistence {
         let conn = self.pool.get().map_err(internal_error)?;
         conn.immediate_transaction::<_, FlowyError, _>(|| {
             let _ = TextRevisionSql::delete(object_id, deleted_rev_ids, &*conn)?;
-            let _ = self.create_revision_records(inserted_records, &*conn)?;
+            let _ = TextRevisionSql::create(inserted_records, &*conn)?;
             Ok(())
         })
     }
 }
 
 impl SQLiteTextBlockRevisionPersistence {
-    pub(crate) fn new(user_id: &str, pool: Arc<ConnectionPool>) -> Self {
+    pub fn new(user_id: &str, pool: Arc<ConnectionPool>) -> Self {
         Self {
             user_id: user_id.to_owned(),
             pool,
@@ -210,6 +208,8 @@ enum TextRevisionState {
     Sync = 0,
     Ack = 1,
 }
+impl_sql_integer_expression!(TextRevisionState);
+impl_rev_state_map!(TextRevisionState);
 
 impl std::default::Default for TextRevisionState {
     fn default() -> Self {
@@ -217,44 +217,6 @@ impl std::default::Default for TextRevisionState {
     }
 }
 
-impl std::convert::From<i32> for TextRevisionState {
-    fn from(value: i32) -> Self {
-        match value {
-            0 => TextRevisionState::Sync,
-            1 => TextRevisionState::Ack,
-            o => {
-                tracing::error!("Unsupported rev state {}, fallback to RevState::Local", o);
-                TextRevisionState::Sync
-            }
-        }
-    }
-}
-
-impl TextRevisionState {
-    pub fn value(&self) -> i32 {
-        *self as i32
-    }
-}
-impl_sql_integer_expression!(TextRevisionState);
-
-impl std::convert::From<TextRevisionState> for RevisionState {
-    fn from(s: TextRevisionState) -> Self {
-        match s {
-            TextRevisionState::Sync => RevisionState::Sync,
-            TextRevisionState::Ack => RevisionState::Ack,
-        }
-    }
-}
-
-impl std::convert::From<RevisionState> for TextRevisionState {
-    fn from(s: RevisionState) -> Self {
-        match s {
-            RevisionState::Sync => TextRevisionState::Sync,
-            RevisionState::Ack => TextRevisionState::Ack,
-        }
-    }
-}
-
 fn mk_revision_record_from_table(user_id: &str, table: RevisionTable) -> RevisionRecord {
     let md5 = md5(&table.data);
     let revision = Revision::new(
@@ -279,6 +241,7 @@ pub enum RevTableType {
     Local = 0,
     Remote = 1,
 }
+impl_sql_integer_expression!(RevTableType);
 
 impl std::default::Default for RevTableType {
     fn default() -> Self {
@@ -298,12 +261,6 @@ impl std::convert::From<i32> for RevTableType {
         }
     }
 }
-impl RevTableType {
-    pub fn value(&self) -> i32 {
-        *self as i32
-    }
-}
-impl_sql_integer_expression!(RevTableType);
 
 impl std::convert::From<RevType> for RevTableType {
     fn from(ty: RevType) -> Self {

+ 30 - 10
frontend/rust-lib/flowy-sync/src/rev_manager.rs

@@ -1,11 +1,13 @@
 use crate::disk::RevisionState;
 use crate::{RevisionPersistence, WSDataProviderDataSource};
+use bytes::Bytes;
 use flowy_collaboration::{
     entities::revision::{RepeatedRevision, Revision, RevisionRange},
     util::{pair_rev_id_from_revisions, RevIdCounter},
 };
 use flowy_error::{FlowyError, FlowyResult};
 use lib_infra::future::FutureResult;
+use lib_ot::core::{Attributes, Delta};
 use std::sync::Arc;
 
 pub trait RevisionCloudService: Send + Sync {
@@ -17,8 +19,26 @@ pub trait RevisionObjectBuilder: Send + Sync {
     fn build_object(object_id: &str, revisions: Vec<Revision>) -> FlowyResult<Self::Output>;
 }
 
-pub trait RevisionCompact: Send + Sync {
-    fn compact_revisions(user_id: &str, object_id: &str, revisions: Vec<Revision>) -> FlowyResult<Revision>;
+pub trait RevisionCompactor: Send + Sync {
+    fn compact(&self, user_id: &str, object_id: &str, mut revisions: Vec<Revision>) -> FlowyResult<Revision> {
+        if revisions.is_empty() {
+            return Err(FlowyError::internal().context("Can't compact the empty folder's revisions"));
+        }
+
+        if revisions.len() == 1 {
+            return Ok(revisions.pop().unwrap());
+        }
+
+        let first_revision = revisions.first().unwrap();
+        let last_revision = revisions.last().unwrap();
+
+        let (base_rev_id, rev_id) = first_revision.pair_rev_id();
+        let md5 = last_revision.md5.clone();
+        let delta_data = self.bytes_from_revisions(revisions)?;
+        Ok(Revision::new(object_id, base_rev_id, rev_id, delta_data, user_id, md5))
+    }
+
+    fn bytes_from_revisions(&self, revisions: Vec<Revision>) -> FlowyResult<Bytes>;
 }
 
 pub struct RevisionManager {
@@ -48,10 +68,9 @@ impl RevisionManager {
         }
     }
 
-    pub async fn load<B, C>(&mut self, cloud: Arc<dyn RevisionCloudService>) -> FlowyResult<B::Output>
+    pub async fn load<B>(&mut self, cloud: Arc<dyn RevisionCloudService>) -> FlowyResult<B::Output>
     where
         B: RevisionObjectBuilder,
-        C: RevisionCompact,
     {
         let (revisions, rev_id) = RevisionLoader {
             object_id: self.object_id.clone(),
@@ -84,15 +103,16 @@ impl RevisionManager {
         Ok(())
     }
 
-    #[tracing::instrument(level = "debug", skip(self, revision))]
-    pub async fn add_local_revision<C>(&self, revision: &Revision) -> Result<(), FlowyError>
-    where
-        C: RevisionCompact,
-    {
+    #[tracing::instrument(level = "debug", skip_all, err)]
+    pub async fn add_local_revision<'a>(
+        &'a self,
+        revision: &Revision,
+        compactor: Box<dyn RevisionCompactor + 'a>,
+    ) -> Result<(), FlowyError> {
         if revision.delta_data.is_empty() {
             return Err(FlowyError::internal().context("Delta data should be empty"));
         }
-        let rev_id = self.rev_persistence.add_sync_revision::<C>(revision).await?;
+        let rev_id = self.rev_persistence.add_sync_revision(revision, compactor).await?;
         self.rev_id_counter.set(rev_id);
         Ok(())
     }

+ 19 - 15
frontend/rust-lib/flowy-sync/src/rev_persistence.rs

@@ -1,9 +1,10 @@
 use crate::cache::{
     disk::{RevisionChangeset, RevisionDiskCache, SQLiteTextBlockRevisionPersistence},
-    memory::{RevisionMemoryCache, RevisionMemoryCacheDelegate},
+    memory::RevisionMemoryCacheDelegate,
 };
 use crate::disk::{RevisionRecord, RevisionState};
-use crate::RevisionCompact;
+use crate::memory::RevisionMemoryCache;
+use crate::RevisionCompactor;
 use flowy_collaboration::entities::revision::{Revision, RevisionRange};
 use flowy_database::ConnectionPool;
 use flowy_error::{internal_error, FlowyError, FlowyResult};
@@ -21,13 +22,17 @@ pub struct RevisionPersistence {
     memory_cache: Arc<RevisionMemoryCache>,
     sync_seq: RwLock<RevisionSyncSequence>,
 }
+
 impl RevisionPersistence {
-    pub fn new(user_id: &str, object_id: &str, pool: Arc<ConnectionPool>) -> RevisionPersistence {
-        let disk_cache = Arc::new(SQLiteTextBlockRevisionPersistence::new(user_id, pool));
-        let memory_cache = Arc::new(RevisionMemoryCache::new(object_id, Arc::new(disk_cache.clone())));
+    pub fn new(
+        user_id: &str,
+        object_id: &str,
+        disk_cache: Arc<dyn RevisionDiskCache<Error = FlowyError>>,
+    ) -> RevisionPersistence {
         let object_id = object_id.to_owned();
         let user_id = user_id.to_owned();
         let sync_seq = RwLock::new(RevisionSyncSequence::new());
+        let memory_cache = Arc::new(RevisionMemoryCache::new(&object_id, Arc::new(disk_cache.clone())));
         Self {
             user_id,
             object_id,
@@ -54,11 +59,12 @@ impl RevisionPersistence {
     }
 
     /// Save the revision to disk and append it to the end of the sync sequence.
-    #[tracing::instrument(level = "trace", skip(self, revision), fields(rev_id, compact_range, object_id=%self.object_id), err)]
-    pub(crate) async fn add_sync_revision<C>(&self, revision: &Revision) -> FlowyResult<i64>
-    where
-        C: RevisionCompact,
-    {
+    #[tracing::instrument(level = "trace", skip_all, fields(rev_id, compact_range, object_id=%self.object_id), err)]
+    pub(crate) async fn add_sync_revision<'a>(
+        &'a self,
+        revision: &'a Revision,
+        compactor: Box<dyn RevisionCompactor + 'a>,
+    ) -> FlowyResult<i64> {
         let result = self.sync_seq.read().await.compact();
         match result {
             None => {
@@ -78,7 +84,7 @@ impl RevisionPersistence {
                 revisions.push(revision.clone());
 
                 // compact multiple revisions into one
-                let compact_revision = C::compact_revisions(&self.user_id, &self.object_id, revisions)?;
+                let compact_revision = compactor.compact(&self.user_id, &self.object_id, revisions)?;
                 let rev_id = compact_revision.rev_id;
                 tracing::Span::current().record("rev_id", &rev_id);
 
@@ -215,17 +221,15 @@ pub fn mk_revision_disk_cache(
     Arc::new(SQLiteTextBlockRevisionPersistence::new(user_id, pool))
 }
 
-impl RevisionMemoryCacheDelegate for Arc<SQLiteTextBlockRevisionPersistence> {
-    #[tracing::instrument(level = "trace", skip(self, records), fields(checkpoint_result), err)]
+impl RevisionMemoryCacheDelegate for Arc<dyn RevisionDiskCache<Error = FlowyError>> {
     fn checkpoint_tick(&self, mut records: Vec<RevisionRecord>) -> FlowyResult<()> {
-        let conn = &*self.pool.get().map_err(internal_error)?;
         records.retain(|record| record.write_to_disk);
         if !records.is_empty() {
             tracing::Span::current().record(
                 "checkpoint_result",
                 &format!("{} records were saved", records.len()).as_str(),
             );
-            let _ = self.create_revision_records(records, conn)?;
+            let _ = self.create_revision_records(records)?;
         }
         Ok(())
     }

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

@@ -1,28 +1,28 @@
 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, RowMetaChangeset, RowOrder};
+use flowy_grid_data_model::entities::{GridBlockMeta, 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;
+pub type GridBlockMetaDelta = PlainTextDelta;
+pub type GridBlockMetaDeltaBuilder = PlainTextDeltaBuilder;
 
 #[derive(Debug, Deserialize, Serialize, Clone)]
-pub struct BlockMetaPad {
+pub struct GridBlockMetaPad {
     block_id: String,
     rows: Vec<Arc<RowMeta>>,
 
     #[serde(skip)]
-    pub(crate) delta: BlockMetaDelta,
+    pub(crate) delta: GridBlockMetaDelta,
 }
 
-impl BlockMetaPad {
-    pub fn from_delta(delta: BlockMetaDelta) -> CollaborateResult<Self> {
+impl GridBlockMetaPad {
+    pub fn from_delta(delta: GridBlockMetaDelta) -> CollaborateResult<Self> {
         let s = delta.to_str()?;
-        let block_meta: BlockMeta = serde_json::from_str(&s).map_err(|e| {
+        let block_meta: GridBlockMeta = serde_json::from_str(&s).map_err(|e| {
             CollaborateError::internal().context(format!("Deserialize delta to block meta failed: {}", e))
         })?;
         let block_id = block_meta.block_id;
@@ -31,25 +31,25 @@ impl BlockMetaPad {
     }
 
     pub fn from_revisions(_grid_id: &str, revisions: Vec<Revision>) -> CollaborateResult<Self> {
-        let block_delta: BlockMetaDelta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
+        let block_delta: GridBlockMetaDelta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
         Self::from_delta(block_delta)
     }
 
-    pub fn add_row(&mut self, row: RowMeta) -> CollaborateResult<Option<BlockMetaChange>> {
+    pub fn add_row(&mut self, row: RowMeta) -> CollaborateResult<Option<GridBlockMetaChange>> {
         self.modify(|rows| {
             rows.push(Arc::new(row));
             Ok(Some(()))
         })
     }
 
-    pub fn delete_rows(&mut self, row_ids: &[String]) -> CollaborateResult<Option<BlockMetaChange>> {
+    pub fn delete_rows(&mut self, row_ids: &[String]) -> CollaborateResult<Option<GridBlockMetaChange>> {
         self.modify(|rows| {
             rows.retain(|row| !row_ids.contains(&row.id));
             Ok(Some(()))
         })
     }
 
-    pub fn update_row(&mut self, changeset: RowMetaChangeset) -> CollaborateResult<Option<BlockMetaChange>> {
+    pub fn update_row(&mut self, changeset: RowMetaChangeset) -> CollaborateResult<Option<GridBlockMetaChange>> {
         let row_id = changeset.row_id.clone();
         self.modify_row(&row_id, |row| {
             let mut is_changed = None;
@@ -74,7 +74,7 @@ impl BlockMetaPad {
         })
     }
 
-    pub fn modify<F>(&mut self, f: F) -> CollaborateResult<Option<BlockMetaChange>>
+    pub fn modify<F>(&mut self, f: F) -> CollaborateResult<Option<GridBlockMetaChange>>
     where
         F: for<'a> FnOnce(&'a mut Vec<Arc<RowMeta>>) -> CollaborateResult<Option<()>>,
     {
@@ -88,14 +88,14 @@ impl BlockMetaPad {
                     None => Ok(None),
                     Some(delta) => {
                         self.delta = self.delta.compose(&delta)?;
-                        Ok(Some(BlockMetaChange { delta, md5: self.md5() }))
+                        Ok(Some(GridBlockMetaChange { delta, md5: self.md5() }))
                     }
                 }
             }
         }
     }
 
-    fn modify_row<F>(&mut self, row_id: &str, f: F) -> CollaborateResult<Option<BlockMetaChange>>
+    fn modify_row<F>(&mut self, row_id: &str, f: F) -> CollaborateResult<Option<GridBlockMetaChange>>
     where
         F: FnOnce(&mut RowMeta) -> CollaborateResult<Option<()>>,
     {
@@ -123,39 +123,39 @@ impl BlockMetaPad {
     }
 }
 
-fn json_from_grid(block_meta: &Arc<BlockMeta>) -> CollaborateResult<String> {
+fn json_from_grid(block_meta: &Arc<GridBlockMeta>) -> CollaborateResult<String> {
     let json = serde_json::to_string(block_meta)
         .map_err(|err| internal_error(format!("Serialize grid to json str failed. {:?}", err)))?;
     Ok(json)
 }
 
-pub struct BlockMetaChange {
-    pub delta: BlockMetaDelta,
+pub struct GridBlockMetaChange {
+    pub delta: GridBlockMetaDelta,
     /// md5: the md5 of the grid after applying the change.
     pub md5: String,
 }
 
-pub fn make_block_meta_delta(block_meta: &BlockMeta) -> BlockMetaDelta {
+pub fn make_block_meta_delta(block_meta: &GridBlockMeta) -> GridBlockMetaDelta {
     let json = serde_json::to_string(&block_meta).unwrap();
     PlainTextDeltaBuilder::new().insert(&json).build()
 }
 
-pub fn make_block_meta_revisions(user_id: &str, block_meta: &BlockMeta) -> RepeatedRevision {
+pub fn make_block_meta_revisions(user_id: &str, block_meta: &GridBlockMeta) -> RepeatedRevision {
     let delta = make_block_meta_delta(block_meta);
     let bytes = delta.to_bytes();
     let revision = Revision::initial_revision(user_id, &block_meta.block_id, bytes);
     revision.into()
 }
 
-impl std::default::Default for BlockMetaPad {
+impl std::default::Default for GridBlockMetaPad {
     fn default() -> Self {
-        let block_meta = BlockMeta {
+        let block_meta = GridBlockMeta {
             block_id: uuid(),
             rows: vec![],
         };
 
         let delta = make_block_meta_delta(&block_meta);
-        BlockMetaPad {
+        GridBlockMetaPad {
             block_id: block_meta.block_id,
             rows: block_meta.rows.into_iter().map(Arc::new).collect::<Vec<_>>(),
             delta,
@@ -165,7 +165,7 @@ impl std::default::Default for BlockMetaPad {
 
 #[cfg(test)]
 mod tests {
-    use crate::client_grid::{BlockMetaDelta, BlockMetaPad};
+    use crate::client_grid::{GridBlockMetaDelta, GridMetaPad};
     use flowy_grid_data_model::entities::{RowMeta, RowMetaChangeset};
     use std::str::FromStr;
 
@@ -241,8 +241,8 @@ mod tests {
         );
     }
 
-    fn test_pad() -> BlockMetaPad {
-        let delta = BlockMetaDelta::from_delta_str(r#"[{"insert":"{\"block_id\":\"1\",\"rows\":[]}"}]"#).unwrap();
-        BlockMetaPad::from_delta(delta).unwrap()
+    fn test_pad() -> GridMetaPad {
+        let delta = GridBlockMetaDelta::from_delta_str(r#"[{"insert":"{\"block_id\":\"1\",\"rows\":[]}"}]"#).unwrap();
+        GridMetaPad::from_delta(delta).unwrap()
     }
 }

+ 116 - 37
shared-lib/flowy-collaboration/src/client_grid/grid_pad.rs

@@ -1,9 +1,13 @@
 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, FieldOrder, Grid, GridMeta, RowMeta, RowOrder};
+use flowy_grid_data_model::entities::{
+    Field, FieldChangeset, FieldOrder, Grid, GridBlock, GridBlockChangeset, GridMeta, RepeatedField,
+    RepeatedFieldOrder, RowMeta, RowOrder,
+};
 use lib_infra::uuid;
 use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
+use std::collections::HashMap;
 use std::sync::Arc;
 
 pub type GridDelta = PlainTextDelta;
@@ -31,13 +35,6 @@ impl GridMetaPad {
         Self::from_delta(grid_delta)
     }
 
-    pub fn create_row(&mut self, row: RowMeta) -> CollaborateResult<Option<GridChange>> {
-        self.modify_grid(|grid| {
-            // grid.rows.push(row);
-            Ok(Some(()))
-        })
-    }
-
     pub fn create_field(&mut self, field: Field) -> CollaborateResult<Option<GridChange>> {
         self.modify_grid(|grid| {
             grid.fields.push(field);
@@ -45,13 +42,6 @@ impl GridMetaPad {
         })
     }
 
-    pub fn delete_rows(&mut self, row_ids: &[String]) -> CollaborateResult<Option<GridChange>> {
-        self.modify_grid(|grid| {
-            // grid.rows.retain(|row| !row_ids.contains(&row.id));
-            Ok(Some(()))
-        })
-    }
-
     pub fn delete_field(&mut self, field_id: &str) -> CollaborateResult<Option<GridChange>> {
         self.modify_grid(|grid| match grid.fields.iter().position(|field| field.id == field_id) {
             None => Ok(None),
@@ -62,30 +52,93 @@ impl GridMetaPad {
         })
     }
 
-    pub fn md5(&self) -> String {
-        md5(&self.delta.to_bytes())
-    }
-
-    pub fn grid_data(&self) -> Grid {
-        let field_orders = self
+    pub fn get_fields(&self, field_orders: RepeatedFieldOrder) -> CollaborateResult<RepeatedField> {
+        let field_by_field_id = self
             .grid_meta
             .fields
             .iter()
-            .map(FieldOrder::from)
-            .collect::<Vec<FieldOrder>>();
-
-        // let row_orders = self
-        //     .grid_meta
-        //     .rows
-        //     .iter()
-        //     .map(RowOrder::from)
-        //     .collect::<Vec<RowOrder>>();
-
-        Grid {
-            id: "".to_string(),
-            field_orders,
-            row_orders: vec![],
-        }
+            .map(|field| (&field.id, field))
+            .collect::<HashMap<&String, &Field>>();
+
+        let fields = field_orders
+            .iter()
+            .flat_map(|field_order| match field_by_field_id.get(&field_order.field_id) {
+                None => {
+                    tracing::error!("Can't find the field with {}", field_order.field_id);
+                    None
+                }
+                Some(field) => Some((*field).clone()),
+            })
+            .collect::<Vec<Field>>();
+        Ok(fields.into())
+    }
+
+    pub fn update_field(&mut self, change: FieldChangeset) -> CollaborateResult<Option<GridChange>> {
+        let field_id = change.field_id.clone();
+        self.modify_field(&field_id, |field| {
+            let mut is_changed = None;
+            if let Some(name) = change.name {
+                field.name = name;
+                is_changed = Some(())
+            }
+
+            if let Some(desc) = change.desc {
+                field.desc = desc;
+                is_changed = Some(())
+            }
+
+            if let Some(field_type) = change.field_type {
+                field.field_type = field_type;
+                is_changed = Some(())
+            }
+
+            if let Some(frozen) = change.frozen {
+                field.frozen = frozen;
+                is_changed = Some(())
+            }
+
+            if let Some(visibility) = change.visibility {
+                field.visibility = visibility;
+                is_changed = Some(())
+            }
+
+            if let Some(width) = change.width {
+                field.width = width;
+                is_changed = Some(())
+            }
+
+            if let Some(type_options) = change.type_options {
+                field.type_options = type_options;
+                is_changed = Some(())
+            }
+
+            Ok(is_changed)
+        })
+    }
+
+    pub fn create_block(&mut self, block: GridBlock) -> CollaborateResult<Option<GridChange>> {
+        self.modify_grid(|grid| {
+            grid.blocks.push(block);
+            Ok(Some(()))
+        })
+    }
+
+    pub fn update_block(&mut self, change: GridBlockChangeset) -> CollaborateResult<Option<GridChange>> {
+        let block_id = change.block_id.clone();
+        self.modify_block(&block_id, |block| {
+            let mut is_changed = None;
+
+            if let Some(row_count) = change.row_count {
+                block.row_count = row_count;
+                is_changed = Some(());
+            }
+
+            Ok(is_changed)
+        })
+    }
+
+    pub fn md5(&self) -> String {
+        md5(&self.delta.to_bytes())
     }
 
     pub fn delta_str(&self) -> String {
@@ -96,7 +149,7 @@ impl GridMetaPad {
         &self.grid_meta.fields
     }
 
-    pub fn modify_grid<F>(&mut self, f: F) -> CollaborateResult<Option<GridChange>>
+    fn modify_grid<F>(&mut self, f: F) -> CollaborateResult<Option<GridChange>>
     where
         F: FnOnce(&mut GridMeta) -> CollaborateResult<Option<()>>,
     {
@@ -116,6 +169,32 @@ impl GridMetaPad {
             }
         }
     }
+
+    pub fn modify_block<F>(&mut self, block_id: &str, f: F) -> CollaborateResult<Option<GridChange>>
+    where
+        F: FnOnce(&mut GridBlock) -> CollaborateResult<Option<()>>,
+    {
+        self.modify_grid(|grid| match grid.blocks.iter().position(|block| block.id == block_id) {
+            None => {
+                tracing::warn!("[GridMetaPad]: Can't find any block with id: {}", block_id);
+                Ok(None)
+            }
+            Some(index) => f(&mut grid.blocks[index]),
+        })
+    }
+
+    pub fn modify_field<F>(&mut self, field_id: &str, f: F) -> CollaborateResult<Option<GridChange>>
+    where
+        F: FnOnce(&mut Field) -> CollaborateResult<Option<()>>,
+    {
+        self.modify_grid(|grid| match grid.fields.iter().position(|field| field.id == field_id) {
+            None => {
+                tracing::warn!("[GridMetaPad]: Can't find any field with id: {}", field_id);
+                Ok(None)
+            }
+            Some(index) => f(&mut grid.fields[index]),
+        })
+    }
 }
 
 fn json_from_grid(grid: &Arc<GridMeta>) -> CollaborateResult<String> {

+ 0 - 3
shared-lib/flowy-grid-data-model/src/entities/grid.rs

@@ -2,9 +2,6 @@ use crate::entities::{Field, RowMeta};
 use flowy_derive::ProtoBuf;
 use std::collections::HashMap;
 
-pub const DEFAULT_ROW_HEIGHT: i32 = 36;
-pub const DEFAULT_FIELD_WIDTH: i32 = 150;
-
 #[derive(Debug, Clone, Default, ProtoBuf)]
 pub struct Grid {
     #[pb(index = 1)]

+ 38 - 5
shared-lib/flowy-grid-data-model/src/entities/meta.rs

@@ -15,11 +15,11 @@ pub struct GridMeta {
     pub fields: Vec<Field>,
 
     #[pb(index = 3)]
-    pub blocks: Vec<Block>,
+    pub blocks: Vec<GridBlock>,
 }
 
 #[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct Block {
+pub struct GridBlock {
     #[pb(index = 1)]
     pub id: String,
 
@@ -30,8 +30,14 @@ pub struct Block {
     pub row_count: i32,
 }
 
+pub struct GridBlockChangeset {
+    pub block_id: String,
+    pub start_row_index: Option<i32>,
+    pub row_count: Option<i32>,
+}
+
 #[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct BlockMeta {
+pub struct GridBlockMeta {
     #[pb(index = 1)]
     pub block_id: String,
 
@@ -81,6 +87,33 @@ impl Field {
     }
 }
 
+#[derive(Debug, Clone, Default, ProtoBuf)]
+pub struct FieldChangeset {
+    #[pb(index = 1)]
+    pub field_id: String,
+
+    #[pb(index = 2, one_of)]
+    pub name: Option<String>,
+
+    #[pb(index = 3, one_of)]
+    pub desc: Option<String>,
+
+    #[pb(index = 4, one_of)]
+    pub field_type: Option<FieldType>,
+
+    #[pb(index = 5, one_of)]
+    pub frozen: Option<bool>,
+
+    #[pb(index = 6, one_of)]
+    pub visibility: Option<bool>,
+
+    #[pb(index = 7, one_of)]
+    pub width: Option<i32>,
+
+    #[pb(index = 8, one_of)]
+    pub type_options: Option<AnyData>,
+}
+
 #[derive(Debug, Default, ProtoBuf)]
 pub struct RepeatedField {
     #[pb(index = 1)]
@@ -191,7 +224,7 @@ pub struct RowMeta {
 }
 
 impl RowMeta {
-    pub fn new(id: &str, grid_id: &str, cells: Vec<CellMeta>) -> Self {
+    pub fn new(id: &str, block_id: &str, cells: Vec<CellMeta>) -> Self {
         let cell_by_field_id = cells
             .into_iter()
             .map(|cell| (cell.id.clone(), cell))
@@ -199,7 +232,7 @@ impl RowMeta {
 
         Self {
             id: id.to_owned(),
-            block_id: grid_id.to_owned(),
+            block_id: block_id.to_owned(),
             cell_by_field_id,
             height: DEFAULT_ROW_HEIGHT,
             visibility: true,

+ 731 - 82
shared-lib/flowy-grid-data-model/src/protobuf/model/meta.rs

@@ -28,7 +28,7 @@ pub struct GridMeta {
     // message fields
     pub grid_id: ::std::string::String,
     pub fields: ::protobuf::RepeatedField<Field>,
-    pub blocks: ::protobuf::RepeatedField<Block>,
+    pub blocks: ::protobuf::RepeatedField<GridBlock>,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -96,10 +96,10 @@ impl GridMeta {
         ::std::mem::replace(&mut self.fields, ::protobuf::RepeatedField::new())
     }
 
-    // repeated .Block blocks = 3;
+    // repeated .GridBlock blocks = 3;
 
 
-    pub fn get_blocks(&self) -> &[Block] {
+    pub fn get_blocks(&self) -> &[GridBlock] {
         &self.blocks
     }
     pub fn clear_blocks(&mut self) {
@@ -107,17 +107,17 @@ impl GridMeta {
     }
 
     // Param is passed by value, moved
-    pub fn set_blocks(&mut self, v: ::protobuf::RepeatedField<Block>) {
+    pub fn set_blocks(&mut self, v: ::protobuf::RepeatedField<GridBlock>) {
         self.blocks = v;
     }
 
     // Mutable pointer to the field.
-    pub fn mut_blocks(&mut self) -> &mut ::protobuf::RepeatedField<Block> {
+    pub fn mut_blocks(&mut self) -> &mut ::protobuf::RepeatedField<GridBlock> {
         &mut self.blocks
     }
 
     // Take field
-    pub fn take_blocks(&mut self) -> ::protobuf::RepeatedField<Block> {
+    pub fn take_blocks(&mut self) -> ::protobuf::RepeatedField<GridBlock> {
         ::std::mem::replace(&mut self.blocks, ::protobuf::RepeatedField::new())
     }
 }
@@ -240,7 +240,7 @@ impl ::protobuf::Message for GridMeta {
                 |m: &GridMeta| { &m.fields },
                 |m: &mut GridMeta| { &mut m.fields },
             ));
-            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<Block>>(
+            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<GridBlock>>(
                 "blocks",
                 |m: &GridMeta| { &m.blocks },
                 |m: &mut GridMeta| { &mut m.blocks },
@@ -281,7 +281,7 @@ impl ::protobuf::reflect::ProtobufValue for GridMeta {
 }
 
 #[derive(PartialEq,Clone,Default)]
-pub struct Block {
+pub struct GridBlock {
     // message fields
     pub id: ::std::string::String,
     pub start_row_index: i32,
@@ -291,14 +291,14 @@ pub struct Block {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a Block {
-    fn default() -> &'a Block {
-        <Block as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a GridBlock {
+    fn default() -> &'a GridBlock {
+        <GridBlock as ::protobuf::Message>::default_instance()
     }
 }
 
-impl Block {
-    pub fn new() -> Block {
+impl GridBlock {
+    pub fn new() -> GridBlock {
         ::std::default::Default::default()
     }
 
@@ -359,7 +359,7 @@ impl Block {
     }
 }
 
-impl ::protobuf::Message for Block {
+impl ::protobuf::Message for GridBlock {
     fn is_initialized(&self) -> bool {
         true
     }
@@ -451,8 +451,8 @@ impl ::protobuf::Message for Block {
         Self::descriptor_static()
     }
 
-    fn new() -> Block {
-        Block::new()
+    fn new() -> GridBlock {
+        GridBlock::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -461,34 +461,34 @@ impl ::protobuf::Message for Block {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "id",
-                |m: &Block| { &m.id },
-                |m: &mut Block| { &mut m.id },
+                |m: &GridBlock| { &m.id },
+                |m: &mut GridBlock| { &mut m.id },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt32>(
                 "start_row_index",
-                |m: &Block| { &m.start_row_index },
-                |m: &mut Block| { &mut m.start_row_index },
+                |m: &GridBlock| { &m.start_row_index },
+                |m: &mut GridBlock| { &mut m.start_row_index },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt32>(
                 "row_count",
-                |m: &Block| { &m.row_count },
-                |m: &mut Block| { &mut m.row_count },
+                |m: &GridBlock| { &m.row_count },
+                |m: &mut GridBlock| { &mut m.row_count },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<Block>(
-                "Block",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<GridBlock>(
+                "GridBlock",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static Block {
-        static instance: ::protobuf::rt::LazyV2<Block> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(Block::new)
+    fn default_instance() -> &'static GridBlock {
+        static instance: ::protobuf::rt::LazyV2<GridBlock> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(GridBlock::new)
     }
 }
 
-impl ::protobuf::Clear for Block {
+impl ::protobuf::Clear for GridBlock {
     fn clear(&mut self) {
         self.id.clear();
         self.start_row_index = 0;
@@ -497,20 +497,20 @@ impl ::protobuf::Clear for Block {
     }
 }
 
-impl ::std::fmt::Debug for Block {
+impl ::std::fmt::Debug for GridBlock {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for Block {
+impl ::protobuf::reflect::ProtobufValue for GridBlock {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
 }
 
 #[derive(PartialEq,Clone,Default)]
-pub struct BlockMeta {
+pub struct GridBlockMeta {
     // message fields
     pub block_id: ::std::string::String,
     pub rows: ::protobuf::RepeatedField<RowMeta>,
@@ -519,14 +519,14 @@ pub struct BlockMeta {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a BlockMeta {
-    fn default() -> &'a BlockMeta {
-        <BlockMeta as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a GridBlockMeta {
+    fn default() -> &'a GridBlockMeta {
+        <GridBlockMeta as ::protobuf::Message>::default_instance()
     }
 }
 
-impl BlockMeta {
-    pub fn new() -> BlockMeta {
+impl GridBlockMeta {
+    pub fn new() -> GridBlockMeta {
         ::std::default::Default::default()
     }
 
@@ -582,7 +582,7 @@ impl BlockMeta {
     }
 }
 
-impl ::protobuf::Message for BlockMeta {
+impl ::protobuf::Message for GridBlockMeta {
     fn is_initialized(&self) -> bool {
         for v in &self.rows {
             if !v.is_initialized() {
@@ -665,8 +665,8 @@ impl ::protobuf::Message for BlockMeta {
         Self::descriptor_static()
     }
 
-    fn new() -> BlockMeta {
-        BlockMeta::new()
+    fn new() -> GridBlockMeta {
+        GridBlockMeta::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -675,29 +675,29 @@ impl ::protobuf::Message for BlockMeta {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "block_id",
-                |m: &BlockMeta| { &m.block_id },
-                |m: &mut BlockMeta| { &mut m.block_id },
+                |m: &GridBlockMeta| { &m.block_id },
+                |m: &mut GridBlockMeta| { &mut m.block_id },
             ));
             fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<RowMeta>>(
                 "rows",
-                |m: &BlockMeta| { &m.rows },
-                |m: &mut BlockMeta| { &mut m.rows },
+                |m: &GridBlockMeta| { &m.rows },
+                |m: &mut GridBlockMeta| { &mut m.rows },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<BlockMeta>(
-                "BlockMeta",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<GridBlockMeta>(
+                "GridBlockMeta",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static BlockMeta {
-        static instance: ::protobuf::rt::LazyV2<BlockMeta> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(BlockMeta::new)
+    fn default_instance() -> &'static GridBlockMeta {
+        static instance: ::protobuf::rt::LazyV2<GridBlockMeta> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(GridBlockMeta::new)
     }
 }
 
-impl ::protobuf::Clear for BlockMeta {
+impl ::protobuf::Clear for GridBlockMeta {
     fn clear(&mut self) {
         self.block_id.clear();
         self.rows.clear();
@@ -705,13 +705,13 @@ impl ::protobuf::Clear for BlockMeta {
     }
 }
 
-impl ::std::fmt::Debug for BlockMeta {
+impl ::std::fmt::Debug for GridBlockMeta {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for BlockMeta {
+impl ::protobuf::reflect::ProtobufValue for GridBlockMeta {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
@@ -1319,6 +1319,645 @@ impl ::protobuf::reflect::ProtobufValue for RepeatedField {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct FieldChangeset {
+    // message fields
+    pub field_id: ::std::string::String,
+    // message oneof groups
+    pub one_of_name: ::std::option::Option<FieldChangeset_oneof_one_of_name>,
+    pub one_of_desc: ::std::option::Option<FieldChangeset_oneof_one_of_desc>,
+    pub one_of_field_type: ::std::option::Option<FieldChangeset_oneof_one_of_field_type>,
+    pub one_of_frozen: ::std::option::Option<FieldChangeset_oneof_one_of_frozen>,
+    pub one_of_visibility: ::std::option::Option<FieldChangeset_oneof_one_of_visibility>,
+    pub one_of_width: ::std::option::Option<FieldChangeset_oneof_one_of_width>,
+    pub one_of_type_options: ::std::option::Option<FieldChangeset_oneof_one_of_type_options>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a FieldChangeset {
+    fn default() -> &'a FieldChangeset {
+        <FieldChangeset as ::protobuf::Message>::default_instance()
+    }
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum FieldChangeset_oneof_one_of_name {
+    name(::std::string::String),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum FieldChangeset_oneof_one_of_desc {
+    desc(::std::string::String),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum FieldChangeset_oneof_one_of_field_type {
+    field_type(FieldType),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum FieldChangeset_oneof_one_of_frozen {
+    frozen(bool),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum FieldChangeset_oneof_one_of_visibility {
+    visibility(bool),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum FieldChangeset_oneof_one_of_width {
+    width(i32),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum FieldChangeset_oneof_one_of_type_options {
+    type_options(AnyData),
+}
+
+impl FieldChangeset {
+    pub fn new() -> FieldChangeset {
+        ::std::default::Default::default()
+    }
+
+    // string field_id = 1;
+
+
+    pub fn get_field_id(&self) -> &str {
+        &self.field_id
+    }
+    pub fn clear_field_id(&mut self) {
+        self.field_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_field_id(&mut self, v: ::std::string::String) {
+        self.field_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_field_id(&mut self) -> &mut ::std::string::String {
+        &mut self.field_id
+    }
+
+    // Take field
+    pub fn take_field_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.field_id, ::std::string::String::new())
+    }
+
+    // string name = 2;
+
+
+    pub fn get_name(&self) -> &str {
+        match self.one_of_name {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_name::name(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_name(&mut self) {
+        self.one_of_name = ::std::option::Option::None;
+    }
+
+    pub fn has_name(&self) -> bool {
+        match self.one_of_name {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_name::name(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_name(&mut self, v: ::std::string::String) {
+        self.one_of_name = ::std::option::Option::Some(FieldChangeset_oneof_one_of_name::name(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_name(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(FieldChangeset_oneof_one_of_name::name(_)) = self.one_of_name {
+        } else {
+            self.one_of_name = ::std::option::Option::Some(FieldChangeset_oneof_one_of_name::name(::std::string::String::new()));
+        }
+        match self.one_of_name {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_name::name(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_name(&mut self) -> ::std::string::String {
+        if self.has_name() {
+            match self.one_of_name.take() {
+                ::std::option::Option::Some(FieldChangeset_oneof_one_of_name::name(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+
+    // string desc = 3;
+
+
+    pub fn get_desc(&self) -> &str {
+        match self.one_of_desc {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_desc::desc(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_desc(&mut self) {
+        self.one_of_desc = ::std::option::Option::None;
+    }
+
+    pub fn has_desc(&self) -> bool {
+        match self.one_of_desc {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_desc::desc(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_desc(&mut self, v: ::std::string::String) {
+        self.one_of_desc = ::std::option::Option::Some(FieldChangeset_oneof_one_of_desc::desc(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_desc(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(FieldChangeset_oneof_one_of_desc::desc(_)) = self.one_of_desc {
+        } else {
+            self.one_of_desc = ::std::option::Option::Some(FieldChangeset_oneof_one_of_desc::desc(::std::string::String::new()));
+        }
+        match self.one_of_desc {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_desc::desc(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_desc(&mut self) -> ::std::string::String {
+        if self.has_desc() {
+            match self.one_of_desc.take() {
+                ::std::option::Option::Some(FieldChangeset_oneof_one_of_desc::desc(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+
+    // .FieldType field_type = 4;
+
+
+    pub fn get_field_type(&self) -> FieldType {
+        match self.one_of_field_type {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_field_type::field_type(v)) => v,
+            _ => FieldType::RichText,
+        }
+    }
+    pub fn clear_field_type(&mut self) {
+        self.one_of_field_type = ::std::option::Option::None;
+    }
+
+    pub fn has_field_type(&self) -> bool {
+        match self.one_of_field_type {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_field_type::field_type(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_field_type(&mut self, v: FieldType) {
+        self.one_of_field_type = ::std::option::Option::Some(FieldChangeset_oneof_one_of_field_type::field_type(v))
+    }
+
+    // bool frozen = 5;
+
+
+    pub fn get_frozen(&self) -> bool {
+        match self.one_of_frozen {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_frozen::frozen(v)) => v,
+            _ => false,
+        }
+    }
+    pub fn clear_frozen(&mut self) {
+        self.one_of_frozen = ::std::option::Option::None;
+    }
+
+    pub fn has_frozen(&self) -> bool {
+        match self.one_of_frozen {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_frozen::frozen(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_frozen(&mut self, v: bool) {
+        self.one_of_frozen = ::std::option::Option::Some(FieldChangeset_oneof_one_of_frozen::frozen(v))
+    }
+
+    // bool visibility = 6;
+
+
+    pub fn get_visibility(&self) -> bool {
+        match self.one_of_visibility {
+            ::std::option::Option::Some(FieldChangeset_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(FieldChangeset_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(FieldChangeset_oneof_one_of_visibility::visibility(v))
+    }
+
+    // int32 width = 7;
+
+
+    pub fn get_width(&self) -> i32 {
+        match self.one_of_width {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_width::width(v)) => v,
+            _ => 0,
+        }
+    }
+    pub fn clear_width(&mut self) {
+        self.one_of_width = ::std::option::Option::None;
+    }
+
+    pub fn has_width(&self) -> bool {
+        match self.one_of_width {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_width::width(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_width(&mut self, v: i32) {
+        self.one_of_width = ::std::option::Option::Some(FieldChangeset_oneof_one_of_width::width(v))
+    }
+
+    // .AnyData type_options = 8;
+
+
+    pub fn get_type_options(&self) -> &AnyData {
+        match self.one_of_type_options {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(ref v)) => v,
+            _ => <AnyData as ::protobuf::Message>::default_instance(),
+        }
+    }
+    pub fn clear_type_options(&mut self) {
+        self.one_of_type_options = ::std::option::Option::None;
+    }
+
+    pub fn has_type_options(&self) -> bool {
+        match self.one_of_type_options {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_type_options(&mut self, v: AnyData) {
+        self.one_of_type_options = ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_type_options(&mut self) -> &mut AnyData {
+        if let ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(_)) = self.one_of_type_options {
+        } else {
+            self.one_of_type_options = ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(AnyData::new()));
+        }
+        match self.one_of_type_options {
+            ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_type_options(&mut self) -> AnyData {
+        if self.has_type_options() {
+            match self.one_of_type_options.take() {
+                ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            AnyData::new()
+        }
+    }
+}
+
+impl ::protobuf::Message for FieldChangeset {
+    fn is_initialized(&self) -> bool {
+        if let Some(FieldChangeset_oneof_one_of_type_options::type_options(ref v)) = self.one_of_type_options {
+            if !v.is_initialized() {
+                return false;
+            }
+        }
+        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.field_id)?;
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_name = ::std::option::Option::Some(FieldChangeset_oneof_one_of_name::name(is.read_string()?));
+                },
+                3 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_desc = ::std::option::Option::Some(FieldChangeset_oneof_one_of_desc::desc(is.read_string()?));
+                },
+                4 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_field_type = ::std::option::Option::Some(FieldChangeset_oneof_one_of_field_type::field_type(is.read_enum()?));
+                },
+                5 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_frozen = ::std::option::Option::Some(FieldChangeset_oneof_one_of_frozen::frozen(is.read_bool()?));
+                },
+                6 => {
+                    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(FieldChangeset_oneof_one_of_visibility::visibility(is.read_bool()?));
+                },
+                7 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_width = ::std::option::Option::Some(FieldChangeset_oneof_one_of_width::width(is.read_int32()?));
+                },
+                8 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_type_options = ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(is.read_message()?));
+                },
+                _ => {
+                    ::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.field_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.field_id);
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_name {
+            match v {
+                &FieldChangeset_oneof_one_of_name::name(ref v) => {
+                    my_size += ::protobuf::rt::string_size(2, &v);
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_desc {
+            match v {
+                &FieldChangeset_oneof_one_of_desc::desc(ref v) => {
+                    my_size += ::protobuf::rt::string_size(3, &v);
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_field_type {
+            match v {
+                &FieldChangeset_oneof_one_of_field_type::field_type(v) => {
+                    my_size += ::protobuf::rt::enum_size(4, v);
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_frozen {
+            match v {
+                &FieldChangeset_oneof_one_of_frozen::frozen(v) => {
+                    my_size += 2;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_visibility {
+            match v {
+                &FieldChangeset_oneof_one_of_visibility::visibility(v) => {
+                    my_size += 2;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_width {
+            match v {
+                &FieldChangeset_oneof_one_of_width::width(v) => {
+                    my_size += ::protobuf::rt::value_size(7, v, ::protobuf::wire_format::WireTypeVarint);
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_type_options {
+            match v {
+                &FieldChangeset_oneof_one_of_type_options::type_options(ref v) => {
+                    let len = v.compute_size();
+                    my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+                },
+            };
+        }
+        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.field_id.is_empty() {
+            os.write_string(1, &self.field_id)?;
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_name {
+            match v {
+                &FieldChangeset_oneof_one_of_name::name(ref v) => {
+                    os.write_string(2, v)?;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_desc {
+            match v {
+                &FieldChangeset_oneof_one_of_desc::desc(ref v) => {
+                    os.write_string(3, v)?;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_field_type {
+            match v {
+                &FieldChangeset_oneof_one_of_field_type::field_type(v) => {
+                    os.write_enum(4, ::protobuf::ProtobufEnum::value(&v))?;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_frozen {
+            match v {
+                &FieldChangeset_oneof_one_of_frozen::frozen(v) => {
+                    os.write_bool(5, v)?;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_visibility {
+            match v {
+                &FieldChangeset_oneof_one_of_visibility::visibility(v) => {
+                    os.write_bool(6, v)?;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_width {
+            match v {
+                &FieldChangeset_oneof_one_of_width::width(v) => {
+                    os.write_int32(7, v)?;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_type_options {
+            match v {
+                &FieldChangeset_oneof_one_of_type_options::type_options(ref v) => {
+                    os.write_tag(8, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+                    os.write_raw_varint32(v.get_cached_size())?;
+                    v.write_to_with_cached_sizes(os)?;
+                },
+            };
+        }
+        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() -> FieldChangeset {
+        FieldChangeset::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>(
+                "field_id",
+                |m: &FieldChangeset| { &m.field_id },
+                |m: &mut FieldChangeset| { &mut m.field_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "name",
+                FieldChangeset::has_name,
+                FieldChangeset::get_name,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "desc",
+                FieldChangeset::has_desc,
+                FieldChangeset::get_desc,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_enum_accessor::<_, FieldType>(
+                "field_type",
+                FieldChangeset::has_field_type,
+                FieldChangeset::get_field_type,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_bool_accessor::<_>(
+                "frozen",
+                FieldChangeset::has_frozen,
+                FieldChangeset::get_frozen,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_bool_accessor::<_>(
+                "visibility",
+                FieldChangeset::has_visibility,
+                FieldChangeset::get_visibility,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_i32_accessor::<_>(
+                "width",
+                FieldChangeset::has_width,
+                FieldChangeset::get_width,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_message_accessor::<_, AnyData>(
+                "type_options",
+                FieldChangeset::has_type_options,
+                FieldChangeset::get_type_options,
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<FieldChangeset>(
+                "FieldChangeset",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static FieldChangeset {
+        static instance: ::protobuf::rt::LazyV2<FieldChangeset> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(FieldChangeset::new)
+    }
+}
+
+impl ::protobuf::Clear for FieldChangeset {
+    fn clear(&mut self) {
+        self.field_id.clear();
+        self.one_of_name = ::std::option::Option::None;
+        self.one_of_desc = ::std::option::Option::None;
+        self.one_of_field_type = ::std::option::Option::None;
+        self.one_of_frozen = ::std::option::Option::None;
+        self.one_of_visibility = ::std::option::Option::None;
+        self.one_of_width = ::std::option::Option::None;
+        self.one_of_type_options = ::std::option::Option::None;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for FieldChangeset {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for FieldChangeset {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(PartialEq,Clone,Default)]
 pub struct AnyData {
     // message fields
@@ -2537,43 +3176,53 @@ impl ::protobuf::reflect::ProtobufValue for FieldType {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\nmeta.proto\"c\n\x08GridMeta\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
+    \n\nmeta.proto\"g\n\x08GridMeta\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
     \x06gridId\x12\x1e\n\x06fields\x18\x02\x20\x03(\x0b2\x06.FieldR\x06field\
-    s\x12\x1e\n\x06blocks\x18\x03\x20\x03(\x0b2\x06.BlockR\x06blocks\"\\\n\
-    \x05Block\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12&\n\x0fstart_row_\
-    index\x18\x02\x20\x01(\x05R\rstartRowIndex\x12\x1b\n\trow_count\x18\x03\
-    \x20\x01(\x05R\x08rowCount\"D\n\tBlockMeta\x12\x19\n\x08block_id\x18\x01\
-    \x20\x01(\tR\x07blockId\x12\x1c\n\x04rows\x18\x02\x20\x03(\x0b2\x08.RowM\
-    etaR\x04rows\"\xe5\x01\n\x05Field\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\
-    \x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x12\n\x04desc\
+    s\x12\"\n\x06blocks\x18\x03\x20\x03(\x0b2\n.GridBlockR\x06blocks\"`\n\tG\
+    ridBlock\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12&\n\x0fstart_row_i\
+    ndex\x18\x02\x20\x01(\x05R\rstartRowIndex\x12\x1b\n\trow_count\x18\x03\
+    \x20\x01(\x05R\x08rowCount\"H\n\rGridBlockMeta\x12\x19\n\x08block_id\x18\
+    \x01\x20\x01(\tR\x07blockId\x12\x1c\n\x04rows\x18\x02\x20\x03(\x0b2\x08.\
+    RowMetaR\x04rows\"\xe5\x01\n\x05Field\x12\x0e\n\x02id\x18\x01\x20\x01(\t\
+    R\x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x12\n\x04desc\
     \x18\x03\x20\x01(\tR\x04desc\x12)\n\nfield_type\x18\x04\x20\x01(\x0e2\n.\
     FieldTypeR\tfieldType\x12\x16\n\x06frozen\x18\x05\x20\x01(\x08R\x06froze\
     n\x12\x1e\n\nvisibility\x18\x06\x20\x01(\x08R\nvisibility\x12\x14\n\x05w\
     idth\x18\x07\x20\x01(\x05R\x05width\x12+\n\x0ctype_options\x18\x08\x20\
     \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\"\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\"\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\
+    items\x18\x01\x20\x03(\x0b2\x06.FieldR\x05items\"\x87\x03\n\x0eFieldChan\
+    geset\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x14\n\x04\
+    name\x18\x02\x20\x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\x03\x20\x01(\t\
+    H\x01R\x04desc\x12+\n\nfield_type\x18\x04\x20\x01(\x0e2\n.FieldTypeH\x02\
+    R\tfieldType\x12\x18\n\x06frozen\x18\x05\x20\x01(\x08H\x03R\x06frozen\
+    \x12\x20\n\nvisibility\x18\x06\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\
+    \x05width\x18\x07\x20\x01(\x05H\x05R\x05width\x12-\n\x0ctype_options\x18\
+    \x08\x20\x01(\x0b2\x08.AnyDataH\x06R\x0btypeOptionsB\r\n\x0bone_of_nameB\
+    \r\n\x0bone_of_descB\x13\n\x11one_of_field_typeB\x0f\n\rone_of_frozenB\
+    \x13\n\x11one_of_visibilityB\x0e\n\x0cone_of_widthB\x15\n\x13one_of_type\
+    _options\"8\n\x07AnyData\x12\x17\n\x07type_id\x18\x01\x20\x01(\tR\x06typ\
+    eId\x12\x14\n\x05value\x18\x02\x20\x01(\x0cR\x05value\"\xff\x01\n\x07Row\
+    Meta\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.CellByFieldIdEntryR\rcellByFieldId\x12\x16\n\x06height\
+    \x18\x04\x20\x01(\x05R\x06height\x12\x1e\n\nvisibility\x18\x05\x20\x01(\
+    \x08R\nvisibility\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\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\nvisibility\x18\x03\x20\x01(\x08H\x01R\nvisibili\
+    ty\x12M\n\x10cell_by_field_id\x18\x04\x20\x03(\x0b2$.RowMetaChangeset.Ce\
+    llByFieldIdEntryR\rcellByFieldId\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\x08\
+    field_id\x18\x03\x20\x01(\tR\x07fieldId\x12\x1c\n\x04data\x18\x04\x20\
+    \x01(\x0b2\x08.AnyDataR\x04data\x12\x16\n\x06height\x18\x05\x20\x01(\x05\
+    R\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\x06prot\
+    o3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 13 - 3
shared-lib/flowy-grid-data-model/src/protobuf/proto/meta.proto

@@ -3,14 +3,14 @@ syntax = "proto3";
 message GridMeta {
     string grid_id = 1;
     repeated Field fields = 2;
-    repeated Block blocks = 3;
+    repeated GridBlock blocks = 3;
 }
-message Block {
+message GridBlock {
     string id = 1;
     int32 start_row_index = 2;
     int32 row_count = 3;
 }
-message BlockMeta {
+message GridBlockMeta {
     string block_id = 1;
     repeated RowMeta rows = 2;
 }
@@ -27,6 +27,16 @@ message Field {
 message RepeatedField {
     repeated Field items = 1;
 }
+message FieldChangeset {
+    string field_id = 1;
+    oneof one_of_name { string name = 2; };
+    oneof one_of_desc { string desc = 3; };
+    oneof one_of_field_type { FieldType field_type = 4; };
+    oneof one_of_frozen { bool frozen = 5; };
+    oneof one_of_visibility { bool visibility = 6; };
+    oneof one_of_width { int32 width = 7; };
+    oneof one_of_type_options { AnyData type_options = 8; };
+}
 message AnyData {
     string type_id = 1;
     bytes value = 2;