浏览代码

feat: config grid db

appflowy 3 年之前
父节点
当前提交
49807a0b57
共有 34 个文件被更改,包括 976 次插入691 次删除
  1. 101 144
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
  2. 16 26
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
  3. 12 0
      frontend/rust-lib/Cargo.lock
  4. 1 1
      frontend/rust-lib/flowy-block/src/web_socket.rs
  5. 2 2
      frontend/rust-lib/flowy-folder/src/services/view/controller.rs
  6. 1 1
      frontend/rust-lib/flowy-folder/src/services/web_socket.rs
  7. 14 3
      frontend/rust-lib/flowy-grid/Cargo.toml
  8. 1 1
      frontend/rust-lib/flowy-grid/Flowy.toml
  9. 1 1
      frontend/rust-lib/flowy-grid/src/lib.rs
  10. 1 1
      frontend/rust-lib/flowy-grid/src/services/cell_data.rs
  11. 143 0
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  12. 3 0
      frontend/rust-lib/flowy-grid/src/services/mod.rs
  13. 95 0
      frontend/rust-lib/flowy-grid/src/services/row_kv.rs
  14. 2 2
      frontend/rust-lib/flowy-grid/src/services/stringify.rs
  15. 1 1
      frontend/rust-lib/flowy-grid/src/services/util.rs
  16. 5 5
      frontend/rust-lib/flowy-net/src/local_server/server.rs
  17. 5 5
      frontend/rust-lib/flowy-sync/src/ws_manager.rs
  18. 2 2
      frontend/rust-lib/flowy-test/src/helper.rs
  19. 2 2
      frontend/rust-lib/flowy-test/src/lib.rs
  20. 2 2
      frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs
  21. 4 0
      shared-lib/Cargo.lock
  22. 1 0
      shared-lib/flowy-collaboration/Cargo.toml
  23. 5 6
      shared-lib/flowy-collaboration/src/client_folder/builder.rs
  24. 46 56
      shared-lib/flowy-collaboration/src/client_folder/folder_pad.rs
  25. 141 0
      shared-lib/flowy-collaboration/src/client_grid/grid_pad.rs
  26. 3 0
      shared-lib/flowy-collaboration/src/client_grid/mod.rs
  27. 1 0
      shared-lib/flowy-collaboration/src/lib.rs
  28. 27 0
      shared-lib/flowy-collaboration/src/util.rs
  29. 3 0
      shared-lib/flowy-grid-data-model/Cargo.toml
  30. 59 27
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  31. 209 391
      shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
  32. 8 11
      shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto
  33. 58 0
      shared-lib/flowy-grid-data-model/tests/serde_test.rs
  34. 1 1
      shared-lib/lib-infra/src/lib.rs

+ 101 - 144
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart

@@ -16,16 +16,14 @@ export 'grid.pbenum.dart';
 class Grid extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Grid', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
-    ..aOM<RepeatedFilter>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'filters', subBuilder: RepeatedFilter.create)
-    ..aOM<RepeatedFieldOrder>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldOrders', subBuilder: RepeatedFieldOrder.create)
-    ..aOM<RepeatedRowOrder>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowOrders', subBuilder: RepeatedRowOrder.create)
+    ..aOM<RepeatedFieldOrder>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldOrders', subBuilder: RepeatedFieldOrder.create)
+    ..aOM<RepeatedRowOrder>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowOrders', subBuilder: RepeatedRowOrder.create)
     ..hasRequiredFields = false
   ;
 
   Grid._() : super();
   factory Grid({
     $core.String? id,
-    RepeatedFilter? filters,
     RepeatedFieldOrder? fieldOrders,
     RepeatedRowOrder? rowOrders,
   }) {
@@ -33,9 +31,6 @@ class Grid extends $pb.GeneratedMessage {
     if (id != null) {
       _result.id = id;
     }
-    if (filters != null) {
-      _result.filters = filters;
-    }
     if (fieldOrders != null) {
       _result.fieldOrders = fieldOrders;
     }
@@ -75,37 +70,26 @@ class Grid extends $pb.GeneratedMessage {
   void clearId() => clearField(1);
 
   @$pb.TagNumber(2)
-  RepeatedFilter get filters => $_getN(1);
+  RepeatedFieldOrder get fieldOrders => $_getN(1);
   @$pb.TagNumber(2)
-  set filters(RepeatedFilter v) { setField(2, v); }
+  set fieldOrders(RepeatedFieldOrder v) { setField(2, v); }
   @$pb.TagNumber(2)
-  $core.bool hasFilters() => $_has(1);
+  $core.bool hasFieldOrders() => $_has(1);
   @$pb.TagNumber(2)
-  void clearFilters() => clearField(2);
+  void clearFieldOrders() => clearField(2);
   @$pb.TagNumber(2)
-  RepeatedFilter ensureFilters() => $_ensure(1);
+  RepeatedFieldOrder ensureFieldOrders() => $_ensure(1);
 
   @$pb.TagNumber(3)
-  RepeatedFieldOrder get fieldOrders => $_getN(2);
+  RepeatedRowOrder get rowOrders => $_getN(2);
   @$pb.TagNumber(3)
-  set fieldOrders(RepeatedFieldOrder v) { setField(3, v); }
+  set rowOrders(RepeatedRowOrder v) { setField(3, v); }
   @$pb.TagNumber(3)
-  $core.bool hasFieldOrders() => $_has(2);
+  $core.bool hasRowOrders() => $_has(2);
   @$pb.TagNumber(3)
-  void clearFieldOrders() => clearField(3);
+  void clearRowOrders() => clearField(3);
   @$pb.TagNumber(3)
-  RepeatedFieldOrder ensureFieldOrders() => $_ensure(2);
-
-  @$pb.TagNumber(4)
-  RepeatedRowOrder get rowOrders => $_getN(3);
-  @$pb.TagNumber(4)
-  set rowOrders(RepeatedRowOrder v) { setField(4, v); }
-  @$pb.TagNumber(4)
-  $core.bool hasRowOrders() => $_has(3);
-  @$pb.TagNumber(4)
-  void clearRowOrders() => clearField(4);
-  @$pb.TagNumber(4)
-  RepeatedRowOrder ensureRowOrders() => $_ensure(3);
+  RepeatedRowOrder ensureRowOrders() => $_ensure(2);
 }
 
 class FieldOrder extends $pb.GeneratedMessage {
@@ -890,6 +874,95 @@ class Cell extends $pb.GeneratedMessage {
   void clearContent() => clearField(3);
 }
 
+class CellChangeset extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CellChangeset', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowId')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
+    ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
+    ..hasRequiredFields = false
+  ;
+
+  CellChangeset._() : super();
+  factory CellChangeset({
+    $core.String? id,
+    $core.String? rowId,
+    $core.String? fieldId,
+    $core.String? data,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    if (rowId != null) {
+      _result.rowId = rowId;
+    }
+    if (fieldId != null) {
+      _result.fieldId = fieldId;
+    }
+    if (data != null) {
+      _result.data = data;
+    }
+    return _result;
+  }
+  factory CellChangeset.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory CellChangeset.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')
+  CellChangeset clone() => CellChangeset()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  CellChangeset copyWith(void Function(CellChangeset) updates) => super.copyWith((message) => updates(message as CellChangeset)) as CellChangeset; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static CellChangeset create() => CellChangeset._();
+  CellChangeset createEmptyInstance() => create();
+  static $pb.PbList<CellChangeset> createRepeated() => $pb.PbList<CellChangeset>();
+  @$core.pragma('dart2js:noInline')
+  static CellChangeset getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CellChangeset>(create);
+  static CellChangeset? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get id => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set id($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get rowId => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set rowId($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasRowId() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearRowId() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.String get fieldId => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set fieldId($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasFieldId() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearFieldId() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $core.String get data => $_getSZ(3);
+  @$pb.TagNumber(4)
+  set data($core.String v) { $_setString(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasData() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearData() => clearField(4);
+}
+
 class CreateGridPayload extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CreateGridPayload', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
@@ -984,119 +1057,3 @@ class GridId extends $pb.GeneratedMessage {
   void clearValue() => clearField(1);
 }
 
-class Filter extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Filter', createEmptyInstance: create)
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
-    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
-    ..hasRequiredFields = false
-  ;
-
-  Filter._() : super();
-  factory Filter({
-    $core.String? id,
-    $core.String? name,
-    $core.String? desc,
-  }) {
-    final _result = create();
-    if (id != null) {
-      _result.id = id;
-    }
-    if (name != null) {
-      _result.name = name;
-    }
-    if (desc != null) {
-      _result.desc = desc;
-    }
-    return _result;
-  }
-  factory Filter.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory Filter.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')
-  Filter clone() => Filter()..mergeFromMessage(this);
-  @$core.Deprecated(
-  'Using this can add significant overhead to your binary. '
-  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
-  'Will be removed in next major version')
-  Filter copyWith(void Function(Filter) updates) => super.copyWith((message) => updates(message as Filter)) as Filter; // ignore: deprecated_member_use
-  $pb.BuilderInfo get info_ => _i;
-  @$core.pragma('dart2js:noInline')
-  static Filter create() => Filter._();
-  Filter createEmptyInstance() => create();
-  static $pb.PbList<Filter> createRepeated() => $pb.PbList<Filter>();
-  @$core.pragma('dart2js:noInline')
-  static Filter getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Filter>(create);
-  static Filter? _defaultInstance;
-
-  @$pb.TagNumber(1)
-  $core.String get id => $_getSZ(0);
-  @$pb.TagNumber(1)
-  set id($core.String v) { $_setString(0, v); }
-  @$pb.TagNumber(1)
-  $core.bool hasId() => $_has(0);
-  @$pb.TagNumber(1)
-  void clearId() => 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);
-}
-
-class RepeatedFilter extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RepeatedFilter', createEmptyInstance: create)
-    ..pc<Filter>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: Filter.create)
-    ..hasRequiredFields = false
-  ;
-
-  RepeatedFilter._() : super();
-  factory RepeatedFilter({
-    $core.Iterable<Filter>? items,
-  }) {
-    final _result = create();
-    if (items != null) {
-      _result.items.addAll(items);
-    }
-    return _result;
-  }
-  factory RepeatedFilter.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory RepeatedFilter.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')
-  RepeatedFilter clone() => RepeatedFilter()..mergeFromMessage(this);
-  @$core.Deprecated(
-  'Using this can add significant overhead to your binary. '
-  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
-  'Will be removed in next major version')
-  RepeatedFilter copyWith(void Function(RepeatedFilter) updates) => super.copyWith((message) => updates(message as RepeatedFilter)) as RepeatedFilter; // ignore: deprecated_member_use
-  $pb.BuilderInfo get info_ => _i;
-  @$core.pragma('dart2js:noInline')
-  static RepeatedFilter create() => RepeatedFilter._();
-  RepeatedFilter createEmptyInstance() => create();
-  static $pb.PbList<RepeatedFilter> createRepeated() => $pb.PbList<RepeatedFilter>();
-  @$core.pragma('dart2js:noInline')
-  static RepeatedFilter getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RepeatedFilter>(create);
-  static RepeatedFilter? _defaultInstance;
-
-  @$pb.TagNumber(1)
-  $core.List<Filter> get items => $_getList(0);
-}
-

+ 16 - 26
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart

@@ -28,14 +28,13 @@ const Grid$json = const {
   '1': 'Grid',
   '2': const [
     const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
-    const {'1': 'filters', '3': 2, '4': 1, '5': 11, '6': '.RepeatedFilter', '10': 'filters'},
-    const {'1': 'field_orders', '3': 3, '4': 1, '5': 11, '6': '.RepeatedFieldOrder', '10': 'fieldOrders'},
-    const {'1': 'row_orders', '3': 4, '4': 1, '5': 11, '6': '.RepeatedRowOrder', '10': 'rowOrders'},
+    const {'1': 'field_orders', '3': 2, '4': 1, '5': 11, '6': '.RepeatedFieldOrder', '10': 'fieldOrders'},
+    const {'1': 'row_orders', '3': 3, '4': 1, '5': 11, '6': '.RepeatedRowOrder', '10': 'rowOrders'},
   ],
 };
 
 /// Descriptor for `Grid`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List gridDescriptor = $convert.base64Decode('CgRHcmlkEg4KAmlkGAEgASgJUgJpZBIpCgdmaWx0ZXJzGAIgASgLMg8uUmVwZWF0ZWRGaWx0ZXJSB2ZpbHRlcnMSNgoMZmllbGRfb3JkZXJzGAMgASgLMhMuUmVwZWF0ZWRGaWVsZE9yZGVyUgtmaWVsZE9yZGVycxIwCgpyb3dfb3JkZXJzGAQgASgLMhEuUmVwZWF0ZWRSb3dPcmRlclIJcm93T3JkZXJz');
+final $typed_data.Uint8List gridDescriptor = $convert.base64Decode('CgRHcmlkEg4KAmlkGAEgASgJUgJpZBI2CgxmaWVsZF9vcmRlcnMYAiABKAsyEy5SZXBlYXRlZEZpZWxkT3JkZXJSC2ZpZWxkT3JkZXJzEjAKCnJvd19vcmRlcnMYAyABKAsyES5SZXBlYXRlZFJvd09yZGVyUglyb3dPcmRlcnM=');
 @$core.Deprecated('Use fieldOrderDescriptor instead')
 const FieldOrder$json = const {
   '1': 'FieldOrder',
@@ -196,6 +195,19 @@ const Cell$json = const {
 
 /// Descriptor for `Cell`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List cellDescriptor = $convert.base64Decode('CgRDZWxsEg4KAmlkGAEgASgJUgJpZBIZCghmaWVsZF9pZBgCIAEoCVIHZmllbGRJZBIYCgdjb250ZW50GAMgASgJUgdjb250ZW50');
+@$core.Deprecated('Use cellChangesetDescriptor instead')
+const CellChangeset$json = const {
+  '1': 'CellChangeset',
+  '2': const [
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+    const {'1': 'row_id', '3': 2, '4': 1, '5': 9, '10': 'rowId'},
+    const {'1': 'field_id', '3': 3, '4': 1, '5': 9, '10': 'fieldId'},
+    const {'1': 'data', '3': 4, '4': 1, '5': 9, '10': 'data'},
+  ],
+};
+
+/// Descriptor for `CellChangeset`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List cellChangesetDescriptor = $convert.base64Decode('Cg1DZWxsQ2hhbmdlc2V0Eg4KAmlkGAEgASgJUgJpZBIVCgZyb3dfaWQYAiABKAlSBXJvd0lkEhkKCGZpZWxkX2lkGAMgASgJUgdmaWVsZElkEhIKBGRhdGEYBCABKAlSBGRhdGE=');
 @$core.Deprecated('Use createGridPayloadDescriptor instead')
 const CreateGridPayload$json = const {
   '1': 'CreateGridPayload',
@@ -216,25 +228,3 @@ const GridId$json = const {
 
 /// Descriptor for `GridId`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List gridIdDescriptor = $convert.base64Decode('CgZHcmlkSWQSFAoFdmFsdWUYASABKAlSBXZhbHVl');
-@$core.Deprecated('Use filterDescriptor instead')
-const Filter$json = const {
-  '1': 'Filter',
-  '2': const [
-    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
-    const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
-    const {'1': 'desc', '3': 3, '4': 1, '5': 9, '10': 'desc'},
-  ],
-};
-
-/// Descriptor for `Filter`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List filterDescriptor = $convert.base64Decode('CgZGaWx0ZXISDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEZGVzYxgDIAEoCVIEZGVzYw==');
-@$core.Deprecated('Use repeatedFilterDescriptor instead')
-const RepeatedFilter$json = const {
-  '1': 'RepeatedFilter',
-  '2': const [
-    const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.Filter', '10': 'items'},
-  ],
-};
-
-/// Descriptor for `RepeatedFilter`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List repeatedFilterDescriptor = $convert.base64Decode('Cg5SZXBlYXRlZEZpbHRlchIdCgVpdGVtcxgBIAMoCzIHLkZpbHRlclIFaXRlbXM=');

+ 12 - 0
frontend/rust-lib/Cargo.lock

@@ -909,6 +909,7 @@ dependencies = [
  "dissimilar",
  "flowy-derive",
  "flowy-folder-data-model",
+ "flowy-grid-data-model",
  "futures",
  "lib-infra",
  "lib-ot",
@@ -1048,14 +1049,22 @@ dependencies = [
 name = "flowy-grid"
 version = "0.1.0"
 dependencies = [
+ "async-trait",
  "bytes",
  "chrono",
+ "dart-notify",
+ "diesel",
+ "flowy-collaboration",
  "flowy-derive",
  "flowy-error",
  "flowy-grid-data-model",
+ "flowy-sync",
  "lazy_static",
  "lib-dispatch",
  "lib-infra",
+ "lib-ot",
+ "lib-sqlite",
+ "parking_lot",
  "protobuf",
  "rust_decimal",
  "rusty-money",
@@ -1073,8 +1082,11 @@ dependencies = [
  "flowy-derive",
  "lib-infra",
  "protobuf",
+ "serde",
+ "serde_json",
  "strum",
  "strum_macros",
+ "uuid",
 ]
 
 [[package]]

+ 1 - 1
frontend/rust-lib/flowy-block/src/web_socket.rs

@@ -97,7 +97,7 @@ impl RevisionWSDataStream for BlockRevisionWSDataStream {
 }
 
 pub(crate) struct BlockWSDataSink(pub(crate) Arc<WSDataProvider>);
-impl RevisionWSDataIterator for BlockWSDataSink {
+impl RevisionWebSocketSink for BlockWSDataSink {
     fn next(&self) -> FutureResult<Option<ClientRevisionWSData>, FlowyError> {
         let sink_provider = self.0.clone();
         FutureResult::new(async move { sink_provider.next().await })

+ 2 - 2
frontend/rust-lib/flowy-folder/src/services/view/controller.rs

@@ -25,7 +25,7 @@ use flowy_block::BlockManager;
 use flowy_database::kv::KV;
 use flowy_folder_data_model::entities::share::{ExportData, ExportParams};
 
-use lib_infra::uuid_string;
+use lib_infra::uuid;
 
 const LATEST_VIEW_ID: &str = "latest_view_id";
 
@@ -176,7 +176,7 @@ impl ViewController {
             thumbnail: view.thumbnail,
             data_type: view.data_type,
             data: document_json,
-            view_id: uuid_string(),
+            view_id: uuid(),
             ext_data: view.ext_data,
             plugin_type: view.plugin_type,
         };

+ 1 - 1
frontend/rust-lib/flowy-folder/src/services/web_socket.rs

@@ -43,7 +43,7 @@ pub(crate) async fn make_folder_ws_manager(
 }
 
 pub(crate) struct FolderWSDataSink(Arc<WSDataProvider>);
-impl RevisionWSDataIterator for FolderWSDataSink {
+impl RevisionWebSocketSink for FolderWSDataSink {
     fn next(&self) -> FutureResult<Option<ClientRevisionWSData>, FlowyError> {
         let sink_provider = self.0.clone();
         FutureResult::new(async move { sink_provider.next().await })

+ 14 - 3
frontend/rust-lib/flowy-grid/Cargo.toml

@@ -6,13 +6,19 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-flowy-grid-data-model = { path = "../../../shared-lib/flowy-grid-data-model" }
 lib-dispatch = { path = "../lib-dispatch" }
-lib-infra = { path = "../../../shared-lib/lib-infra" }
+dart-notify = { path = "../dart-notify" }
+lib-sqlite = { path = "../lib-sqlite" }
+flowy-sync = { path = "../flowy-sync" }
 flowy-error = { path = "../flowy-error"}
+flowy-derive = { path = "../../../shared-lib/flowy-derive" }
+lib-ot = { path = "../../../shared-lib/lib-ot" }
+lib-infra = { path = "../../../shared-lib/lib-infra" }
+flowy-grid-data-model = { path = "../../../shared-lib/flowy-grid-data-model" }
+flowy-collaboration = { path = "../../../shared-lib/flowy-collaboration" }
+
 strum = "0.21"
 strum_macros = "0.21"
-flowy-derive = { path = "../../../shared-lib/flowy-derive" }
 tracing = { version = "0.1", features = ["log"] }
 protobuf = {version = "2.18.0"}
 rust_decimal = "1.8.1"
@@ -21,6 +27,11 @@ lazy_static = "1.4.0"
 chrono = "0.4.19"
 uuid = { version = "0.8", features = ["serde", "v4"] }
 bytes = { version = "1.0" }
+async-trait = "0.1.52"
+diesel = {version = "1.4.8", features = ["sqlite"]}
+
+
+parking_lot = "0.11"
 
 [build-dependencies]
 lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file_gen", "proto_gen"] }

+ 1 - 1
frontend/rust-lib/flowy-grid/Flowy.toml

@@ -1,3 +1,3 @@
 
-proto_crates = ["src/event_map.rs", "src/cell_service/cell_data.rs"]
+proto_crates = ["src/event_map.rs", "src/services/cell_data.rs"]
 event_files = ["src/event_map.rs"]

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

@@ -5,5 +5,5 @@ mod controller;
 mod event_handler;
 mod event_map;
 
-mod cell_service;
 mod protobuf;
+mod services;

+ 1 - 1
frontend/rust-lib/flowy-grid/src/cell_service/cell_data.rs → frontend/rust-lib/flowy-grid/src/services/cell_data.rs

@@ -1,5 +1,5 @@
-use crate::cell_service::util::*;
 use crate::impl_any_data;
+use crate::services::util::*;
 use bytes::Bytes;
 use chrono::format::strftime::StrftimeItems;
 use chrono::NaiveDateTime;

+ 143 - 0
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -0,0 +1,143 @@
+use crate::services::row_kv::{RowKVPersistence, RowKVTransaction};
+use flowy_collaboration::client_grid::{GridChange, GridPad};
+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::{GridId, RawRow};
+use flowy_sync::{
+    RevisionCloudService, RevisionCompact, RevisionManager, RevisionObjectBuilder, RevisionPersistence,
+    RevisionWebSocket, RevisionWebSocketManager,
+};
+use lib_infra::future::FutureResult;
+use lib_ot::core::PlainTextAttributes;
+use lib_sqlite::ConnectionPool;
+use parking_lot::RwLock;
+use std::sync::Arc;
+
+pub struct ClientGridEditor {
+    user_id: String,
+    grid_id: GridId,
+    grid: Arc<RwLock<GridPad>>,
+    rev_manager: Arc<RevisionManager>,
+    kv: Arc<RowKVPersistence>,
+}
+
+impl ClientGridEditor {
+    pub async fn new(
+        user_id: &str,
+        grid_id: &GridId,
+        token: &str,
+        pool: Arc<ConnectionPool>,
+        _web_socket: Arc<dyn RevisionWebSocket>,
+    ) -> FlowyResult<Self> {
+        let rev_persistence = Arc::new(RevisionPersistence::new(user_id, grid_id.as_ref(), pool.clone()));
+        let mut rev_manager = RevisionManager::new(user_id, grid_id.as_ref(), rev_persistence);
+        let cloud = Arc::new(GridRevisionCloudService {
+            token: token.to_string(),
+        });
+        let grid = Arc::new(RwLock::new(
+            rev_manager.load::<GridPadBuilder, GridRevisionCompact>(cloud).await?,
+        ));
+        let rev_manager = Arc::new(rev_manager);
+        let kv = Arc::new(RowKVPersistence::new(pool));
+
+        let user_id = user_id.to_owned();
+        let grid_id = grid_id.to_owned();
+        Ok(Self {
+            user_id,
+            grid_id,
+            grid,
+            rev_manager,
+            kv,
+        })
+    }
+
+    pub async fn create_row(&self, row: RawRow) -> FlowyResult<()> {
+        let _ = self
+            .modify(|grid| {
+                let change = grid.create_row(&row)?;
+                Ok(change)
+            })
+            .await?;
+
+        let _ = self.kv.set(row)?;
+        Ok(())
+    }
+
+    pub async fn modify<F>(&self, f: F) -> FlowyResult<()>
+    where
+        F: FnOnce(&mut GridPad) -> FlowyResult<Option<GridChange>>,
+    {
+        let mut write_guard = self.grid.write();
+        match f(&mut *write_guard)? {
+            None => {}
+            Some(change) => {
+                let _ = self.apply_change(change).await?;
+            }
+        }
+        Ok(())
+    }
+
+    async fn apply_change(&self, change: GridChange) -> FlowyResult<()> {
+        let GridChange { delta, md5 } = change;
+        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,
+            &self.user_id,
+            md5,
+        );
+        let _ = self
+            .rev_manager
+            .add_local_revision::<GridRevisionCompact>(&revision)
+            .await?;
+        Ok(())
+    }
+}
+
+struct GridPadBuilder();
+impl RevisionObjectBuilder for GridPadBuilder {
+    type Output = GridPad;
+
+    fn build_object(_object_id: &str, revisions: Vec<Revision>) -> FlowyResult<Self::Output> {
+        let pad = GridPad::from_revisions(revisions)?;
+        Ok(pad)
+    }
+}
+
+struct GridRevisionCloudService {
+    #[allow(dead_code)]
+    token: String,
+}
+
+impl RevisionCloudService for GridRevisionCloudService {
+    #[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 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"));
+        }
+
+        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 = 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))
+    }
+}

+ 3 - 0
frontend/rust-lib/flowy-grid/src/cell_service/mod.rs → frontend/rust-lib/flowy-grid/src/services/mod.rs

@@ -2,4 +2,7 @@ mod stringify;
 mod util;
 
 pub mod cell_data;
+pub mod grid_editor;
+mod row_kv;
+
 pub use stringify::*;

+ 95 - 0
frontend/rust-lib/flowy-grid/src/services/row_kv.rs

@@ -0,0 +1,95 @@
+use async_trait::async_trait;
+use diesel::SqliteConnection;
+use flowy_error::{FlowyError, FlowyResult};
+use flowy_grid_data_model::entities::RawRow;
+use lib_infra::future::{BoxResultFuture, FutureResult};
+use lib_sqlite::{ConnectionManager, ConnectionPool};
+use std::sync::Arc;
+
+pub trait RowKVTransaction {
+    fn get(&self, row_id: &str) -> FlowyResult<Option<RawRow>>;
+    fn set(&self, row: RawRow) -> FlowyResult<()>;
+    fn remove(&self, row_id: &str) -> FlowyResult<()>;
+
+    fn batch_get(&self, ids: Vec<String>) -> FlowyResult<()>;
+    fn batch_set(&self, rows: Vec<RawRow>) -> FlowyResult<()>;
+    fn batch_delete(&self, ids: Vec<String>) -> FlowyResult<()>;
+}
+
+pub struct RowKVPersistence {
+    pool: Arc<ConnectionPool>,
+}
+
+impl RowKVPersistence {
+    pub fn new(pool: Arc<ConnectionPool>) -> Self {
+        Self { pool }
+    }
+
+    pub fn begin_transaction<F, O>(&self, f: F) -> FlowyResult<O>
+    where
+        F: for<'a> FnOnce(Box<dyn RowKVTransaction + 'a>) -> FlowyResult<O>,
+    {
+        let conn = self.pool.get()?;
+        conn.immediate_transaction::<_, FlowyError, _>(|| {
+            let sql_transaction = SqliteTransaction { conn: &conn };
+            f(Box::new(sql_transaction))
+        })
+    }
+}
+
+impl RowKVTransaction for RowKVPersistence {
+    fn get(&self, row_id: &str) -> FlowyResult<Option<RawRow>> {
+        self.begin_transaction(|transaction| transaction.get(row_id))
+    }
+
+    fn set(&self, row: RawRow) -> FlowyResult<()> {
+        self.begin_transaction(|transaction| transaction.set(row))
+    }
+
+    fn remove(&self, row_id: &str) -> FlowyResult<()> {
+        self.begin_transaction(|transaction| transaction.remove(row_id))
+    }
+
+    fn batch_get(&self, ids: Vec<String>) -> FlowyResult<()> {
+        self.begin_transaction(|transaction| transaction.batch_get(ids))
+    }
+
+    fn batch_set(&self, rows: Vec<RawRow>) -> FlowyResult<()> {
+        self.begin_transaction(|transaction| transaction.batch_set(rows))
+    }
+
+    fn batch_delete(&self, ids: Vec<String>) -> FlowyResult<()> {
+        self.begin_transaction(|transaction| transaction.batch_delete(ids))
+    }
+}
+
+pub struct SqliteTransaction<'a> {
+    conn: &'a SqliteConnection,
+}
+
+#[async_trait]
+impl<'a> RowKVTransaction for SqliteTransaction<'a> {
+    fn get(&self, row_id: &str) -> FlowyResult<Option<RawRow>> {
+        todo!()
+    }
+
+    fn set(&self, row: RawRow) -> FlowyResult<()> {
+        todo!()
+    }
+
+    fn remove(&self, row_id: &str) -> FlowyResult<()> {
+        todo!()
+    }
+
+    fn batch_get(&self, ids: Vec<String>) -> FlowyResult<()> {
+        todo!()
+    }
+
+    fn batch_set(&self, rows: Vec<RawRow>) -> FlowyResult<()> {
+        todo!()
+    }
+
+    fn batch_delete(&self, ids: Vec<String>) -> FlowyResult<()> {
+        todo!()
+    }
+}

+ 2 - 2
frontend/rust-lib/flowy-grid/src/cell_service/stringify.rs → frontend/rust-lib/flowy-grid/src/services/stringify.rs

@@ -1,5 +1,5 @@
-use crate::cell_service::cell_data::*;
-use crate::cell_service::util::*;
+use crate::services::cell_data::*;
+use crate::services::util::*;
 use flowy_error::FlowyError;
 use flowy_grid_data_model::entities::{AnyData, Field, FieldType};
 

+ 1 - 1
frontend/rust-lib/flowy-grid/src/cell_service/util.rs → frontend/rust-lib/flowy-grid/src/services/util.rs

@@ -1,4 +1,4 @@
-use crate::cell_service::cell_data::FlowyMoney;
+use crate::services::cell_data::FlowyMoney;
 use flowy_error::FlowyError;
 use flowy_grid_data_model::entities::{AnyData, Field, FieldType};
 use lazy_static::lazy_static;

+ 5 - 5
frontend/rust-lib/flowy-net/src/local_server/server.rs

@@ -259,7 +259,7 @@ use flowy_user::event_map::UserCloudService;
 use flowy_user_data_model::entities::{
     SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserParams, UserProfile,
 };
-use lib_infra::{future::FutureResult, timestamp, uuid_string};
+use lib_infra::{future::FutureResult, timestamp, uuid};
 
 impl FolderCouldServiceV1 for LocalServer {
     fn init(&self) {}
@@ -267,7 +267,7 @@ impl FolderCouldServiceV1 for LocalServer {
     fn create_workspace(&self, _token: &str, params: CreateWorkspaceParams) -> FutureResult<Workspace, FlowyError> {
         let time = timestamp();
         let workspace = Workspace {
-            id: uuid_string(),
+            id: uuid(),
             name: params.name,
             desc: params.desc,
             apps: RepeatedApp::default(),
@@ -327,7 +327,7 @@ impl FolderCouldServiceV1 for LocalServer {
     fn create_app(&self, _token: &str, params: CreateAppParams) -> FutureResult<App, FlowyError> {
         let time = timestamp();
         let app = App {
-            id: uuid_string(),
+            id: uuid(),
             workspace_id: params.workspace_id,
             name: params.name,
             desc: params.desc,
@@ -369,7 +369,7 @@ impl FolderCouldServiceV1 for LocalServer {
 
 impl UserCloudService for LocalServer {
     fn sign_up(&self, params: SignUpParams) -> FutureResult<SignUpResponse, FlowyError> {
-        let uid = uuid_string();
+        let uid = uuid();
         FutureResult::new(async move {
             Ok(SignUpResponse {
                 user_id: uid.clone(),
@@ -381,7 +381,7 @@ impl UserCloudService for LocalServer {
     }
 
     fn sign_in(&self, params: SignInParams) -> FutureResult<SignInResponse, FlowyError> {
-        let user_id = uuid_string();
+        let user_id = uuid();
         FutureResult::new(async {
             Ok(SignInResponse {
                 user_id: user_id.clone(),

+ 5 - 5
frontend/rust-lib/flowy-sync/src/ws_manager.rs

@@ -30,7 +30,7 @@ pub trait RevisionWSDataStream: Send + Sync {
 
 // The sink provides the data that will be sent through the web socket to the
 // backend.
-pub trait RevisionWSDataIterator: Send + Sync {
+pub trait RevisionWebSocketSink: Send + Sync {
     fn next(&self) -> FutureResult<Option<ClientRevisionWSData>, FlowyError>;
 }
 
@@ -43,7 +43,7 @@ pub trait RevisionWebSocket: Send + Sync + 'static {
 pub struct RevisionWebSocketManager {
     pub object_name: String,
     pub object_id: String,
-    ws_data_sink: Arc<dyn RevisionWSDataIterator>,
+    ws_data_sink: Arc<dyn RevisionWebSocketSink>,
     ws_data_stream: Arc<dyn RevisionWSDataStream>,
     rev_web_socket: Arc<dyn RevisionWebSocket>,
     pub ws_passthrough_tx: Sender<ServerRevisionWSData>,
@@ -62,7 +62,7 @@ impl RevisionWebSocketManager {
         object_name: &str,
         object_id: &str,
         rev_web_socket: Arc<dyn RevisionWebSocket>,
-        ws_data_sink: Arc<dyn RevisionWSDataIterator>,
+        ws_data_sink: Arc<dyn RevisionWebSocketSink>,
         ws_data_stream: Arc<dyn RevisionWSDataStream>,
         ping_duration: Duration,
     ) -> Self {
@@ -246,7 +246,7 @@ type SinkStopTx = broadcast::Sender<()>;
 pub struct RevisionWSSink {
     object_id: String,
     object_name: String,
-    provider: Arc<dyn RevisionWSDataIterator>,
+    provider: Arc<dyn RevisionWebSocketSink>,
     rev_web_socket: Arc<dyn RevisionWebSocket>,
     stop_rx: Option<SinkStopRx>,
     ping_duration: Duration,
@@ -256,7 +256,7 @@ impl RevisionWSSink {
     pub fn new(
         object_id: &str,
         object_name: &str,
-        provider: Arc<dyn RevisionWSDataIterator>,
+        provider: Arc<dyn RevisionWebSocketSink>,
         rev_web_socket: Arc<dyn RevisionWebSocket>,
         stop_rx: SinkStopRx,
         ping_duration: Duration,

+ 2 - 2
frontend/rust-lib/flowy-test/src/helper.rs

@@ -14,7 +14,7 @@ use flowy_user::{
     event_map::UserEvent::{InitUser, SignIn, SignOut, SignUp},
 };
 use lib_dispatch::prelude::{EventDispatcher, ModuleRequest, ToBytes};
-use lib_infra::uuid_string;
+use lib_infra::uuid;
 use std::{fs, path::PathBuf, sync::Arc};
 
 pub struct ViewTest {
@@ -118,7 +118,7 @@ pub fn root_dir() -> String {
 }
 
 pub fn random_email() -> String {
-    format!("{}@appflowy.io", uuid_string())
+    format!("{}@appflowy.io", uuid())
 }
 
 pub fn login_email() -> String {

+ 2 - 2
frontend/rust-lib/flowy-test/src/lib.rs

@@ -5,7 +5,7 @@ use crate::helper::*;
 use flowy_net::{get_client_server_configuration, ClientServerConfiguration};
 use flowy_sdk::{FlowySDK, FlowySDKConfig};
 use flowy_user::entities::UserProfile;
-use lib_infra::uuid_string;
+use lib_infra::uuid;
 
 pub mod prelude {
     pub use crate::{event_builder::*, helper::*, *};
@@ -36,7 +36,7 @@ impl std::default::Default for FlowySDKTest {
 
 impl FlowySDKTest {
     pub fn new(server_config: ClientServerConfiguration) -> Self {
-        let config = FlowySDKConfig::new(&root_dir(), server_config, &uuid_string()).log_filter("trace");
+        let config = FlowySDKConfig::new(&root_dir(), server_config, &uuid()).log_filter("trace");
         let sdk = std::thread::spawn(|| FlowySDK::new(config)).join().unwrap();
         std::mem::forget(sdk.dispatcher());
         Self { inner: sdk }

+ 2 - 2
frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs

@@ -2,7 +2,7 @@ use crate::helper::*;
 use flowy_test::{event_builder::UserModuleEventBuilder, FlowySDKTest};
 use flowy_user::{errors::ErrorCode, event_map::UserEvent::*};
 use flowy_user_data_model::entities::{UpdateUserPayload, UserProfile};
-use lib_infra::uuid_string;
+use lib_infra::uuid;
 use serial_test::*;
 
 #[tokio::test]
@@ -54,7 +54,7 @@ async fn user_update_with_name() {
 async fn user_update_with_email() {
     let sdk = FlowySDKTest::default();
     let user = sdk.init_user().await;
-    let new_email = format!("{}@gmail.com", uuid_string());
+    let new_email = format!("{}@gmail.com", uuid());
     let request = UpdateUserPayload::new(&user.id).email(&new_email);
     let _ = UserModuleEventBuilder::new(sdk.clone())
         .event(UpdateUser)

+ 4 - 0
shared-lib/Cargo.lock

@@ -413,6 +413,7 @@ dependencies = [
  "dissimilar",
  "flowy-derive",
  "flowy-folder-data-model",
+ "flowy-grid-data-model",
  "futures",
  "lib-infra",
  "lib-ot",
@@ -486,8 +487,11 @@ dependencies = [
  "flowy-derive",
  "lib-infra",
  "protobuf",
+ "serde",
+ "serde_json",
  "strum",
  "strum_macros",
+ "uuid",
 ]
 
 [[package]]

+ 1 - 0
shared-lib/flowy-collaboration/Cargo.toml

@@ -10,6 +10,7 @@ lib-ot = { path = "../lib-ot" }
 lib-infra = { path = "../lib-infra" }
 flowy-derive = { path = "../flowy-derive" }
 flowy-folder-data-model = { path = "../flowy-folder-data-model" }
+flowy-grid-data-model = { path = "../flowy-grid-data-model" }
 protobuf = {version = "2.18.0"}
 bytes = "1.0"
 log = "0.4.14"

+ 5 - 6
shared-lib/flowy-collaboration/src/client_folder/builder.rs

@@ -41,10 +41,9 @@ impl FolderPadBuilder {
 
         // TODO: Reconvert from history if delta.to_str() failed.
         let folder_json = delta.to_str()?;
-        let mut folder: FolderPad = serde_json::from_str(&folder_json).map_err(|e| {
-            CollaborateError::internal().context(format!("Deserialize json to root folder failed: {}", e))
-        })?;
-        folder.root = delta;
+        let mut folder: FolderPad = serde_json::from_str(&folder_json)
+            .map_err(|e| CollaborateError::internal().context(format!("Deserialize delta to folder failed: {}", e)))?;
+        folder.delta = delta;
         Ok(folder)
     }
 
@@ -55,11 +54,11 @@ impl FolderPadBuilder {
 
     pub(crate) fn build(self) -> CollaborateResult<FolderPad> {
         let json = serde_json::to_string(&self)
-            .map_err(|e| CollaborateError::internal().context(format!("serial trash to json failed: {}", e)))?;
+            .map_err(|e| CollaborateError::internal().context(format!("Serialize to folder json str failed: {}", e)))?;
         Ok(FolderPad {
             workspaces: self.workspaces,
             trash: self.trash,
-            root: PlainTextDeltaBuilder::new().insert(&json).build(),
+            delta: PlainTextDeltaBuilder::new().insert(&json).build(),
         })
     }
 }

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

@@ -1,3 +1,4 @@
+use crate::util::cal_diff;
 use crate::{
     client_folder::builder::FolderPadBuilder,
     entities::{
@@ -6,9 +7,9 @@ use crate::{
     },
     errors::{CollaborateError, CollaborateResult},
 };
-use dissimilar::*;
 use flowy_folder_data_model::entities::{app::App, trash::Trash, view::View, workspace::Workspace};
-use lib_ot::core::{FlowyStr, OperationTransformable, PlainTextDelta, PlainTextDeltaBuilder};
+use lib_ot::core::*;
+use lib_ot::rich_text::RichTextAttributes;
 use serde::{Deserialize, Serialize};
 use std::sync::Arc;
 
@@ -17,35 +18,7 @@ pub struct FolderPad {
     pub(crate) workspaces: Vec<Arc<Workspace>>,
     pub(crate) trash: Vec<Arc<Trash>>,
     #[serde(skip)]
-    pub(crate) root: FolderDelta,
-}
-
-pub fn default_folder_delta() -> FolderDelta {
-    PlainTextDeltaBuilder::new()
-        .insert(r#"{"workspaces":[],"trash":[]}"#)
-        .build()
-}
-
-pub fn initial_folder_delta(folder_pad: &FolderPad) -> CollaborateResult<FolderDelta> {
-    let json = folder_pad.to_json()?;
-    let delta = PlainTextDeltaBuilder::new().insert(&json).build();
-    Ok(delta)
-}
-
-impl std::default::Default for FolderPad {
-    fn default() -> Self {
-        FolderPad {
-            workspaces: vec![],
-            trash: vec![],
-            root: default_folder_delta(),
-        }
-    }
-}
-
-pub struct FolderChange {
-    pub delta: FolderDelta,
-    /// md5: the md5 of the FolderPad's delta after applying the change.
-    pub md5: String,
+    pub(crate) delta: FolderDelta,
 }
 
 impl FolderPad {
@@ -65,20 +38,20 @@ impl FolderPad {
     }
 
     pub fn delta(&self) -> &FolderDelta {
-        &self.root
+        &self.delta
     }
 
     pub fn reset_folder(&mut self, delta: FolderDelta) -> CollaborateResult<String> {
         let folder = FolderPad::from_delta(delta)?;
         self.workspaces = folder.workspaces;
         self.trash = folder.trash;
-        self.root = folder.root;
+        self.delta = folder.delta;
 
         Ok(self.md5())
     }
 
     pub fn compose_remote_delta(&mut self, delta: FolderDelta) -> CollaborateResult<String> {
-        let composed_delta = self.root.compose(&delta)?;
+        let composed_delta = self.delta.compose(&delta)?;
         self.reset_folder(composed_delta)
     }
 
@@ -295,7 +268,7 @@ impl FolderPad {
     }
 
     pub fn md5(&self) -> String {
-        md5(&self.root.to_bytes())
+        md5(&self.delta.to_bytes())
     }
 
     pub fn to_json(&self) -> CollaborateResult<String> {
@@ -315,9 +288,13 @@ impl FolderPad {
             Some(_) => {
                 let old = cloned_self.to_json()?;
                 let new = self.to_json()?;
-                let delta = cal_diff(old, new);
-                self.root = self.root.compose(&delta)?;
-                Ok(Some(FolderChange { delta, md5: self.md5() }))
+                match cal_diff::<PlainTextAttributes>(old, new) {
+                    None => Ok(None),
+                    Some(delta) => {
+                        self.delta = self.delta.compose(&delta)?;
+                        Ok(Some(FolderChange { delta, md5: self.md5() }))
+                    }
+                }
             }
         }
     }
@@ -346,9 +323,13 @@ impl FolderPad {
             Some(_) => {
                 let old = cloned_self.to_json()?;
                 let new = self.to_json()?;
-                let delta = cal_diff(old, new);
-                self.root = self.root.compose(&delta)?;
-                Ok(Some(FolderChange { delta, md5: self.md5() }))
+                match cal_diff::<PlainTextAttributes>(old, new) {
+                    None => Ok(None),
+                    Some(delta) => {
+                        self.delta = self.delta.compose(&delta)?;
+                        Ok(Some(FolderChange { delta, md5: self.md5() }))
+                    }
+                }
             }
         }
     }
@@ -391,23 +372,32 @@ impl FolderPad {
     }
 }
 
-fn cal_diff(old: String, new: String) -> PlainTextDelta {
-    let chunks = dissimilar::diff(&old, &new);
-    let mut delta_builder = PlainTextDeltaBuilder::new();
-    for chunk in &chunks {
-        match chunk {
-            Chunk::Equal(s) => {
-                delta_builder = delta_builder.retain(FlowyStr::from(*s).utf16_size());
-            }
-            Chunk::Delete(s) => {
-                delta_builder = delta_builder.delete(FlowyStr::from(*s).utf16_size());
-            }
-            Chunk::Insert(s) => {
-                delta_builder = delta_builder.insert(*s);
-            }
+pub fn default_folder_delta() -> FolderDelta {
+    PlainTextDeltaBuilder::new()
+        .insert(r#"{"workspaces":[],"trash":[]}"#)
+        .build()
+}
+
+pub fn initial_folder_delta(folder_pad: &FolderPad) -> CollaborateResult<FolderDelta> {
+    let json = folder_pad.to_json()?;
+    let delta = PlainTextDeltaBuilder::new().insert(&json).build();
+    Ok(delta)
+}
+
+impl std::default::Default for FolderPad {
+    fn default() -> Self {
+        FolderPad {
+            workspaces: vec![],
+            trash: vec![],
+            delta: default_folder_delta(),
         }
     }
-    delta_builder.build()
+}
+
+pub struct FolderChange {
+    pub delta: FolderDelta,
+    /// md5: the md5 of the FolderPad's delta after applying the change.
+    pub md5: String,
 }
 
 #[cfg(test)]

+ 141 - 0
shared-lib/flowy-collaboration/src/client_grid/grid_pad.rs

@@ -0,0 +1,141 @@
+use crate::entities::revision::{md5, Revision};
+use crate::errors::{internal_error, CollaborateError, CollaborateResult};
+use crate::util::{cal_diff, make_delta_from_revisions};
+use flowy_grid_data_model::entities::{CellChangeset, Field, FieldOrder, Grid, RawRow, RowOrder};
+use lib_infra::uuid;
+use lib_ot::core::{FlowyStr, OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
+use std::sync::Arc;
+
+pub type GridDelta = PlainTextDelta;
+pub type GridDeltaBuilder = PlainTextDeltaBuilder;
+
+pub struct GridPad {
+    pub(crate) grid: Arc<Grid>,
+    pub(crate) delta: GridDelta,
+}
+
+impl GridPad {
+    pub fn from_delta(delta: GridDelta) -> CollaborateResult<Self> {
+        let s = delta.to_str()?;
+        let grid: Grid = serde_json::from_str(&s)
+            .map_err(|e| CollaborateError::internal().context(format!("Deserialize delta to grid failed: {}", e)))?;
+
+        Ok(Self {
+            grid: Arc::new(grid),
+            delta,
+        })
+    }
+
+    pub fn from_revisions(revisions: Vec<Revision>) -> CollaborateResult<Self> {
+        let folder_delta: GridDelta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
+        Self::from_delta(folder_delta)
+    }
+
+    pub fn create_row(&mut self, row: &RawRow) -> CollaborateResult<Option<GridChange>> {
+        self.modify_grid(|grid| {
+            let row_order = RowOrder {
+                grid_id: grid.id.clone(),
+                row_id: row.id.clone(),
+                visibility: true,
+            };
+            grid.row_orders.push(row_order);
+            Ok(Some(()))
+        })
+    }
+
+    pub fn create_field(&mut self, field: &Field) -> CollaborateResult<Option<GridChange>> {
+        self.modify_grid(|grid| {
+            let field_order = FieldOrder {
+                field_id: field.id.clone(),
+                visibility: true,
+            };
+            grid.field_orders.push(field_order);
+            Ok(Some(()))
+        })
+    }
+
+    pub fn delete_row(&mut self, row_id: &str) -> CollaborateResult<Option<GridChange>> {
+        self.modify_grid(
+            |grid| match grid.row_orders.iter().position(|row_order| row_order.row_id == row_id) {
+                None => Ok(None),
+                Some(index) => {
+                    grid.row_orders.remove(index);
+                    Ok(Some(()))
+                }
+            },
+        )
+    }
+
+    pub fn delete_field(&mut self, field_id: &str) -> CollaborateResult<Option<GridChange>> {
+        self.modify_grid(|grid| {
+            match grid
+                .field_orders
+                .iter()
+                .position(|field_order| field_order.field_id == field_id)
+            {
+                None => Ok(None),
+                Some(index) => {
+                    grid.field_orders.remove(index);
+                    Ok(Some(()))
+                }
+            }
+        })
+    }
+
+    pub fn md5(&self) -> String {
+        md5(&self.delta.to_bytes())
+    }
+
+    pub fn modify_grid<F>(&mut self, f: F) -> CollaborateResult<Option<GridChange>>
+    where
+        F: FnOnce(&mut Grid) -> CollaborateResult<Option<()>>,
+    {
+        let cloned_grid = self.grid.clone();
+        match f(&mut Arc::make_mut(&mut self.grid))? {
+            None => Ok(None),
+            Some(_) => {
+                let old = json_from_grid(&cloned_grid)?;
+                let new = json_from_grid(&self.grid)?;
+                match cal_diff::<PlainTextAttributes>(old, new) {
+                    None => Ok(None),
+                    Some(delta) => {
+                        self.delta = self.delta.compose(&delta)?;
+                        Ok(Some(GridChange { delta, md5: self.md5() }))
+                    }
+                }
+            }
+        }
+    }
+}
+
+fn json_from_grid(grid: &Arc<Grid>) -> CollaborateResult<String> {
+    let json = serde_json::to_string(grid)
+        .map_err(|err| internal_error(format!("Serialize grid to json str failed. {:?}", err)))?;
+    Ok(json)
+}
+
+pub struct GridChange {
+    pub delta: GridDelta,
+    /// md5: the md5 of the grid after applying the change.
+    pub md5: String,
+}
+
+pub fn default_grid_delta(grid: &Grid) -> GridDelta {
+    let json = serde_json::to_string(&grid).unwrap();
+    PlainTextDeltaBuilder::new().insert(&json).build()
+}
+
+impl std::default::Default for GridPad {
+    fn default() -> Self {
+        let grid = Grid {
+            id: uuid(),
+            field_orders: Default::default(),
+            row_orders: Default::default(),
+        };
+        let delta = default_grid_delta(&grid);
+        GridPad {
+            grid: Arc::new(grid),
+            delta,
+        }
+    }
+}

+ 3 - 0
shared-lib/flowy-collaboration/src/client_grid/mod.rs

@@ -0,0 +1,3 @@
+mod grid_pad;
+
+pub use grid_pad::*;

+ 1 - 0
shared-lib/flowy-collaboration/src/lib.rs

@@ -1,5 +1,6 @@
 pub mod client_document;
 pub mod client_folder;
+pub mod client_grid;
 pub mod entities;
 pub mod errors;
 pub mod protobuf;

+ 27 - 0
shared-lib/flowy-collaboration/src/util.rs

@@ -10,6 +10,8 @@ use crate::{
         Revision as RevisionPB,
     },
 };
+use dissimilar::Chunk;
+use lib_ot::core::{DeltaBuilder, FlowyStr};
 use lib_ot::{
     core::{Attributes, Delta, OperationTransformable, NEW_LINE, WHITESPACE},
     rich_text::RichTextDelta,
@@ -254,3 +256,28 @@ pub fn rev_id_from_str(s: &str) -> Result<i64, CollaborateError> {
         .map_err(|e| CollaborateError::internal().context(format!("Parse rev_id from {} failed. {}", s, e)))?;
     Ok(rev_id)
 }
+
+pub fn cal_diff<T: Attributes>(old: String, new: String) -> Option<Delta<T>> {
+    let chunks = dissimilar::diff(&old, &new);
+    let mut delta_builder = DeltaBuilder::<T>::new();
+    for chunk in &chunks {
+        match chunk {
+            Chunk::Equal(s) => {
+                delta_builder = delta_builder.retain(FlowyStr::from(*s).utf16_size());
+            }
+            Chunk::Delete(s) => {
+                delta_builder = delta_builder.delete(FlowyStr::from(*s).utf16_size());
+            }
+            Chunk::Insert(s) => {
+                delta_builder = delta_builder.insert(*s);
+            }
+        }
+    }
+
+    let delta = delta_builder.build();
+    if delta.is_empty() {
+        None
+    } else {
+        Some(delta)
+    }
+}

+ 3 - 0
shared-lib/flowy-grid-data-model/Cargo.toml

@@ -11,6 +11,9 @@ protobuf = {version = "2.18.0"}
 bytes = "1.0"
 strum = "0.21"
 strum_macros = "0.21"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = {version = "1.0"}
+uuid = { version = "0.8", features = ["serde", "v4"] }
 
 [build-dependencies]
 lib-infra = { path = "../lib-infra", features = ["protobuf_file_gen"] }

+ 59 - 27
shared-lib/flowy-grid-data-model/src/entities/grid.rs

@@ -1,24 +1,23 @@
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
-
 use strum_macros::{Display, EnumIter, EnumString};
 
-#[derive(Debug, Default, ProtoBuf)]
+#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, ProtoBuf)]
 pub struct Grid {
     #[pb(index = 1)]
     pub id: String,
 
     #[pb(index = 2)]
-    pub filters: RepeatedFilter,
-
-    #[pb(index = 3)]
+    #[serde(flatten)]
     pub field_orders: RepeatedFieldOrder,
 
-    #[pb(index = 4)]
+    #[pb(index = 3)]
+    #[serde(flatten)]
     pub row_orders: RepeatedRowOrder,
 }
 
-#[derive(Debug, Default, ProtoBuf)]
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, ProtoBuf)]
 pub struct FieldOrder {
     #[pb(index = 1)]
     pub field_id: String,
@@ -27,12 +26,27 @@ pub struct FieldOrder {
     pub visibility: bool,
 }
 
-#[derive(Debug, Default, ProtoBuf)]
+#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, ProtoBuf)]
 pub struct RepeatedFieldOrder {
     #[pb(index = 1)]
+    #[serde(rename(serialize = "field_orders", deserialize = "field_orders"))]
     pub items: Vec<FieldOrder>,
 }
 
+impl std::ops::Deref for RepeatedFieldOrder {
+    type Target = Vec<FieldOrder>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.items
+    }
+}
+
+impl std::ops::DerefMut for RepeatedFieldOrder {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.items
+    }
+}
+
 #[derive(Debug, Default, ProtoBuf)]
 pub struct Field {
     #[pb(index = 1)]
@@ -63,7 +77,7 @@ pub struct RepeatedField {
     pub items: Vec<Field>,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum, EnumString, EnumIter, Display)]
+#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum, EnumString, EnumIter, Display, Serialize, Deserialize)]
 pub enum FieldType {
     RichText = 0,
     Number = 1,
@@ -130,7 +144,7 @@ impl ToString for AnyData {
     }
 }
 
-#[derive(Debug, Default, ProtoBuf)]
+#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, ProtoBuf)]
 pub struct RowOrder {
     #[pb(index = 1)]
     pub grid_id: String,
@@ -142,12 +156,27 @@ pub struct RowOrder {
     pub visibility: bool,
 }
 
-#[derive(Debug, Default, ProtoBuf)]
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, ProtoBuf)]
 pub struct RepeatedRowOrder {
     #[pb(index = 1)]
+    #[serde(rename(serialize = "row_orders", deserialize = "row_orders"))]
     pub items: Vec<RowOrder>,
 }
 
+impl std::ops::Deref for RepeatedRowOrder {
+    type Target = Vec<RowOrder>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.items
+    }
+}
+
+impl std::ops::DerefMut for RepeatedRowOrder {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.items
+    }
+}
+
 #[derive(Debug, Default, ProtoBuf)]
 pub struct RawRow {
     #[pb(index = 1)]
@@ -202,6 +231,21 @@ pub struct Cell {
     pub content: String,
 }
 
+#[derive(Debug, Default, ProtoBuf)]
+pub struct CellChangeset {
+    #[pb(index = 1)]
+    pub id: String,
+
+    #[pb(index = 2)]
+    pub row_id: String,
+
+    #[pb(index = 3)]
+    pub field_id: String,
+
+    #[pb(index = 4)]
+    pub data: String,
+}
+
 #[derive(ProtoBuf, Default)]
 pub struct CreateGridPayload {
     #[pb(index = 1)]
@@ -214,20 +258,8 @@ pub struct GridId {
     pub value: String,
 }
 
-#[derive(Debug, Default, ProtoBuf)]
-pub struct Filter {
-    #[pb(index = 1)]
-    pub id: String,
-
-    #[pb(index = 2)]
-    pub name: String,
-
-    #[pb(index = 3)]
-    pub desc: String,
-}
-
-#[derive(Debug, Default, ProtoBuf)]
-pub struct RepeatedFilter {
-    #[pb(index = 1)]
-    pub items: Vec<Filter>,
+impl AsRef<str> for GridId {
+    fn as_ref(&self) -> &str {
+        &self.value
+    }
 }

+ 209 - 391
shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs

@@ -27,7 +27,6 @@
 pub struct Grid {
     // message fields
     pub id: ::std::string::String,
-    pub filters: ::protobuf::SingularPtrField<RepeatedFilter>,
     pub field_orders: ::protobuf::SingularPtrField<RepeatedFieldOrder>,
     pub row_orders: ::protobuf::SingularPtrField<RepeatedRowOrder>,
     // special fields
@@ -72,40 +71,7 @@ impl Grid {
         ::std::mem::replace(&mut self.id, ::std::string::String::new())
     }
 
-    // .RepeatedFilter filters = 2;
-
-
-    pub fn get_filters(&self) -> &RepeatedFilter {
-        self.filters.as_ref().unwrap_or_else(|| <RepeatedFilter as ::protobuf::Message>::default_instance())
-    }
-    pub fn clear_filters(&mut self) {
-        self.filters.clear();
-    }
-
-    pub fn has_filters(&self) -> bool {
-        self.filters.is_some()
-    }
-
-    // Param is passed by value, moved
-    pub fn set_filters(&mut self, v: RepeatedFilter) {
-        self.filters = ::protobuf::SingularPtrField::some(v);
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_filters(&mut self) -> &mut RepeatedFilter {
-        if self.filters.is_none() {
-            self.filters.set_default();
-        }
-        self.filters.as_mut().unwrap()
-    }
-
-    // Take field
-    pub fn take_filters(&mut self) -> RepeatedFilter {
-        self.filters.take().unwrap_or_else(|| RepeatedFilter::new())
-    }
-
-    // .RepeatedFieldOrder field_orders = 3;
+    // .RepeatedFieldOrder field_orders = 2;
 
 
     pub fn get_field_orders(&self) -> &RepeatedFieldOrder {
@@ -138,7 +104,7 @@ impl Grid {
         self.field_orders.take().unwrap_or_else(|| RepeatedFieldOrder::new())
     }
 
-    // .RepeatedRowOrder row_orders = 4;
+    // .RepeatedRowOrder row_orders = 3;
 
 
     pub fn get_row_orders(&self) -> &RepeatedRowOrder {
@@ -174,11 +140,6 @@ impl Grid {
 
 impl ::protobuf::Message for Grid {
     fn is_initialized(&self) -> bool {
-        for v in &self.filters {
-            if !v.is_initialized() {
-                return false;
-            }
-        };
         for v in &self.field_orders {
             if !v.is_initialized() {
                 return false;
@@ -200,12 +161,9 @@ impl ::protobuf::Message for Grid {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
                 },
                 2 => {
-                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.filters)?;
-                },
-                3 => {
                     ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.field_orders)?;
                 },
-                4 => {
+                3 => {
                     ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.row_orders)?;
                 },
                 _ => {
@@ -223,10 +181,6 @@ impl ::protobuf::Message for Grid {
         if !self.id.is_empty() {
             my_size += ::protobuf::rt::string_size(1, &self.id);
         }
-        if let Some(ref v) = self.filters.as_ref() {
-            let len = v.compute_size();
-            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
-        }
         if let Some(ref v) = self.field_orders.as_ref() {
             let len = v.compute_size();
             my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
@@ -244,18 +198,13 @@ impl ::protobuf::Message for Grid {
         if !self.id.is_empty() {
             os.write_string(1, &self.id)?;
         }
-        if let Some(ref v) = self.filters.as_ref() {
-            os.write_tag(2, ::protobuf::wire_format::WireTypeLengthDelimited)?;
-            os.write_raw_varint32(v.get_cached_size())?;
-            v.write_to_with_cached_sizes(os)?;
-        }
         if let Some(ref v) = self.field_orders.as_ref() {
-            os.write_tag(3, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_tag(2, ::protobuf::wire_format::WireTypeLengthDelimited)?;
             os.write_raw_varint32(v.get_cached_size())?;
             v.write_to_with_cached_sizes(os)?;
         }
         if let Some(ref v) = self.row_orders.as_ref() {
-            os.write_tag(4, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_tag(3, ::protobuf::wire_format::WireTypeLengthDelimited)?;
             os.write_raw_varint32(v.get_cached_size())?;
             v.write_to_with_cached_sizes(os)?;
         }
@@ -302,11 +251,6 @@ impl ::protobuf::Message for Grid {
                 |m: &Grid| { &m.id },
                 |m: &mut Grid| { &mut m.id },
             ));
-            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<RepeatedFilter>>(
-                "filters",
-                |m: &Grid| { &m.filters },
-                |m: &mut Grid| { &mut m.filters },
-            ));
             fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<RepeatedFieldOrder>>(
                 "field_orders",
                 |m: &Grid| { &m.field_orders },
@@ -334,7 +278,6 @@ impl ::protobuf::Message for Grid {
 impl ::protobuf::Clear for Grid {
     fn clear(&mut self) {
         self.id.clear();
-        self.filters.clear();
         self.field_orders.clear();
         self.row_orders.clear();
         self.unknown_fields.clear();
@@ -3012,212 +2955,134 @@ impl ::protobuf::reflect::ProtobufValue for Cell {
 }
 
 #[derive(PartialEq,Clone,Default)]
-pub struct CreateGridPayload {
+pub struct CellChangeset {
     // message fields
-    pub name: ::std::string::String,
+    pub id: ::std::string::String,
+    pub row_id: ::std::string::String,
+    pub field_id: ::std::string::String,
+    pub data: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a CreateGridPayload {
-    fn default() -> &'a CreateGridPayload {
-        <CreateGridPayload as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a CellChangeset {
+    fn default() -> &'a CellChangeset {
+        <CellChangeset as ::protobuf::Message>::default_instance()
     }
 }
 
-impl CreateGridPayload {
-    pub fn new() -> CreateGridPayload {
+impl CellChangeset {
+    pub fn new() -> CellChangeset {
         ::std::default::Default::default()
     }
 
-    // string name = 1;
+    // string id = 1;
 
 
-    pub fn get_name(&self) -> &str {
-        &self.name
+    pub fn get_id(&self) -> &str {
+        &self.id
     }
-    pub fn clear_name(&mut self) {
-        self.name.clear();
+    pub fn clear_id(&mut self) {
+        self.id.clear();
     }
 
     // Param is passed by value, moved
-    pub fn set_name(&mut self, v: ::std::string::String) {
-        self.name = v;
+    pub fn set_id(&mut self, v: ::std::string::String) {
+        self.id = v;
     }
 
     // Mutable pointer to the field.
     // If field is not initialized, it is initialized with default value first.
-    pub fn mut_name(&mut self) -> &mut ::std::string::String {
-        &mut self.name
+    pub fn mut_id(&mut self) -> &mut ::std::string::String {
+        &mut self.id
     }
 
     // Take field
-    pub fn take_name(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.name, ::std::string::String::new())
-    }
-}
-
-impl ::protobuf::Message for CreateGridPayload {
-    fn is_initialized(&self) -> bool {
-        true
-    }
-
-    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        while !is.eof()? {
-            let (field_number, wire_type) = is.read_tag_unpack()?;
-            match field_number {
-                1 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name)?;
-                },
-                _ => {
-                    ::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.name.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.name);
-        }
-        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
-        self.cached_size.set(my_size);
-        my_size
+    pub fn take_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.id, ::std::string::String::new())
     }
 
-    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        if !self.name.is_empty() {
-            os.write_string(1, &self.name)?;
-        }
-        os.write_unknown_fields(self.get_unknown_fields())?;
-        ::std::result::Result::Ok(())
-    }
+    // string row_id = 2;
 
-    fn get_cached_size(&self) -> u32 {
-        self.cached_size.get()
-    }
 
-    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
-        &self.unknown_fields
+    pub fn get_row_id(&self) -> &str {
+        &self.row_id
     }
-
-    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
-        &mut self.unknown_fields
+    pub fn clear_row_id(&mut self) {
+        self.row_id.clear();
     }
 
-    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
+    // Param is passed by value, moved
+    pub fn set_row_id(&mut self, v: ::std::string::String) {
+        self.row_id = v;
     }
 
-    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
-        Self::descriptor_static()
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_row_id(&mut self) -> &mut ::std::string::String {
+        &mut self.row_id
     }
 
-    fn new() -> CreateGridPayload {
-        CreateGridPayload::new()
+    // Take field
+    pub fn take_row_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.row_id, ::std::string::String::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>(
-                "name",
-                |m: &CreateGridPayload| { &m.name },
-                |m: &mut CreateGridPayload| { &mut m.name },
-            ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CreateGridPayload>(
-                "CreateGridPayload",
-                fields,
-                file_descriptor_proto()
-            )
-        })
-    }
+    // string field_id = 3;
 
-    fn default_instance() -> &'static CreateGridPayload {
-        static instance: ::protobuf::rt::LazyV2<CreateGridPayload> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(CreateGridPayload::new)
-    }
-}
 
-impl ::protobuf::Clear for CreateGridPayload {
-    fn clear(&mut self) {
-        self.name.clear();
-        self.unknown_fields.clear();
+    pub fn get_field_id(&self) -> &str {
+        &self.field_id
     }
-}
-
-impl ::std::fmt::Debug for CreateGridPayload {
-    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
-        ::protobuf::text_format::fmt(self, f)
+    pub fn clear_field_id(&mut self) {
+        self.field_id.clear();
     }
-}
 
-impl ::protobuf::reflect::ProtobufValue for CreateGridPayload {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Message(self)
+    // Param is passed by value, moved
+    pub fn set_field_id(&mut self, v: ::std::string::String) {
+        self.field_id = v;
     }
-}
 
-#[derive(PartialEq,Clone,Default)]
-pub struct GridId {
-    // message fields
-    pub value: ::std::string::String,
-    // special fields
-    pub unknown_fields: ::protobuf::UnknownFields,
-    pub cached_size: ::protobuf::CachedSize,
-}
-
-impl<'a> ::std::default::Default for &'a GridId {
-    fn default() -> &'a GridId {
-        <GridId as ::protobuf::Message>::default_instance()
+    // 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
     }
-}
 
-impl GridId {
-    pub fn new() -> GridId {
-        ::std::default::Default::default()
+    // Take field
+    pub fn take_field_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.field_id, ::std::string::String::new())
     }
 
-    // string value = 1;
+    // string data = 4;
 
 
-    pub fn get_value(&self) -> &str {
-        &self.value
+    pub fn get_data(&self) -> &str {
+        &self.data
     }
-    pub fn clear_value(&mut self) {
-        self.value.clear();
+    pub fn clear_data(&mut self) {
+        self.data.clear();
     }
 
     // Param is passed by value, moved
-    pub fn set_value(&mut self, v: ::std::string::String) {
-        self.value = v;
+    pub fn set_data(&mut self, v: ::std::string::String) {
+        self.data = v;
     }
 
     // Mutable pointer to the field.
     // If field is not initialized, it is initialized with default value first.
-    pub fn mut_value(&mut self) -> &mut ::std::string::String {
-        &mut self.value
+    pub fn mut_data(&mut self) -> &mut ::std::string::String {
+        &mut self.data
     }
 
     // Take field
-    pub fn take_value(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.value, ::std::string::String::new())
+    pub fn take_data(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.data, ::std::string::String::new())
     }
 }
 
-impl ::protobuf::Message for GridId {
+impl ::protobuf::Message for CellChangeset {
     fn is_initialized(&self) -> bool {
         true
     }
@@ -3227,7 +3092,16 @@ impl ::protobuf::Message for GridId {
             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.value)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.row_id)?;
+                },
+                3 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?;
+                },
+                4 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -3241,8 +3115,17 @@ impl ::protobuf::Message for GridId {
     #[allow(unused_variables)]
     fn compute_size(&self) -> u32 {
         let mut my_size = 0;
-        if !self.value.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.value);
+        if !self.id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.id);
+        }
+        if !self.row_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.row_id);
+        }
+        if !self.field_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(3, &self.field_id);
+        }
+        if !self.data.is_empty() {
+            my_size += ::protobuf::rt::string_size(4, &self.data);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -3250,8 +3133,17 @@ impl ::protobuf::Message for GridId {
     }
 
     fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        if !self.value.is_empty() {
-            os.write_string(1, &self.value)?;
+        if !self.id.is_empty() {
+            os.write_string(1, &self.id)?;
+        }
+        if !self.row_id.is_empty() {
+            os.write_string(2, &self.row_id)?;
+        }
+        if !self.field_id.is_empty() {
+            os.write_string(3, &self.field_id)?;
+        }
+        if !self.data.is_empty() {
+            os.write_string(4, &self.data)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -3283,8 +3175,8 @@ impl ::protobuf::Message for GridId {
         Self::descriptor_static()
     }
 
-    fn new() -> GridId {
-        GridId::new()
+    fn new() -> CellChangeset {
+        CellChangeset::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -3292,92 +3184,82 @@ impl ::protobuf::Message for GridId {
         descriptor.get(|| {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "value",
-                |m: &GridId| { &m.value },
-                |m: &mut GridId| { &mut m.value },
+                "id",
+                |m: &CellChangeset| { &m.id },
+                |m: &mut CellChangeset| { &mut m.id },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<GridId>(
-                "GridId",
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "row_id",
+                |m: &CellChangeset| { &m.row_id },
+                |m: &mut CellChangeset| { &mut m.row_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "field_id",
+                |m: &CellChangeset| { &m.field_id },
+                |m: &mut CellChangeset| { &mut m.field_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "data",
+                |m: &CellChangeset| { &m.data },
+                |m: &mut CellChangeset| { &mut m.data },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CellChangeset>(
+                "CellChangeset",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static GridId {
-        static instance: ::protobuf::rt::LazyV2<GridId> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(GridId::new)
+    fn default_instance() -> &'static CellChangeset {
+        static instance: ::protobuf::rt::LazyV2<CellChangeset> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(CellChangeset::new)
     }
 }
 
-impl ::protobuf::Clear for GridId {
+impl ::protobuf::Clear for CellChangeset {
     fn clear(&mut self) {
-        self.value.clear();
+        self.id.clear();
+        self.row_id.clear();
+        self.field_id.clear();
+        self.data.clear();
         self.unknown_fields.clear();
     }
 }
 
-impl ::std::fmt::Debug for GridId {
+impl ::std::fmt::Debug for CellChangeset {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for GridId {
+impl ::protobuf::reflect::ProtobufValue for CellChangeset {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
 }
 
 #[derive(PartialEq,Clone,Default)]
-pub struct Filter {
+pub struct CreateGridPayload {
     // message fields
-    pub id: ::std::string::String,
     pub name: ::std::string::String,
-    pub desc: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a Filter {
-    fn default() -> &'a Filter {
-        <Filter as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a CreateGridPayload {
+    fn default() -> &'a CreateGridPayload {
+        <CreateGridPayload as ::protobuf::Message>::default_instance()
     }
 }
 
-impl Filter {
-    pub fn new() -> Filter {
+impl CreateGridPayload {
+    pub fn new() -> CreateGridPayload {
         ::std::default::Default::default()
     }
 
-    // string id = 1;
-
-
-    pub fn get_id(&self) -> &str {
-        &self.id
-    }
-    pub fn clear_id(&mut self) {
-        self.id.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_id(&mut self, v: ::std::string::String) {
-        self.id = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_id(&mut self) -> &mut ::std::string::String {
-        &mut self.id
-    }
-
-    // Take field
-    pub fn take_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.id, ::std::string::String::new())
-    }
-
-    // string name = 2;
+    // string name = 1;
 
 
     pub fn get_name(&self) -> &str {
@@ -3402,35 +3284,9 @@ impl Filter {
     pub fn take_name(&mut self) -> ::std::string::String {
         ::std::mem::replace(&mut self.name, ::std::string::String::new())
     }
-
-    // string desc = 3;
-
-
-    pub fn get_desc(&self) -> &str {
-        &self.desc
-    }
-    pub fn clear_desc(&mut self) {
-        self.desc.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_desc(&mut self, v: ::std::string::String) {
-        self.desc = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_desc(&mut self) -> &mut ::std::string::String {
-        &mut self.desc
-    }
-
-    // Take field
-    pub fn take_desc(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.desc, ::std::string::String::new())
-    }
 }
 
-impl ::protobuf::Message for Filter {
+impl ::protobuf::Message for CreateGridPayload {
     fn is_initialized(&self) -> bool {
         true
     }
@@ -3440,14 +3296,8 @@ impl ::protobuf::Message for Filter {
             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.id)?;
-                },
-                2 => {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name)?;
                 },
-                3 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.desc)?;
-                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -3460,14 +3310,8 @@ impl ::protobuf::Message for Filter {
     #[allow(unused_variables)]
     fn compute_size(&self) -> u32 {
         let mut my_size = 0;
-        if !self.id.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.id);
-        }
         if !self.name.is_empty() {
-            my_size += ::protobuf::rt::string_size(2, &self.name);
-        }
-        if !self.desc.is_empty() {
-            my_size += ::protobuf::rt::string_size(3, &self.desc);
+            my_size += ::protobuf::rt::string_size(1, &self.name);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -3475,14 +3319,8 @@ impl ::protobuf::Message for Filter {
     }
 
     fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        if !self.id.is_empty() {
-            os.write_string(1, &self.id)?;
-        }
         if !self.name.is_empty() {
-            os.write_string(2, &self.name)?;
-        }
-        if !self.desc.is_empty() {
-            os.write_string(3, &self.desc)?;
+            os.write_string(1, &self.name)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -3514,117 +3352,101 @@ impl ::protobuf::Message for Filter {
         Self::descriptor_static()
     }
 
-    fn new() -> Filter {
-        Filter::new()
+    fn new() -> CreateGridPayload {
+        CreateGridPayload::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>(
-                "id",
-                |m: &Filter| { &m.id },
-                |m: &mut Filter| { &mut m.id },
-            ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "name",
-                |m: &Filter| { &m.name },
-                |m: &mut Filter| { &mut m.name },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "desc",
-                |m: &Filter| { &m.desc },
-                |m: &mut Filter| { &mut m.desc },
+                |m: &CreateGridPayload| { &m.name },
+                |m: &mut CreateGridPayload| { &mut m.name },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<Filter>(
-                "Filter",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CreateGridPayload>(
+                "CreateGridPayload",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static Filter {
-        static instance: ::protobuf::rt::LazyV2<Filter> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(Filter::new)
+    fn default_instance() -> &'static CreateGridPayload {
+        static instance: ::protobuf::rt::LazyV2<CreateGridPayload> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(CreateGridPayload::new)
     }
 }
 
-impl ::protobuf::Clear for Filter {
+impl ::protobuf::Clear for CreateGridPayload {
     fn clear(&mut self) {
-        self.id.clear();
         self.name.clear();
-        self.desc.clear();
         self.unknown_fields.clear();
     }
 }
 
-impl ::std::fmt::Debug for Filter {
+impl ::std::fmt::Debug for CreateGridPayload {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for Filter {
+impl ::protobuf::reflect::ProtobufValue for CreateGridPayload {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
 }
 
 #[derive(PartialEq,Clone,Default)]
-pub struct RepeatedFilter {
+pub struct GridId {
     // message fields
-    pub items: ::protobuf::RepeatedField<Filter>,
+    pub value: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a RepeatedFilter {
-    fn default() -> &'a RepeatedFilter {
-        <RepeatedFilter as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a GridId {
+    fn default() -> &'a GridId {
+        <GridId as ::protobuf::Message>::default_instance()
     }
 }
 
-impl RepeatedFilter {
-    pub fn new() -> RepeatedFilter {
+impl GridId {
+    pub fn new() -> GridId {
         ::std::default::Default::default()
     }
 
-    // repeated .Filter items = 1;
+    // string value = 1;
 
 
-    pub fn get_items(&self) -> &[Filter] {
-        &self.items
+    pub fn get_value(&self) -> &str {
+        &self.value
     }
-    pub fn clear_items(&mut self) {
-        self.items.clear();
+    pub fn clear_value(&mut self) {
+        self.value.clear();
     }
 
     // Param is passed by value, moved
-    pub fn set_items(&mut self, v: ::protobuf::RepeatedField<Filter>) {
-        self.items = v;
+    pub fn set_value(&mut self, v: ::std::string::String) {
+        self.value = v;
     }
 
     // Mutable pointer to the field.
-    pub fn mut_items(&mut self) -> &mut ::protobuf::RepeatedField<Filter> {
-        &mut self.items
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_value(&mut self) -> &mut ::std::string::String {
+        &mut self.value
     }
 
     // Take field
-    pub fn take_items(&mut self) -> ::protobuf::RepeatedField<Filter> {
-        ::std::mem::replace(&mut self.items, ::protobuf::RepeatedField::new())
+    pub fn take_value(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.value, ::std::string::String::new())
     }
 }
 
-impl ::protobuf::Message for RepeatedFilter {
+impl ::protobuf::Message for GridId {
     fn is_initialized(&self) -> bool {
-        for v in &self.items {
-            if !v.is_initialized() {
-                return false;
-            }
-        };
         true
     }
 
@@ -3633,7 +3455,7 @@ impl ::protobuf::Message for RepeatedFilter {
             let (field_number, wire_type) = is.read_tag_unpack()?;
             match field_number {
                 1 => {
-                    ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.items)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.value)?;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -3647,21 +3469,18 @@ impl ::protobuf::Message for RepeatedFilter {
     #[allow(unused_variables)]
     fn compute_size(&self) -> u32 {
         let mut my_size = 0;
-        for value in &self.items {
-            let len = value.compute_size();
-            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
-        };
+        if !self.value.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.value);
+        }
         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<()> {
-        for v in &self.items {
-            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
-            os.write_raw_varint32(v.get_cached_size())?;
-            v.write_to_with_cached_sizes(os)?;
-        };
+        if !self.value.is_empty() {
+            os.write_string(1, &self.value)?;
+        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -3692,47 +3511,47 @@ impl ::protobuf::Message for RepeatedFilter {
         Self::descriptor_static()
     }
 
-    fn new() -> RepeatedFilter {
-        RepeatedFilter::new()
+    fn new() -> GridId {
+        GridId::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_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<Filter>>(
-                "items",
-                |m: &RepeatedFilter| { &m.items },
-                |m: &mut RepeatedFilter| { &mut m.items },
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "value",
+                |m: &GridId| { &m.value },
+                |m: &mut GridId| { &mut m.value },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<RepeatedFilter>(
-                "RepeatedFilter",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<GridId>(
+                "GridId",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static RepeatedFilter {
-        static instance: ::protobuf::rt::LazyV2<RepeatedFilter> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(RepeatedFilter::new)
+    fn default_instance() -> &'static GridId {
+        static instance: ::protobuf::rt::LazyV2<GridId> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(GridId::new)
     }
 }
 
-impl ::protobuf::Clear for RepeatedFilter {
+impl ::protobuf::Clear for GridId {
     fn clear(&mut self) {
-        self.items.clear();
+        self.value.clear();
         self.unknown_fields.clear();
     }
 }
 
-impl ::std::fmt::Debug for RepeatedFilter {
+impl ::std::fmt::Debug for GridId {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for RepeatedFilter {
+impl ::protobuf::reflect::ProtobufValue for GridId {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
@@ -3801,11 +3620,10 @@ impl ::protobuf::reflect::ProtobufValue for FieldType {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\ngrid.proto\"\xab\x01\n\x04Grid\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\
-    \x02id\x12)\n\x07filters\x18\x02\x20\x01(\x0b2\x0f.RepeatedFilterR\x07fi\
-    lters\x126\n\x0cfield_orders\x18\x03\x20\x01(\x0b2\x13.RepeatedFieldOrde\
-    rR\x0bfieldOrders\x120\n\nrow_orders\x18\x04\x20\x01(\x0b2\x11.RepeatedR\
-    owOrderR\trowOrders\"G\n\nFieldOrder\x12\x19\n\x08field_id\x18\x01\x20\
+    \n\ngrid.proto\"\x80\x01\n\x04Grid\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\
+    \x02id\x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\x13.RepeatedFieldOrd\
+    erR\x0bfieldOrders\x120\n\nrow_orders\x18\x03\x20\x01(\x0b2\x11.Repeated\
+    RowOrderR\trowOrders\"G\n\nFieldOrder\x12\x19\n\x08field_id\x18\x01\x20\
     \x01(\tR\x07fieldId\x12\x1e\n\nvisibility\x18\x02\x20\x01(\x08R\nvisibil\
     ity\"7\n\x12RepeatedFieldOrder\x12!\n\x05items\x18\x01\x20\x03(\x0b2\x0b\
     .FieldOrderR\x05items\"\xc5\x01\n\x05Field\x12\x0e\n\x02id\x18\x01\x20\
@@ -3835,15 +3653,15 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     y\x18\x01\x20\x01(\tR\x03key\x12\x1b\n\x05value\x18\x02\x20\x01(\x0b2\
     \x05.CellR\x05value:\x028\x01\"K\n\x04Cell\x12\x0e\n\x02id\x18\x01\x20\
     \x01(\tR\x02id\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\x07fieldId\x12\
-    \x18\n\x07content\x18\x03\x20\x01(\tR\x07content\"'\n\x11CreateGridPaylo\
-    ad\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\
-    \x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"@\n\x06Filter\x12\x0e\n\
-    \x02id\x18\x01\x20\x01(\tR\x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\
-    \x04name\x12\x12\n\x04desc\x18\x03\x20\x01(\tR\x04desc\"/\n\x0eRepeatedF\
-    ilter\x12\x1d\n\x05items\x18\x01\x20\x03(\x0b2\x07.FilterR\x05items*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\
+    \x18\n\x07content\x18\x03\x20\x01(\tR\x07content\"e\n\rCellChangeset\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(\tR\x07fieldId\
+    \x12\x12\n\x04data\x18\x04\x20\x01(\tR\x04data\"'\n\x11CreateGridPayload\
+    \x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\
+    \n\x05value\x18\x01\x20\x01(\tR\x05value*d\n\tFieldType\x12\x0c\n\x08Ric\
+    hText\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\
+    \x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\
+    \n\x08Checkbox\x10\x05b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 8 - 11
shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto

@@ -2,9 +2,8 @@ syntax = "proto3";
 
 message Grid {
     string id = 1;
-    RepeatedFilter filters = 2;
-    RepeatedFieldOrder field_orders = 3;
-    RepeatedRowOrder row_orders = 4;
+    RepeatedFieldOrder field_orders = 2;
+    RepeatedRowOrder row_orders = 3;
 }
 message FieldOrder {
     string field_id = 1;
@@ -60,20 +59,18 @@ message Cell {
     string field_id = 2;
     string content = 3;
 }
+message CellChangeset {
+    string id = 1;
+    string row_id = 2;
+    string field_id = 3;
+    string data = 4;
+}
 message CreateGridPayload {
     string name = 1;
 }
 message GridId {
     string value = 1;
 }
-message Filter {
-    string id = 1;
-    string name = 2;
-    string desc = 3;
-}
-message RepeatedFilter {
-    repeated Filter items = 1;
-}
 enum FieldType {
     RichText = 0;
     Number = 1;

+ 58 - 0
shared-lib/flowy-grid-data-model/tests/serde_test.rs

@@ -0,0 +1,58 @@
+use flowy_grid_data_model::entities::*;
+
+#[test]
+fn grid_serde_test() {
+    let grid_id = "1".to_owned();
+    let field_orders = RepeatedFieldOrder {
+        items: vec![create_field_order("1")],
+    };
+    let row_orders = RepeatedRowOrder {
+        items: vec![create_row_order(&grid_id, "1")],
+    };
+
+    let grid = Grid {
+        id: grid_id,
+        field_orders,
+        row_orders,
+    };
+
+    let json = serde_json::to_string(&grid).unwrap();
+    let grid2: Grid = serde_json::from_str(&json).unwrap();
+    assert_eq!(grid, grid2);
+    assert_eq!(
+        json,
+        r#"{"id":"1","field_orders":[{"field_id":"1","visibility":false}],"row_orders":[{"grid_id":"1","row_id":"1","visibility":false}]}"#
+    )
+}
+
+#[test]
+fn grid_default_serde_test() {
+    let grid_id = "1".to_owned();
+    let grid = Grid {
+        id: grid_id,
+        field_orders: RepeatedFieldOrder::default(),
+        row_orders: RepeatedRowOrder::default(),
+    };
+
+    let json = serde_json::to_string(&grid).unwrap();
+    assert_eq!(json, r#"{"id":"1","field_orders":[],"row_orders":[]}"#)
+}
+
+fn create_field_order(field_id: &str) -> FieldOrder {
+    FieldOrder {
+        field_id: field_id.to_owned(),
+        visibility: false,
+    }
+}
+
+fn create_row_order(grid_id: &str, row_id: &str) -> RowOrder {
+    RowOrder {
+        grid_id: grid_id.to_string(),
+        row_id: row_id.to_string(),
+        visibility: false,
+    }
+}
+
+fn uuid() -> String {
+    uuid::Uuid::new_v4().to_string()
+}

+ 1 - 1
shared-lib/lib-infra/src/lib.rs

@@ -3,7 +3,7 @@ pub mod future;
 pub mod retry;
 
 #[allow(dead_code)]
-pub fn uuid_string() -> String {
+pub fn uuid() -> String {
     uuid::Uuid::new_v4().to_string()
 }