浏览代码

Merge pull request #412 from AppFlowy-IO/feat_grid

Create grid/table
Nathan.fooo 3 年之前
父节点
当前提交
a323073244
共有 100 个文件被更改,包括 3896 次插入238 次删除
  1. 32 10
      frontend/app_flowy/lib/plugin/plugin.dart
  2. 10 0
      frontend/app_flowy/lib/startup/home_deps_resolver.dart
  3. 2 19
      frontend/app_flowy/lib/startup/tasks/load_plugin.dart
  4. 2 2
      frontend/app_flowy/lib/workspace/application/doc/doc_bloc.dart
  5. 10 6
      frontend/app_flowy/lib/workspace/application/doc/doc_service.dart
  6. 1 1
      frontend/app_flowy/lib/workspace/application/doc/share_bloc.dart
  7. 1 1
      frontend/app_flowy/lib/workspace/application/doc/share_service.dart
  8. 146 0
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  9. 33 0
      frontend/app_flowy/lib/workspace/application/grid/grid_service.dart
  10. 0 1
      frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart
  11. 10 11
      frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart
  12. 1 1
      frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart
  13. 3 3
      frontend/app_flowy/lib/workspace/presentation/home/navigation.dart
  14. 8 12
      frontend/app_flowy/lib/workspace/presentation/plugins/blank/blank.dart
  15. 14 18
      frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart
  16. 66 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/grid.dart
  17. 73 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/controller/flowy_table_selection.dart
  18. 22 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/controller/grid_scroll.dart
  19. 152 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
  20. 13 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/layout.dart
  21. 16 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart
  22. 17 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_builder.dart
  23. 33 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_container.dart
  24. 10 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_decoration.dart
  25. 100 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_cell.dart
  26. 62 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_row.dart
  27. 47 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/footer/grid_footer.dart
  28. 5 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/constants.dart
  29. 60 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header.dart
  30. 52 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header_cell.dart
  31. 0 1
      frontend/app_flowy/lib/workspace/presentation/plugins/trash/menu.dart
  32. 8 12
      frontend/app_flowy/lib/workspace/presentation/plugins/trash/trash.dart
  33. 3 3
      frontend/app_flowy/packages/flowy_sdk/example/pubspec.lock
  34. 8 0
      frontend/app_flowy/packages/flowy_sdk/example/windows/flutter/generated_plugins.cmake
  35. 54 0
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-block/dart_event.dart
  36. 5 5
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-folder/dart_event.dart
  37. 71 0
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart
  38. 5 0
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart
  39. 3 3
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/entities.pb.dart
  40. 1 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/entities.pbenum.dart
  41. 1 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/entities.pbjson.dart
  42. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/entities.pbserver.dart
  43. 11 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/event_map.pb.dart
  44. 28 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/event_map.pbenum.dart
  45. 22 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/event_map.pbjson.dart
  46. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/event_map.pbserver.dart
  47. 3 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/protobuf.dart
  48. 37 37
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-collaboration/document_info.pb.dart
  49. 10 10
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-collaboration/document_info.pbjson.dart
  50. 0 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/protobuf.dart
  51. 3 3
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/view.pb.dart
  52. 4 4
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/view.pbenum.dart
  53. 3 3
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/view.pbjson.dart
  54. 2 2
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder/event_map.pbenum.dart
  55. 2 2
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder/event_map.pbjson.dart
  56. 1187 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
  57. 34 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbenum.dart
  58. 252 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
  59. 2 2
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbserver.dart
  60. 2 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/protobuf.dart
  61. 458 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_data.pb.dart
  62. 62 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_data.pbenum.dart
  63. 125 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_data.pbjson.dart
  64. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_data.pbserver.dart
  65. 11 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pb.dart
  66. 30 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart
  67. 23 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart
  68. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbserver.dart
  69. 3 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/protobuf.dart
  70. 2 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/lib-ws/msg.pbenum.dart
  71. 2 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/lib-ws/msg.pbjson.dart
  72. 16 2
      frontend/app_flowy/packages/flowy_sdk/pubspec.lock
  73. 88 1
      frontend/rust-lib/Cargo.lock
  74. 1 0
      frontend/rust-lib/Cargo.toml
  75. 2 2
      frontend/rust-lib/dart-ffi/Cargo.toml
  76. 5 1
      frontend/rust-lib/flowy-block/Cargo.toml
  77. 3 0
      frontend/rust-lib/flowy-block/Flowy.toml
  78. 9 0
      frontend/rust-lib/flowy-block/build.rs
  79. 5 5
      frontend/rust-lib/flowy-block/src/block_editor.rs
  80. 0 0
      frontend/rust-lib/flowy-block/src/entities.rs
  81. 42 0
      frontend/rust-lib/flowy-block/src/event_handler.rs
  82. 30 0
      frontend/rust-lib/flowy-block/src/event_map.rs
  83. 6 2
      frontend/rust-lib/flowy-block/src/lib.rs
  84. 17 11
      frontend/rust-lib/flowy-block/src/manager.rs
  85. 4 0
      frontend/rust-lib/flowy-block/src/protobuf/mod.rs
  86. 7 7
      frontend/rust-lib/flowy-block/src/protobuf/model/entities.rs
  87. 95 0
      frontend/rust-lib/flowy-block/src/protobuf/model/event_map.rs
  88. 8 0
      frontend/rust-lib/flowy-block/src/protobuf/model/mod.rs
  89. 0 0
      frontend/rust-lib/flowy-block/src/protobuf/proto/entities.proto
  90. 7 0
      frontend/rust-lib/flowy-block/src/protobuf/proto/event_map.proto
  91. 4 4
      frontend/rust-lib/flowy-block/src/queue.rs
  92. 1 1
      frontend/rust-lib/flowy-block/src/web_socket.rs
  93. 2 2
      frontend/rust-lib/flowy-block/tests/document/edit_script.rs
  94. 1 1
      frontend/rust-lib/flowy-block/tests/editor/attribute_test.rs
  95. 14 14
      frontend/rust-lib/flowy-block/tests/editor/mod.rs
  96. 3 3
      frontend/rust-lib/flowy-block/tests/editor/serde_test.rs
  97. 2 1
      frontend/rust-lib/flowy-database/migrations/2021-09-22-074638_flowy-doc-op/down.sql
  98. 2 0
      frontend/rust-lib/flowy-database/migrations/2022-03-04-101530_flowy-grid/down.sql
  99. 5 0
      frontend/rust-lib/flowy-database/migrations/2022-03-04-101530_flowy-grid/up.sql
  100. 3 5
      frontend/rust-lib/flowy-database/src/lib.rs

+ 32 - 10
frontend/app_flowy/lib/plugin/plugin.dart

@@ -3,27 +3,46 @@ library flowy_plugin;
 import 'package:app_flowy/plugin/plugin.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
+import 'package:flowy_infra/notifier.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:flutter/widgets.dart';
 
 export "./src/sandbox.dart";
 
-typedef PluginType = int;
+enum DefaultPlugin {
+  quill,
+  blank,
+  trash,
+  grid,
+}
 
-typedef PluginDataType = ViewDataType;
+extension FlowyDefaultPluginExt on DefaultPlugin {
+  int type() {
+    switch (this) {
+      case DefaultPlugin.quill:
+        return 0;
+      case DefaultPlugin.blank:
+        return 1;
+      case DefaultPlugin.trash:
+        return 2;
+      case DefaultPlugin.grid:
+        return 3;
+    }
+  }
+}
 
+typedef PluginType = int;
+typedef PluginDataType = ViewDataType;
 typedef PluginId = String;
 
 abstract class Plugin {
-  PluginId get pluginId;
-
-  PluginDisplay get pluginDisplay;
+  PluginId get id;
 
-  PluginType get pluginType;
+  PluginDisplay get display;
 
-  ChangeNotifier? get displayNotifier => null;
+  PluginType get ty;
 
-  void dispose();
+  void dispose() {}
 }
 
 abstract class PluginBuilder {
@@ -33,14 +52,15 @@ abstract class PluginBuilder {
 
   PluginType get pluginType;
 
-  ViewDataType get dataType => ViewDataType.PlainText;
+  ViewDataType get dataType => ViewDataType.Block;
 }
 
 abstract class PluginConfig {
+  // Return false will disable the user to create it. For example, a trash plugin shouldn't be created by the user,
   bool get creatable => true;
 }
 
-abstract class PluginDisplay with NavigationItem {
+abstract class PluginDisplay<T> with NavigationItem {
   @override
   Widget get leftBarItem;
 
@@ -49,6 +69,8 @@ abstract class PluginDisplay with NavigationItem {
 
   List<NavigationItem> get navigationItems;
 
+  PublishNotifier<T>? get notifier => null;
+
   Widget buildWidget();
 }
 

+ 10 - 0
frontend/app_flowy/lib/startup/home_deps_resolver.dart

@@ -7,6 +7,8 @@ import 'package:app_flowy/workspace/application/doc/doc_bloc.dart';
 import 'package:app_flowy/workspace/application/doc/doc_service.dart';
 import 'package:app_flowy/workspace/application/doc/share_bloc.dart';
 import 'package:app_flowy/workspace/application/doc/share_service.dart';
+import 'package:app_flowy/workspace/application/grid/grid_bloc.dart';
+import 'package:app_flowy/workspace/application/grid/grid_service.dart';
 import 'package:app_flowy/workspace/application/home/home_listen_bloc.dart';
 import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
 import 'package:app_flowy/workspace/application/menu/menu_user_bloc.dart';
@@ -97,6 +99,14 @@ class HomeDepsResolver {
       ),
     );
 
+    // Grid
+    getIt.registerFactoryParam<GridBloc, View, void>(
+      (view, _) => GridBloc(
+        view: view,
+        service: GridService(),
+      ),
+    );
+
     // trash
     getIt.registerLazySingleton<TrashService>(() => TrashService());
     getIt.registerLazySingleton<TrashListener>(() => TrashListener());

+ 2 - 19
frontend/app_flowy/lib/startup/tasks/load_plugin.dart

@@ -2,27 +2,9 @@ import 'package:app_flowy/plugin/plugin.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart';
 import 'package:app_flowy/workspace/presentation/plugins/doc/document.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/grid.dart';
 import 'package:app_flowy/workspace/presentation/plugins/trash/trash.dart';
 
-enum DefaultPlugin {
-  quillEditor,
-  blank,
-  trash,
-}
-
-extension FlowyDefaultPluginExt on DefaultPlugin {
-  int type() {
-    switch (this) {
-      case DefaultPlugin.quillEditor:
-        return 0;
-      case DefaultPlugin.blank:
-        return 1;
-      case DefaultPlugin.trash:
-        return 2;
-    }
-  }
-}
-
 class PluginLoadTask extends LaunchTask {
   @override
   LaunchTaskType get type => LaunchTaskType.dataProcessing;
@@ -32,5 +14,6 @@ class PluginLoadTask extends LaunchTask {
     registerPlugin(builder: BlankPluginBuilder(), config: BlankPluginConfig());
     registerPlugin(builder: TrashPluginBuilder(), config: TrashPluginConfig());
     registerPlugin(builder: DocumentPluginBuilder());
+    registerPlugin(builder: GridPluginBuilder(), config: GridPluginConfig());
   }
 }

+ 2 - 2
frontend/app_flowy/lib/workspace/application/doc/doc_bloc.dart

@@ -88,7 +88,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
     final result = await service.openDocument(docId: view.id);
     result.fold(
       (block) {
-        document = _decodeJsonToDocument(block.deltaJson);
+        document = _decodeJsonToDocument(block.deltaStr);
         _subscription = document.changes.listen((event) {
           final delta = event.item2;
           final documentDelta = document.toDelta();
@@ -115,7 +115,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
 
     result.fold((rustDoc) {
       // final json = utf8.decode(doc.data);
-      final rustDelta = Delta.fromJson(jsonDecode(rustDoc.deltaJson));
+      final rustDelta = Delta.fromJson(jsonDecode(rustDoc.deltaStr));
       if (documentDelta != rustDelta) {
         Log.error("Receive : $rustDelta");
         Log.error("Expected : $documentDelta");

+ 10 - 6
frontend/app_flowy/lib/workspace/application/doc/doc_service.dart

@@ -5,16 +5,20 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 
 class DocumentService {
-  Future<Either<BlockDelta, FlowyError>> openDocument({required String docId}) {
-    final request = ViewId(value: docId);
-    return FolderEventOpenView(request).send();
+  Future<Either<BlockDelta, FlowyError>> openDocument({
+    required String docId,
+  }) async {
+    await FolderEventSetLatestView(ViewId(value: docId)).send();
+
+    final payload = BlockId(value: docId);
+    return BlockEventGetBlockData(payload).send();
   }
 
   Future<Either<BlockDelta, FlowyError>> composeDelta({required String docId, required String data}) {
-    final request = BlockDelta.create()
+    final payload = BlockDelta.create()
       ..blockId = docId
-      ..deltaJson = data;
-    return FolderEventApplyDocDelta(request).send();
+      ..deltaStr = data;
+    return FolderEventApplyDocDelta(payload).send();
   }
 
   Future<Either<Unit, FlowyError>> closeDocument({required String docId}) {

+ 1 - 1
frontend/app_flowy/lib/workspace/application/doc/share_bloc.dart

@@ -1,6 +1,6 @@
 import 'package:app_flowy/workspace/application/doc/share_service.dart';
 import 'package:app_flowy/workspace/application/markdown/delta_markdown.dart';
-import 'package:flowy_sdk/protobuf/flowy-folder-data-model/share.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-block/entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';

+ 1 - 1
frontend/app_flowy/lib/workspace/application/doc/share_service.dart

@@ -1,8 +1,8 @@
 import 'dart:async';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
-import 'package:flowy_sdk/protobuf/flowy-folder-data-model/protobuf.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-block/protobuf.dart';
 
 class ShareService {
   Future<Either<ExportData, FlowyError>> export(String docId, ExportType type) {

+ 146 - 0
frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart

@@ -0,0 +1,146 @@
+import 'dart:async';
+
+import 'package:dartz/dartz.dart';
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+import 'grid_service.dart';
+
+part 'grid_bloc.freezed.dart';
+
+class GridBloc extends Bloc<GridEvent, GridState> {
+  final GridService service;
+  final View view;
+  Grid? _grid;
+  List<Field>? _fields;
+
+  GridBloc({required this.view, required this.service}) : super(GridState.initial()) {
+    on<GridEvent>(
+      (event, emit) async {
+        await event.map(
+          initial: (Initial value) async {
+            await _loadGrid(emit);
+            await _loadFields(emit);
+            await _loadGridInfo(emit);
+          },
+          createRow: (_CreateRow value) {
+            service.createRow(gridId: view.id);
+          },
+          delete: (_Delete value) {},
+          rename: (_Rename value) {},
+          updateDesc: (_Desc value) {},
+        );
+      },
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    return super.close();
+  }
+
+  Future<void> _loadGrid(Emitter<GridState> emit) async {
+    final result = await service.openGrid(gridId: view.id);
+    result.fold(
+      (grid) {
+        _grid = grid;
+      },
+      (err) {
+        emit(state.copyWith(loadingState: GridLoadingState.finish(right(err))));
+      },
+    );
+  }
+
+  Future<void> _loadFields(Emitter<GridState> emit) async {
+    if (_grid != null) {
+      final result = await service.getFields(gridId: _grid!.id, fieldOrders: _grid!.fieldOrders);
+      result.fold(
+        (fields) {
+          _fields = fields.items;
+        },
+        (err) {
+          emit(state.copyWith(loadingState: GridLoadingState.finish(right(err))));
+        },
+      );
+    }
+  }
+
+  Future<void> _loadGridInfo(Emitter<GridState> emit) async {
+    if (_grid != null && _fields != null) {
+      final result = await service.getRows(gridId: _grid!.id, rowOrders: _grid!.rowOrders);
+      result.fold((repeatedRow) {
+        final rows = repeatedRow.items;
+        final gridInfo = GridInfo(rows: rows, fields: _fields!);
+        emit(
+          state.copyWith(loadingState: GridLoadingState.finish(left(unit)), gridInfo: some(left(gridInfo))),
+        );
+      }, (err) {
+        emit(
+          state.copyWith(loadingState: GridLoadingState.finish(right(err)), gridInfo: none()),
+        );
+      });
+    }
+  }
+}
+
+@freezed
+abstract class GridEvent with _$GridEvent {
+  const factory GridEvent.initial() = Initial;
+  const factory GridEvent.rename(String gridId, String name) = _Rename;
+  const factory GridEvent.updateDesc(String gridId, String desc) = _Desc;
+  const factory GridEvent.delete(String gridId) = _Delete;
+  const factory GridEvent.createRow() = _CreateRow;
+}
+
+@freezed
+abstract class GridState with _$GridState {
+  const factory GridState({
+    required GridLoadingState loadingState,
+    required Option<Either<GridInfo, FlowyError>> gridInfo,
+  }) = _GridState;
+
+  factory GridState.initial() => GridState(
+        loadingState: const _Loading(),
+        gridInfo: none(),
+      );
+}
+
+@freezed
+class GridLoadingState with _$GridLoadingState {
+  const factory GridLoadingState.loading() = _Loading;
+  const factory GridLoadingState.finish(Either<Unit, FlowyError> successOrFail) = _Finish;
+}
+
+class GridInfo {
+  List<Row> rows;
+  List<Field> fields;
+
+  GridInfo({
+    required this.rows,
+    required this.fields,
+  });
+
+  RowInfo rowInfoAtIndex(int index) {
+    final row = rows[index];
+    return RowInfo(
+      fields: fields,
+      cellMap: row.cellByFieldId,
+    );
+  }
+
+  int numberOfRows() {
+    return rows.length;
+  }
+}
+
+class RowInfo {
+  List<Field> fields;
+  Map<String, Cell> cellMap;
+  RowInfo({
+    required this.fields,
+    required this.cellMap,
+  });
+}

+ 33 - 0
frontend/app_flowy/lib/workspace/application/grid/grid_service.dart

@@ -0,0 +1,33 @@
+import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'package:dartz/dartz.dart';
+
+class GridService {
+  Future<Either<Grid, FlowyError>> openGrid({required String gridId}) async {
+    await FolderEventSetLatestView(ViewId(value: gridId)).send();
+
+    final payload = GridId(value: gridId);
+    return GridEventGetGridData(payload).send();
+  }
+
+  Future<Either<void, FlowyError>> createRow({required String gridId}) {
+    return GridEventCreateRow(GridId(value: gridId)).send();
+  }
+
+  Future<Either<RepeatedRow, FlowyError>> getRows({required String gridId, required RepeatedRowOrder rowOrders}) {
+    final payload = QueryRowPayload.create()
+      ..gridId = gridId
+      ..rowOrders = rowOrders;
+    return GridEventGetRows(payload).send();
+  }
+
+  Future<Either<RepeatedField, FlowyError>> getFields(
+      {required String gridId, required RepeatedFieldOrder fieldOrders}) {
+    final payload = QueryFieldPayload.create()
+      ..gridId = gridId
+      ..fieldOrders = fieldOrders;
+    return GridEventGetFields(payload).send();
+  }
+}

+ 0 - 1
frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart

@@ -1,6 +1,5 @@
 import 'dart:async';
 import 'package:app_flowy/plugin/plugin.dart';
-import 'package:app_flowy/startup/tasks/load_plugin.dart';
 import 'package:app_flowy/workspace/application/workspace/workspace_listener.dart';
 import 'package:app_flowy/workspace/application/workspace/workspace_service.dart';
 import 'package:dartz/dartz.dart';

+ 10 - 11
frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart

@@ -8,7 +8,6 @@ import 'package:time/time.dart';
 import 'package:fluttertoast/fluttertoast.dart';
 
 import 'package:app_flowy/plugin/plugin.dart';
-import 'package:app_flowy/startup/tasks/load_plugin.dart';
 import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart';
 import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
 import 'package:app_flowy/workspace/presentation/home/navigation.dart';
@@ -108,20 +107,20 @@ class HomeStackNotifier extends ChangeNotifier {
   Plugin _plugin;
   PublishNotifier<bool> collapsedNotifier = PublishNotifier();
 
-  Widget get titleWidget => _plugin.pluginDisplay.leftBarItem;
+  Widget get titleWidget => _plugin.display.leftBarItem;
 
   HomeStackNotifier({Plugin? plugin}) : _plugin = plugin ?? makePlugin(pluginType: DefaultPlugin.blank.type());
 
   set plugin(Plugin newPlugin) {
-    if (newPlugin.pluginId == _plugin.pluginId) {
+    if (newPlugin.id == _plugin.id) {
       return;
     }
 
-    _plugin.displayNotifier?.removeListener(notifyListeners);
+    _plugin.display.notifier?.removeListener(notifyListeners);
     _plugin.dispose();
 
     _plugin = newPlugin;
-    _plugin.displayNotifier?.addListener(notifyListeners);
+    _plugin.display.notifier?.addListener(notifyListeners);
     notifyListeners();
   }
 
@@ -134,7 +133,7 @@ class HomeStackManager {
   HomeStackManager();
 
   Widget title() {
-    return _notifier.plugin.pluginDisplay.leftBarItem;
+    return _notifier.plugin.display.leftBarItem;
   }
 
   PublishNotifier<bool> get collapsedNotifier => _notifier.collapsedNotifier;
@@ -166,12 +165,12 @@ class HomeStackManager {
       ],
       child: Consumer(builder: (ctx, HomeStackNotifier notifier, child) {
         return FadingIndexedStack(
-          index: getIt<PluginSandbox>().indexOf(notifier.plugin.pluginType),
+          index: getIt<PluginSandbox>().indexOf(notifier.plugin.ty),
           children: getIt<PluginSandbox>().supportPluginTypes.map((pluginType) {
-            if (pluginType == notifier.plugin.pluginType) {
-              return notifier.plugin.pluginDisplay.buildWidget();
+            if (pluginType == notifier.plugin.ty) {
+              return notifier.plugin.display.buildWidget();
             } else {
-              return const BlankStackPage();
+              return const BlankPage();
             }
           }).toList(),
         );
@@ -198,7 +197,7 @@ class HomeTopBar extends StatelessWidget {
             value: Provider.of<HomeStackNotifier>(context, listen: false),
             child: Consumer(
               builder: (BuildContext context, HomeStackNotifier notifier, Widget? child) {
-                return notifier.plugin.pluginDisplay.rightBarItem ?? const SizedBox();
+                return notifier.plugin.display.rightBarItem ?? const SizedBox();
               },
             ),
           ) // _renderMoreButton(),

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart

@@ -55,7 +55,7 @@ class HomeMenu extends StatelessWidget {
       child: MultiBlocListener(
         listeners: [
           BlocListener<MenuBloc, MenuState>(
-            listenWhen: (p, c) => p.plugin.pluginId != c.plugin.pluginId,
+            listenWhen: (p, c) => p.plugin.id != c.plugin.id,
             listener: (context, state) {
               getIt<HomeStackManager>().setPlugin(state.plugin);
             },

+ 3 - 3
frontend/app_flowy/lib/workspace/presentation/home/navigation.dart

@@ -17,8 +17,8 @@ class NavigationNotifier with ChangeNotifier {
 
   void update(HomeStackNotifier notifier) {
     bool shouldNotify = false;
-    if (navigationItems != notifier.plugin.pluginDisplay.navigationItems) {
-      navigationItems = notifier.plugin.pluginDisplay.navigationItems;
+    if (navigationItems != notifier.plugin.display.navigationItems) {
+      navigationItems = notifier.plugin.display.navigationItems;
       shouldNotify = true;
     }
 
@@ -59,7 +59,7 @@ class FlowyNavigation extends StatelessWidget {
       create: (_) {
         final notifier = Provider.of<HomeStackNotifier>(context, listen: false);
         return NavigationNotifier(
-          navigationItems: notifier.plugin.pluginDisplay.navigationItems,
+          navigationItems: notifier.plugin.display.navigationItems,
           collapasedNotifier: notifier.collapsedNotifier,
         );
       },

+ 8 - 12
frontend/app_flowy/lib/workspace/presentation/plugins/blank/blank.dart

@@ -1,4 +1,3 @@
-import 'package:app_flowy/startup/tasks/load_plugin.dart';
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
@@ -32,16 +31,13 @@ class BlankPagePlugin extends Plugin {
   }) : _pluginType = pluginType;
 
   @override
-  void dispose() {}
+  PluginDisplay get display => BlankPagePluginDisplay();
 
   @override
-  PluginDisplay get pluginDisplay => BlankPagePluginDisplay();
+  PluginId get id => "BlankStack";
 
   @override
-  PluginId get pluginId => "BlankStack";
-
-  @override
-  PluginType get pluginType => _pluginType;
+  PluginType get ty => _pluginType;
 }
 
 class BlankPagePluginDisplay extends PluginDisplay {
@@ -49,20 +45,20 @@ class BlankPagePluginDisplay extends PluginDisplay {
   Widget get leftBarItem => FlowyText.medium(LocaleKeys.blankPageTitle.tr(), fontSize: 12);
 
   @override
-  Widget buildWidget() => const BlankStackPage();
+  Widget buildWidget() => const BlankPage();
 
   @override
   List<NavigationItem> get navigationItems => [this];
 }
 
-class BlankStackPage extends StatefulWidget {
-  const BlankStackPage({Key? key}) : super(key: key);
+class BlankPage extends StatefulWidget {
+  const BlankPage({Key? key}) : super(key: key);
 
   @override
-  State<BlankStackPage> createState() => _BlankStackPageState();
+  State<BlankPage> createState() => _BlankPageState();
 }
 
-class _BlankStackPageState extends State<BlankStackPage> {
+class _BlankPageState extends State<BlankPage> {
   @override
   Widget build(BuildContext context) {
     return SizedBox.expand(

+ 14 - 18
frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart

@@ -7,7 +7,6 @@ export './src/widget/toolbar/tool_bar.dart';
 
 import 'package:app_flowy/plugin/plugin.dart';
 import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/startup/tasks/load_plugin.dart';
 import 'package:app_flowy/workspace/application/appearance.dart';
 import 'package:app_flowy/workspace/application/doc/share_bloc.dart';
 import 'package:app_flowy/workspace/application/view/view_listener.dart';
@@ -16,12 +15,13 @@ import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
 import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
 import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
 import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/notifier.dart';
 import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/widget/rounded_button.dart';
 import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-folder-data-model/share.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-block/entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flutter/material.dart';
@@ -47,16 +47,15 @@ class DocumentPluginBuilder extends PluginBuilder {
   String get menuName => "Doc";
 
   @override
-  PluginType get pluginType => DefaultPlugin.quillEditor.type();
+  PluginType get pluginType => DefaultPlugin.quill.type();
 
   @override
-  ViewDataType get dataType => ViewDataType.RichText;
+  ViewDataType get dataType => ViewDataType.Block;
 }
 
 class DocumentPlugin implements Plugin {
   late View _view;
   ViewListener? _listener;
-  final ValueNotifier<int> _displayNotifier = ValueNotifier<int>(0);
   late PluginType _pluginType;
 
   DocumentPlugin({required PluginType pluginType, required View view, Key? key}) : _view = view {
@@ -66,7 +65,7 @@ class DocumentPlugin implements Plugin {
       result.fold(
         (newView) {
           _view = newView;
-          _displayNotifier.value = _view.hashCode;
+          display.notifier!.value = _view.hashCode;
         },
         (error) {},
       );
@@ -81,19 +80,17 @@ class DocumentPlugin implements Plugin {
   }
 
   @override
-  PluginDisplay get pluginDisplay => DocumentPluginDisplay(view: _view);
+  PluginDisplay<int> get display => DocumentPluginDisplay(view: _view);
 
   @override
-  PluginType get pluginType => _pluginType;
+  PluginType get ty => _pluginType;
 
   @override
-  PluginId get pluginId => _view.id;
-
-  @override
-  ChangeNotifier? get displayNotifier => _displayNotifier;
+  PluginId get id => _view.id;
 }
 
-class DocumentPluginDisplay extends PluginDisplay {
+class DocumentPluginDisplay extends PluginDisplay<int> {
+  final PublishNotifier<int> _displayNotifier = PublishNotifier<int>();
   final View _view;
 
   DocumentPluginDisplay({required View view, Key? key}) : _view = view;
@@ -110,6 +107,9 @@ class DocumentPluginDisplay extends PluginDisplay {
   @override
   List<NavigationItem> get navigationItems => _makeNavigationItems();
 
+  @override
+  PublishNotifier<int>? get notifier => _displayNotifier;
+
   List<NavigationItem> _makeNavigationItems() {
     return [
       this,
@@ -257,7 +257,7 @@ class DocumentShareButton extends StatelessWidget {
             context.read<DocShareBloc>().add(const DocShareEvent.shareMarkdown());
             break;
           case ShareAction.copyLink:
-            showWorkInProgressDialog(context);
+            FlowyAlertDialog(title: LocaleKeys.shareAction_workInProgress.tr()).show(context);
             break;
         }
       });
@@ -269,10 +269,6 @@ class DocumentShareButton extends StatelessWidget {
       anchorOffset: offset,
     );
   }
-
-  void showWorkInProgressDialog(BuildContext context) {
-    FlowyAlertDialog(title: LocaleKeys.shareAction_workInProgress.tr()).show(context);
-  }
 }
 
 class ShareActions with ActionList<ShareActionWrapper> implements FlowyOverlayDelegate {

+ 66 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/grid.dart

@@ -0,0 +1,66 @@
+import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
+import 'package:app_flowy/plugin/plugin.dart';
+import 'package:flutter/material.dart';
+
+import 'src/grid_page.dart';
+
+class GridPluginBuilder implements PluginBuilder {
+  @override
+  Plugin build(dynamic data) {
+    if (data is View) {
+      return GridPlugin(pluginType: pluginType, view: data);
+    } else {
+      throw FlowyPluginException.invalidData;
+    }
+  }
+
+  @override
+  String get menuName => "Grid";
+
+  @override
+  PluginType get pluginType => DefaultPlugin.grid.type();
+
+  @override
+  ViewDataType get dataType => ViewDataType.Grid;
+}
+
+class GridPluginConfig implements PluginConfig {
+  @override
+  bool get creatable => true;
+}
+
+class GridPlugin extends Plugin {
+  final View _view;
+  final PluginType _pluginType;
+
+  GridPlugin({
+    required View view,
+    required PluginType pluginType,
+  })  : _pluginType = pluginType,
+        _view = view;
+
+  @override
+  PluginDisplay get display => GridPluginDisplay(view: _view);
+
+  @override
+  PluginId get id => _view.id;
+
+  @override
+  PluginType get ty => _pluginType;
+}
+
+class GridPluginDisplay extends PluginDisplay {
+  final View _view;
+  GridPluginDisplay({required View view, Key? key}) : _view = view;
+
+  @override
+  Widget get leftBarItem => const FlowyText.medium("Grid demo", fontSize: 12);
+
+  @override
+  Widget buildWidget() => GridPage(view: _view);
+
+  @override
+  List<NavigationItem> get navigationItems => [this];
+}

+ 73 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/controller/flowy_table_selection.dart

@@ -0,0 +1,73 @@
+/// The data structor representing each selection of flowy table.
+enum FlowyTableSelectionType {
+  item,
+  row,
+  col,
+}
+
+class FlowyTableSelectionItem {
+  final FlowyTableSelectionType type;
+  final int? row;
+  final int? column;
+
+  const FlowyTableSelectionItem({
+    required this.type,
+    this.row,
+    this.column,
+  });
+
+  @override
+  String toString() {
+    return '$type($row, $column)';
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    return other is FlowyTableSelectionItem &&
+        type == other.type &&
+        row == other.row &&
+        column == other.column;
+  }
+
+  @override
+  int get hashCode => type.hashCode ^ row.hashCode ^ column.hashCode;
+}
+
+class FlowyTableSelection {
+  Set<FlowyTableSelectionItem> _items = {};
+
+  Set<FlowyTableSelectionItem> get items => _items;
+
+  FlowyTableSelection(
+    this._items,
+  );
+
+  FlowyTableSelection.combine(
+      FlowyTableSelection lhs, FlowyTableSelection rhs) {
+    this..combine(lhs)..combine(rhs);
+  }
+
+  FlowyTableSelection operator +(FlowyTableSelection other) {
+    return this..combine(other);
+  }
+
+  void combine(FlowyTableSelection other) {
+    var totalItems = items..union(other.items);
+    final rows = totalItems
+        .where((ele) => ele.type == FlowyTableSelectionType.row)
+        .map((e) => e.row)
+        .toSet();
+    final cols = totalItems
+        .where((ele) => ele.type == FlowyTableSelectionType.col)
+        .map((e) => e.column)
+        .toSet();
+    totalItems.removeWhere((ele) {
+      return ele.type == FlowyTableSelectionType.item &&
+          (rows.contains(ele.row) || cols.contains(ele.column));
+    });
+    _items = totalItems;
+  }
+}

+ 22 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/controller/grid_scroll.dart

@@ -0,0 +1,22 @@
+import 'package:flutter/material.dart';
+
+class GridScrollController {
+  final ScrollController _verticalController = ScrollController();
+  final ScrollController _horizontalController = ScrollController();
+
+  ScrollController get verticalController => _verticalController;
+  ScrollController get horizontalController => _horizontalController;
+
+  GridScrollController();
+
+  // final SelectionChangeCallback? onSelectionChanged;
+
+  // final ShouldApplySelection? shouldApplySelection;
+
+  // final ScrollCallback? onScroll;
+
+  void dispose() {
+    verticalController.dispose();
+    horizontalController.dispose();
+  }
+}

+ 152 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart

@@ -0,0 +1,152 @@
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/grid/grid_bloc.dart';
+import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
+import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
+import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart';
+import 'package:flowy_infra_ui/widget/error_page.dart';
+import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter/material.dart';
+import 'package:styled_widget/styled_widget.dart';
+
+import 'controller/grid_scroll.dart';
+import 'layout/layout.dart';
+import 'layout/sizes.dart';
+import 'widgets/content/grid_row.dart';
+import 'widgets/footer/grid_footer.dart';
+import 'widgets/header/header.dart';
+
+class GridPage extends StatefulWidget {
+  final View view;
+
+  GridPage({Key? key, required this.view}) : super(key: ValueKey(view.id));
+
+  @override
+  State<GridPage> createState() => _GridPageState();
+}
+
+class _GridPageState extends State<GridPage> {
+  @override
+  Widget build(BuildContext context) {
+    return MultiBlocProvider(
+      providers: [
+        BlocProvider<GridBloc>(
+          create: (context) => getIt<GridBloc>(param1: widget.view)..add(const GridEvent.initial()),
+        ),
+      ],
+      child: BlocBuilder<GridBloc, GridState>(
+        builder: (context, state) {
+          return state.loadingState.map(
+            loading: (_) => const Center(child: CircularProgressIndicator.adaptive()),
+            finish: (result) => result.successOrFail.fold(
+              (_) => const GridBody(),
+              (err) => FlowyErrorPage(err.toString()),
+            ),
+          );
+        },
+      ),
+    );
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  @override
+  void deactivate() {
+    super.deactivate();
+  }
+
+  @override
+  void didUpdateWidget(covariant GridPage oldWidget) {
+    super.didUpdateWidget(oldWidget);
+  }
+}
+
+class GridBody extends StatefulWidget {
+  const GridBody({Key? key}) : super(key: key);
+
+  @override
+  _GridBodyState createState() => _GridBodyState();
+}
+
+class _GridBodyState extends State<GridBody> {
+  final _scrollController = GridScrollController();
+
+  @override
+  void dispose() {
+    _scrollController.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<GridBloc, GridState>(
+      builder: (context, state) {
+        return state.gridInfo.fold(
+          () => const Center(child: CircularProgressIndicator.adaptive()),
+          (some) => some.fold(
+            (gridInfo) => _renderGrid(context, gridInfo),
+            (err) => FlowyErrorPage(err.toString()),
+          ),
+        );
+      },
+    );
+  }
+
+  Widget _renderGrid(BuildContext context, GridInfo gridInfo) {
+    return Stack(
+      children: [
+        StyledSingleChildScrollView(
+          controller: _scrollController.horizontalController,
+          axis: Axis.horizontal,
+          child: SizedBox(
+            width: GridLayout.headerWidth(gridInfo.fields),
+            child: CustomScrollView(
+              physics: StyledScrollPhysics(),
+              controller: _scrollController.verticalController,
+              slivers: <Widget>[
+                _buildHeader(gridInfo.fields),
+                _buildRows(gridInfo),
+                _builderFooter(context),
+              ],
+            ),
+          ),
+        ),
+        ScrollbarListStack(
+          axis: Axis.vertical,
+          controller: _scrollController.verticalController,
+          barSize: GridSize.scrollBarSize,
+          child: Container(),
+        ).padding(right: 0, top: GridSize.headerHeight, bottom: GridSize.scrollBarSize),
+      ],
+    );
+  }
+
+  Widget _buildHeader(List<Field> fields) {
+    return SliverPersistentHeader(
+      delegate: GridHeaderDelegate(fields),
+      floating: true,
+      pinned: true,
+    );
+  }
+
+  Widget _buildRows(GridInfo gridInfo) {
+    return SliverList(
+      delegate: SliverChildBuilderDelegate((context, index) {
+        final rowInfo = gridInfo.rowInfoAtIndex(index);
+        return RepaintBoundary(child: GridRowWidget(rowInfo));
+      }, childCount: gridInfo.numberOfRows()),
+    );
+  }
+
+  Widget _builderFooter(BuildContext context) {
+    return GridFooter(
+      onAddRow: () {
+        context.read<GridBloc>().add(const GridEvent.createRow());
+      },
+    );
+  }
+}

+ 13 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/layout.dart

@@ -0,0 +1,13 @@
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+
+import 'sizes.dart';
+
+class GridLayout {
+  static double headerWidth(List<Field> fields) {
+    if (fields.isEmpty) return 0;
+
+    final fieldsWidth = fields.map((field) => field.width.toDouble()).reduce((value, element) => value + element);
+
+    return fieldsWidth + GridSize.firstHeaderPadding;
+  }
+}

+ 16 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart

@@ -0,0 +1,16 @@
+class GridInsets {
+  static double scale = 1;
+
+  static double get horizontal => 6 * scale;
+  static double get vertical => 6 * scale;
+}
+
+class GridSize {
+  static double scale = 1;
+
+  static double get scrollBarSize => 12 * scale;
+  static double get headerHeight => 50 * scale;
+  static double get rowHeight => 50 * scale;
+  static double get footerHeight => 40 * scale;
+  static double get firstHeaderPadding => 20 * scale;
+}

+ 17 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_builder.dart

@@ -0,0 +1,17 @@
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'grid_cell.dart';
+
+class GridCellBuilder {
+  static GridCellWidget buildCell(Field? field, Cell? cell) {
+    if (field == null || cell == null) {
+      return const BlankCell();
+    }
+
+    switch (field.fieldType) {
+      case FieldType.RichText:
+        return GridTextCell(cell.content);
+      default:
+        return const BlankCell();
+    }
+  }
+}

+ 33 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_container.dart

@@ -0,0 +1,33 @@
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:flowy_infra_ui/widget/mouse_hover_builder.dart';
+import 'package:flutter/material.dart';
+import 'cell_decoration.dart';
+import 'grid_cell.dart';
+
+class CellContainer extends StatelessWidget {
+  final GridCellWidget child;
+  final double width;
+  const CellContainer({Key? key, required this.child, required this.width}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return GestureDetector(
+      behavior: HitTestBehavior.translucent,
+      onTap: () {
+        // context
+        //     .read<HomeBloc>()
+        //     .add(HomeEvent.setEditPannel(CellEditPannelContext()));
+      },
+      child: MouseHoverBuilder(
+        builder: (_, isHovered) => Container(
+          width: width,
+          decoration: CellDecoration.box(
+            color: isHovered ? Colors.red.withOpacity(.1) : Colors.transparent,
+          ),
+          padding: EdgeInsets.symmetric(vertical: GridInsets.vertical, horizontal: GridInsets.horizontal),
+          child: child,
+        ),
+      ),
+    );
+  }
+}

+ 10 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_decoration.dart

@@ -0,0 +1,10 @@
+import 'package:flutter/material.dart';
+
+class CellDecoration {
+  static BoxDecoration box({required Color color}) {
+    return BoxDecoration(
+      border: Border.all(color: Colors.black26, width: 0.2),
+      color: color,
+    );
+  }
+}

+ 100 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_cell.dart

@@ -0,0 +1,100 @@
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:flowy_infra_ui/widget/mouse_hover_builder.dart';
+import 'package:flutter/material.dart';
+
+import 'cell_decoration.dart';
+// ignore: import_of_legacy_library_into_null_safe
+
+/// The interface of base cell.
+abstract class GridCellWidget extends StatelessWidget {
+  final canSelect = true;
+
+  const GridCellWidget({Key? key}) : super(key: key);
+}
+
+class GridTextCell extends GridCellWidget {
+  final String content;
+  const GridTextCell(this.content, {Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Text(content);
+  }
+}
+
+class DateCell extends GridCellWidget {
+  final String content;
+  const DateCell(this.content, {Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Text(content);
+  }
+}
+
+class NumberCell extends GridCellWidget {
+  final String content;
+  const NumberCell(this.content, {Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Text(content);
+  }
+}
+
+class SingleSelectCell extends GridCellWidget {
+  final String content;
+  const SingleSelectCell(this.content, {Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Text(content);
+  }
+}
+
+class MultiSelectCell extends GridCellWidget {
+  final String content;
+  const MultiSelectCell(this.content, {Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Text(content);
+  }
+}
+
+class BlankCell extends GridCellWidget {
+  const BlankCell({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container();
+  }
+}
+
+class RowLeading extends StatelessWidget {
+  const RowLeading({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    // return Expanded(
+    //   child: Container(
+    //     color: Colors.white10,
+    //     width: GridSize.firstHeaderPadding,
+    //   ),
+    // );
+
+    return GestureDetector(
+      behavior: HitTestBehavior.translucent,
+      onTap: () {},
+      child: MouseHoverBuilder(
+        builder: (_, isHovered) => Container(
+          width: GridSize.firstHeaderPadding,
+          decoration: CellDecoration.box(
+            color: isHovered ? Colors.red.withOpacity(.1) : Colors.white,
+          ),
+          padding: EdgeInsets.symmetric(vertical: GridInsets.vertical, horizontal: GridInsets.horizontal),
+        ),
+      ),
+    );
+  }
+}

+ 62 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_row.dart

@@ -0,0 +1,62 @@
+import 'package:app_flowy/workspace/application/grid/grid_bloc.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row;
+import 'package:flutter/material.dart';
+import 'cell_builder.dart';
+import 'cell_container.dart';
+import 'grid_cell.dart';
+
+class GridRowContext {
+  final RepeatedFieldOrder fieldOrders;
+  final Map<String, Field> fieldById;
+  final Map<String, Cell> cellByFieldId;
+  GridRowContext(this.fieldOrders, this.fieldById, this.cellByFieldId);
+}
+
+class GridRowWidget extends StatelessWidget {
+  final RowInfo rowInfo;
+  final Function(bool)? onHoverChange;
+  const GridRowWidget(this.rowInfo, {Key? key, this.onHoverChange}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      height: GridSize.rowHeight,
+      child: _buildRowBody(),
+    );
+  }
+
+  Widget _buildRowBody() {
+    Widget rowWidget = Row(
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: _buildCells(),
+    );
+
+    if (onHoverChange != null) {
+      rowWidget = MouseRegion(
+        onEnter: (event) => onHoverChange!(true),
+        onExit: (event) => onHoverChange!(false),
+        cursor: MouseCursor.uncontrolled,
+        child: rowWidget,
+      );
+    }
+
+    return rowWidget;
+  }
+
+  List<Widget> _buildCells() {
+    var cells = List<Widget>.empty(growable: true);
+    cells.add(const RowLeading());
+
+    for (var field in rowInfo.fields) {
+      final data = rowInfo.cellMap[field.id];
+      final cell = CellContainer(
+        width: field.width.toDouble(),
+        child: GridCellBuilder.buildCell(field, data),
+      );
+
+      cells.add(cell);
+    }
+    return cells;
+  }
+}

+ 47 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/footer/grid_footer.dart

@@ -0,0 +1,47 @@
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:flowy_infra_ui/widget/mouse_hover_builder.dart';
+import 'package:flutter/material.dart';
+
+import '../content/cell_decoration.dart';
+
+class GridFooter extends StatelessWidget {
+  final VoidCallback? onAddRow;
+  const GridFooter({Key? key, required this.onAddRow}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return SliverToBoxAdapter(
+      child: SizedBox(
+        height: GridSize.footerHeight,
+        child: Row(
+          children: [
+            AddRowButton(onTap: onAddRow),
+          ],
+        ),
+      ),
+    );
+  }
+}
+
+class AddRowButton extends StatelessWidget {
+  final VoidCallback? onTap;
+  const AddRowButton({Key? key, required this.onTap}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return GestureDetector(
+      behavior: HitTestBehavior.translucent,
+      onTap: onTap,
+      child: MouseHoverBuilder(
+        builder: (_, isHovered) => Container(
+          width: GridSize.firstHeaderPadding,
+          height: GridSize.footerHeight,
+          decoration: CellDecoration.box(
+            color: isHovered ? Colors.red.withOpacity(.1) : Colors.white,
+          ),
+          child: const Icon(Icons.add, size: 16),
+        ),
+      ),
+    );
+  }
+}

+ 5 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/constants.dart

@@ -0,0 +1,5 @@
+import 'package:flutter/material.dart';
+
+class GridHeaderConstants {
+  static Color get backgroundColor => Colors.grey;
+}

+ 60 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header.dart

@@ -0,0 +1,60 @@
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row;
+import 'package:flutter/material.dart';
+
+import 'header_cell.dart';
+
+class GridHeaderDelegate extends SliverPersistentHeaderDelegate {
+  final List<Field> fields;
+
+  GridHeaderDelegate(this.fields);
+
+  @override
+  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
+    return GridHeader(fields: fields);
+  }
+
+  @override
+  double get maxExtent => GridSize.headerHeight;
+
+  @override
+  double get minExtent => GridSize.headerHeight;
+
+  @override
+  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
+    if (oldDelegate is GridHeaderDelegate) {
+      return fields != oldDelegate.fields;
+    }
+    return false;
+  }
+}
+
+class GridHeader extends StatelessWidget {
+  final List<Field> fields;
+
+  const GridHeader({required this.fields, Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final headers = List<Widget>.empty(growable: true);
+    fields.asMap().forEach((index, field) {
+      final header = HeaderCellContainer(
+        width: field.width.toDouble(),
+        child: HeaderCell(
+          field,
+        ),
+      );
+
+      //
+      headers.add(header);
+    });
+
+    return Row(
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: [
+        const HeaderCellLeading(),
+        ...headers,
+      ],
+    );
+  }
+}

+ 52 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header_cell.dart

@@ -0,0 +1,52 @@
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'package:flutter/material.dart';
+import 'constants.dart';
+
+class HeaderCell extends StatelessWidget {
+  final Field field;
+  const HeaderCell(this.field, {Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Text(
+      field.name,
+      style: const TextStyle(fontSize: 15.0, color: Colors.black),
+    );
+  }
+}
+
+class HeaderCellContainer extends StatelessWidget {
+  final HeaderCell child;
+  final double width;
+  const HeaderCellContainer({Key? key, required this.child, required this.width}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return GestureDetector(
+      behavior: HitTestBehavior.translucent,
+      onTap: () {},
+      child: Container(
+        width: width,
+        decoration: BoxDecoration(
+          border: Border.all(color: Colors.black26, width: 0.5),
+          color: GridHeaderConstants.backgroundColor,
+        ),
+        padding: EdgeInsets.symmetric(vertical: GridInsets.vertical, horizontal: GridInsets.horizontal),
+        child: child,
+      ),
+    );
+  }
+}
+
+class HeaderCellLeading extends StatelessWidget {
+  const HeaderCellLeading({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      width: GridSize.firstHeaderPadding,
+      color: GridHeaderConstants.backgroundColor,
+    );
+  }
+}

+ 0 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/trash/menu.dart

@@ -1,6 +1,5 @@
 import 'package:app_flowy/plugin/plugin.dart';
 import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/startup/tasks/load_plugin.dart';
 import 'package:app_flowy/workspace/application/appearance.dart';
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
 import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';

+ 8 - 12
frontend/app_flowy/lib/workspace/presentation/plugins/trash/trash.dart

@@ -4,7 +4,6 @@ export "./src/trash_header.dart";
 
 import 'package:app_flowy/plugin/plugin.dart';
 import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/startup/tasks/load_plugin.dart';
 import 'package:app_flowy/workspace/application/trash/trash_bloc.dart';
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
 import 'package:easy_localization/easy_localization.dart';
@@ -49,16 +48,13 @@ class TrashPlugin extends Plugin {
   TrashPlugin({required PluginType pluginType}) : _pluginType = pluginType;
 
   @override
-  void dispose() {}
+  PluginDisplay get display => TrashPluginDisplay();
 
   @override
-  PluginDisplay get pluginDisplay => TrashPluginDisplay();
+  PluginId get id => "TrashStack";
 
   @override
-  PluginId get pluginId => "TrashStack";
-
-  @override
-  PluginType get pluginType => _pluginType;
+  PluginType get ty => _pluginType;
 }
 
 class TrashPluginDisplay extends PluginDisplay {
@@ -69,20 +65,20 @@ class TrashPluginDisplay extends PluginDisplay {
   Widget? get rightBarItem => null;
 
   @override
-  Widget buildWidget() => const TrashStackPage(key: ValueKey('TrashStackPage'));
+  Widget buildWidget() => const TrashPage(key: ValueKey('TrashPage'));
 
   @override
   List<NavigationItem> get navigationItems => [this];
 }
 
-class TrashStackPage extends StatefulWidget {
-  const TrashStackPage({Key? key}) : super(key: key);
+class TrashPage extends StatefulWidget {
+  const TrashPage({Key? key}) : super(key: key);
 
   @override
-  State<TrashStackPage> createState() => _TrashStackPageState();
+  State<TrashPage> createState() => _TrashPageState();
 }
 
-class _TrashStackPageState extends State<TrashStackPage> {
+class _TrashPageState extends State<TrashPage> {
   final ScrollController _scrollController = ScrollController();
   @override
   Widget build(BuildContext context) {

+ 3 - 3
frontend/app_flowy/packages/flowy_sdk/example/pubspec.lock

@@ -7,7 +7,7 @@ packages:
       name: archive
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "3.1.6"
+    version: "3.1.8"
   async:
     dependency: transitive
     description:
@@ -200,7 +200,7 @@ packages:
       name: path
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.8.0"
+    version: "1.8.1"
   platform:
     dependency: transitive
     description:
@@ -275,7 +275,7 @@ packages:
       name: test_api
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.4.8"
+    version: "0.4.9"
   typed_data:
     dependency: transitive
     description:

+ 8 - 0
frontend/app_flowy/packages/flowy_sdk/example/windows/flutter/generated_plugins.cmake

@@ -6,6 +6,9 @@ list(APPEND FLUTTER_PLUGIN_LIST
   flowy_sdk
 )
 
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+)
+
 set(PLUGIN_BUNDLED_LIBRARIES)
 
 foreach(plugin ${FLUTTER_PLUGIN_LIST})
@@ -14,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
   list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
   list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
 endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)

+ 54 - 0
frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-block/dart_event.dart

@@ -0,0 +1,54 @@
+
+/// Auto generate. Do not edit
+part of '../../dispatch.dart';
+class BlockEventGetBlockData {
+     BlockId request;
+     BlockEventGetBlockData(this.request);
+
+    Future<Either<BlockDelta, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = BlockEvent.GetBlockData.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(BlockDelta.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
+class BlockEventApplyDelta {
+     BlockDelta request;
+     BlockEventApplyDelta(this.request);
+
+    Future<Either<BlockDelta, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = BlockEvent.ApplyDelta.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(BlockDelta.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
+class BlockEventExportDocument {
+     ExportPayload request;
+     BlockEventExportDocument(this.request);
+
+    Future<Either<ExportData, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = BlockEvent.ExportDocument.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(ExportData.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+

+ 5 - 5
frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-folder/dart_event.dart

@@ -267,18 +267,18 @@ class FolderEventCopyLink {
     }
 }
 
-class FolderEventOpenView {
+class FolderEventSetLatestView {
      ViewId request;
-     FolderEventOpenView(this.request);
+     FolderEventSetLatestView(this.request);
 
-    Future<Either<BlockDelta, FlowyError>> send() {
+    Future<Either<Unit, FlowyError>> send() {
     final request = FFIRequest.create()
-          ..event = FolderEvent.OpenView.toString()
+          ..event = FolderEvent.SetLatestView.toString()
           ..payload = requestToBytes(this.request);
 
     return Dispatch.asyncRequest(request)
         .then((bytesResult) => bytesResult.fold(
-           (okBytes) => left(BlockDelta.fromBuffer(okBytes)),
+           (bytes) => left(unit),
            (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
     }

+ 71 - 0
frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart

@@ -0,0 +1,71 @@
+
+/// Auto generate. Do not edit
+part of '../../dispatch.dart';
+class GridEventGetGridData {
+     GridId request;
+     GridEventGetGridData(this.request);
+
+    Future<Either<Grid, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.GetGridData.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(Grid.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
+class GridEventGetRows {
+     QueryRowPayload request;
+     GridEventGetRows(this.request);
+
+    Future<Either<RepeatedRow, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.GetRows.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(RepeatedRow.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
+class GridEventGetFields {
+     QueryFieldPayload request;
+     GridEventGetFields(this.request);
+
+    Future<Either<RepeatedField, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.GetFields.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(RepeatedField.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
+class GridEventCreateRow {
+     GridId request;
+     GridEventCreateRow(this.request);
+
+    Future<Either<Unit, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.CreateRow.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+

+ 5 - 0
frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart

@@ -9,6 +9,7 @@ import 'package:flowy_sdk/protobuf/flowy-net/event.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-net/network_state.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/event_map.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/event_map.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/event_map.pb.dart';
 import 'package:isolates/isolates.dart';
 import 'package:isolates/ports.dart';
 import 'package:ffi/ffi.dart';
@@ -20,7 +21,9 @@ import 'package:flowy_sdk/ffi.dart' as ffi;
 import 'package:flowy_sdk/protobuf/flowy-user-data-model/protobuf.dart';
 import 'package:flowy_sdk/protobuf/dart-ffi/protobuf.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/protobuf.dart';
+import 'package:flowy_sdk/protobuf/flowy-block/protobuf.dart';
 import 'package:flowy_sdk/protobuf/flowy-collaboration/protobuf.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
 
 // ignore: unused_import
 import 'package:protobuf/protobuf.dart';
@@ -30,6 +33,8 @@ import 'error.dart';
 part 'dart_event/flowy-folder/dart_event.dart';
 part 'dart_event/flowy-net/dart_event.dart';
 part 'dart_event/flowy-user/dart_event.dart';
+part 'dart_event/flowy-grid/dart_event.dart';
+part 'dart_event/flowy-block/dart_event.dart';
 
 enum FFIException {
   RequestIsEmpty,

+ 3 - 3
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/share.pb.dart → frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/entities.pb.dart

@@ -1,6 +1,6 @@
 ///
 //  Generated code. Do not modify.
-//  source: share.proto
+//  source: entities.proto
 //
 // @dart = 2.12
 // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
@@ -9,9 +9,9 @@ import 'dart:core' as $core;
 
 import 'package:protobuf/protobuf.dart' as $pb;
 
-import 'share.pbenum.dart';
+import 'entities.pbenum.dart';
 
-export 'share.pbenum.dart';
+export 'entities.pbenum.dart';
 
 class ExportPayload extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ExportPayload', createEmptyInstance: create)

+ 1 - 1
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/share.pbenum.dart → frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/entities.pbenum.dart

@@ -1,6 +1,6 @@
 ///
 //  Generated code. Do not modify.
-//  source: share.proto
+//  source: entities.proto
 //
 // @dart = 2.12
 // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields

+ 1 - 1
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/share.pbjson.dart → frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/entities.pbjson.dart

@@ -1,6 +1,6 @@
 ///
 //  Generated code. Do not modify.
-//  source: share.proto
+//  source: entities.proto
 //
 // @dart = 2.12
 // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package

+ 9 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/entities.pbserver.dart

@@ -0,0 +1,9 @@
+///
+//  Generated code. Do not modify.
+//  source: entities.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+export 'entities.pb.dart';
+

+ 11 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/event_map.pb.dart

@@ -0,0 +1,11 @@
+///
+//  Generated code. Do not modify.
+//  source: event_map.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+import 'dart:core' as $core;
+
+export 'event_map.pbenum.dart';
+

+ 28 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/event_map.pbenum.dart

@@ -0,0 +1,28 @@
+///
+//  Generated code. Do not modify.
+//  source: event_map.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+// ignore_for_file: UNDEFINED_SHOWN_NAME
+import 'dart:core' as $core;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class BlockEvent extends $pb.ProtobufEnum {
+  static const BlockEvent GetBlockData = BlockEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetBlockData');
+  static const BlockEvent ApplyDelta = BlockEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ApplyDelta');
+  static const BlockEvent ExportDocument = BlockEvent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ExportDocument');
+
+  static const $core.List<BlockEvent> values = <BlockEvent> [
+    GetBlockData,
+    ApplyDelta,
+    ExportDocument,
+  ];
+
+  static final $core.Map<$core.int, BlockEvent> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static BlockEvent? valueOf($core.int value) => _byValue[value];
+
+  const BlockEvent._($core.int v, $core.String n) : super(v, n);
+}
+

+ 22 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/event_map.pbjson.dart

@@ -0,0 +1,22 @@
+///
+//  Generated code. Do not modify.
+//  source: event_map.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+import 'dart:core' as $core;
+import 'dart:convert' as $convert;
+import 'dart:typed_data' as $typed_data;
+@$core.Deprecated('Use blockEventDescriptor instead')
+const BlockEvent$json = const {
+  '1': 'BlockEvent',
+  '2': const [
+    const {'1': 'GetBlockData', '2': 0},
+    const {'1': 'ApplyDelta', '2': 1},
+    const {'1': 'ExportDocument', '2': 2},
+  ],
+};
+
+/// Descriptor for `BlockEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List blockEventDescriptor = $convert.base64Decode('CgpCbG9ja0V2ZW50EhAKDEdldEJsb2NrRGF0YRAAEg4KCkFwcGx5RGVsdGEQARISCg5FeHBvcnREb2N1bWVudBAC');

+ 9 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/event_map.pbserver.dart

@@ -0,0 +1,9 @@
+///
+//  Generated code. Do not modify.
+//  source: event_map.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+export 'event_map.pb.dart';
+

+ 3 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-block/protobuf.dart

@@ -0,0 +1,3 @@
+// Auto-generated, do not edit 
+export './entities.pb.dart';
+export './event_map.pb.dart';

+ 37 - 37
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-collaboration/document_info.pb.dart

@@ -77,7 +77,7 @@ class CreateBlockParams extends $pb.GeneratedMessage {
 
 class BlockInfo extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'BlockInfo', createEmptyInstance: create)
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'docId')
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blockId')
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'text')
     ..aInt64(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'revId')
     ..aInt64(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'baseRevId')
@@ -86,14 +86,14 @@ class BlockInfo extends $pb.GeneratedMessage {
 
   BlockInfo._() : super();
   factory BlockInfo({
-    $core.String? docId,
+    $core.String? blockId,
     $core.String? text,
     $fixnum.Int64? revId,
     $fixnum.Int64? baseRevId,
   }) {
     final _result = create();
-    if (docId != null) {
-      _result.docId = docId;
+    if (blockId != null) {
+      _result.blockId = blockId;
     }
     if (text != null) {
       _result.text = text;
@@ -128,13 +128,13 @@ class BlockInfo extends $pb.GeneratedMessage {
   static BlockInfo? _defaultInstance;
 
   @$pb.TagNumber(1)
-  $core.String get docId => $_getSZ(0);
+  $core.String get blockId => $_getSZ(0);
   @$pb.TagNumber(1)
-  set docId($core.String v) { $_setString(0, v); }
+  set blockId($core.String v) { $_setString(0, v); }
   @$pb.TagNumber(1)
-  $core.bool hasDocId() => $_has(0);
+  $core.bool hasBlockId() => $_has(0);
   @$pb.TagNumber(1)
-  void clearDocId() => clearField(1);
+  void clearBlockId() => clearField(1);
 
   @$pb.TagNumber(2)
   $core.String get text => $_getSZ(1);
@@ -164,56 +164,56 @@ class BlockInfo extends $pb.GeneratedMessage {
   void clearBaseRevId() => clearField(4);
 }
 
-class ResetDocumentParams extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ResetDocumentParams', createEmptyInstance: create)
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'docId')
+class ResetBlockParams extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ResetBlockParams', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blockId')
     ..aOM<$0.RepeatedRevision>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'revisions', subBuilder: $0.RepeatedRevision.create)
     ..hasRequiredFields = false
   ;
 
-  ResetDocumentParams._() : super();
-  factory ResetDocumentParams({
-    $core.String? docId,
+  ResetBlockParams._() : super();
+  factory ResetBlockParams({
+    $core.String? blockId,
     $0.RepeatedRevision? revisions,
   }) {
     final _result = create();
-    if (docId != null) {
-      _result.docId = docId;
+    if (blockId != null) {
+      _result.blockId = blockId;
     }
     if (revisions != null) {
       _result.revisions = revisions;
     }
     return _result;
   }
-  factory ResetDocumentParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory ResetDocumentParams.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory ResetBlockParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory ResetBlockParams.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')
-  ResetDocumentParams clone() => ResetDocumentParams()..mergeFromMessage(this);
+  ResetBlockParams clone() => ResetBlockParams()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  ResetDocumentParams copyWith(void Function(ResetDocumentParams) updates) => super.copyWith((message) => updates(message as ResetDocumentParams)) as ResetDocumentParams; // ignore: deprecated_member_use
+  ResetBlockParams copyWith(void Function(ResetBlockParams) updates) => super.copyWith((message) => updates(message as ResetBlockParams)) as ResetBlockParams; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static ResetDocumentParams create() => ResetDocumentParams._();
-  ResetDocumentParams createEmptyInstance() => create();
-  static $pb.PbList<ResetDocumentParams> createRepeated() => $pb.PbList<ResetDocumentParams>();
+  static ResetBlockParams create() => ResetBlockParams._();
+  ResetBlockParams createEmptyInstance() => create();
+  static $pb.PbList<ResetBlockParams> createRepeated() => $pb.PbList<ResetBlockParams>();
   @$core.pragma('dart2js:noInline')
-  static ResetDocumentParams getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ResetDocumentParams>(create);
-  static ResetDocumentParams? _defaultInstance;
+  static ResetBlockParams getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ResetBlockParams>(create);
+  static ResetBlockParams? _defaultInstance;
 
   @$pb.TagNumber(1)
-  $core.String get docId => $_getSZ(0);
+  $core.String get blockId => $_getSZ(0);
   @$pb.TagNumber(1)
-  set docId($core.String v) { $_setString(0, v); }
+  set blockId($core.String v) { $_setString(0, v); }
   @$pb.TagNumber(1)
-  $core.bool hasDocId() => $_has(0);
+  $core.bool hasBlockId() => $_has(0);
   @$pb.TagNumber(1)
-  void clearDocId() => clearField(1);
+  void clearBlockId() => clearField(1);
 
   @$pb.TagNumber(2)
   $0.RepeatedRevision get revisions => $_getN(1);
@@ -230,21 +230,21 @@ class ResetDocumentParams extends $pb.GeneratedMessage {
 class BlockDelta extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'BlockDelta', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blockId')
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deltaJson')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deltaStr')
     ..hasRequiredFields = false
   ;
 
   BlockDelta._() : super();
   factory BlockDelta({
     $core.String? blockId,
-    $core.String? deltaJson,
+    $core.String? deltaStr,
   }) {
     final _result = create();
     if (blockId != null) {
       _result.blockId = blockId;
     }
-    if (deltaJson != null) {
-      _result.deltaJson = deltaJson;
+    if (deltaStr != null) {
+      _result.deltaStr = deltaStr;
     }
     return _result;
   }
@@ -279,13 +279,13 @@ class BlockDelta extends $pb.GeneratedMessage {
   void clearBlockId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.String get deltaJson => $_getSZ(1);
+  $core.String get deltaStr => $_getSZ(1);
   @$pb.TagNumber(2)
-  set deltaJson($core.String v) { $_setString(1, v); }
+  set deltaStr($core.String v) { $_setString(1, v); }
   @$pb.TagNumber(2)
-  $core.bool hasDeltaJson() => $_has(1);
+  $core.bool hasDeltaStr() => $_has(1);
   @$pb.TagNumber(2)
-  void clearDeltaJson() => clearField(2);
+  void clearDeltaStr() => clearField(2);
 }
 
 class NewDocUser extends $pb.GeneratedMessage {

+ 10 - 10
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-collaboration/document_info.pbjson.dart

@@ -23,7 +23,7 @@ final $typed_data.Uint8List createBlockParamsDescriptor = $convert.base64Decode(
 const BlockInfo$json = const {
   '1': 'BlockInfo',
   '2': const [
-    const {'1': 'doc_id', '3': 1, '4': 1, '5': 9, '10': 'docId'},
+    const {'1': 'block_id', '3': 1, '4': 1, '5': 9, '10': 'blockId'},
     const {'1': 'text', '3': 2, '4': 1, '5': 9, '10': 'text'},
     const {'1': 'rev_id', '3': 3, '4': 1, '5': 3, '10': 'revId'},
     const {'1': 'base_rev_id', '3': 4, '4': 1, '5': 3, '10': 'baseRevId'},
@@ -31,29 +31,29 @@ const BlockInfo$json = const {
 };
 
 /// Descriptor for `BlockInfo`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List blockInfoDescriptor = $convert.base64Decode('CglCbG9ja0luZm8SFQoGZG9jX2lkGAEgASgJUgVkb2NJZBISCgR0ZXh0GAIgASgJUgR0ZXh0EhUKBnJldl9pZBgDIAEoA1IFcmV2SWQSHgoLYmFzZV9yZXZfaWQYBCABKANSCWJhc2VSZXZJZA==');
-@$core.Deprecated('Use resetDocumentParamsDescriptor instead')
-const ResetDocumentParams$json = const {
-  '1': 'ResetDocumentParams',
+final $typed_data.Uint8List blockInfoDescriptor = $convert.base64Decode('CglCbG9ja0luZm8SGQoIYmxvY2tfaWQYASABKAlSB2Jsb2NrSWQSEgoEdGV4dBgCIAEoCVIEdGV4dBIVCgZyZXZfaWQYAyABKANSBXJldklkEh4KC2Jhc2VfcmV2X2lkGAQgASgDUgliYXNlUmV2SWQ=');
+@$core.Deprecated('Use resetBlockParamsDescriptor instead')
+const ResetBlockParams$json = const {
+  '1': 'ResetBlockParams',
   '2': const [
-    const {'1': 'doc_id', '3': 1, '4': 1, '5': 9, '10': 'docId'},
+    const {'1': 'block_id', '3': 1, '4': 1, '5': 9, '10': 'blockId'},
     const {'1': 'revisions', '3': 2, '4': 1, '5': 11, '6': '.RepeatedRevision', '10': 'revisions'},
   ],
 };
 
-/// Descriptor for `ResetDocumentParams`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List resetDocumentParamsDescriptor = $convert.base64Decode('ChNSZXNldERvY3VtZW50UGFyYW1zEhUKBmRvY19pZBgBIAEoCVIFZG9jSWQSLwoJcmV2aXNpb25zGAIgASgLMhEuUmVwZWF0ZWRSZXZpc2lvblIJcmV2aXNpb25z');
+/// Descriptor for `ResetBlockParams`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List resetBlockParamsDescriptor = $convert.base64Decode('ChBSZXNldEJsb2NrUGFyYW1zEhkKCGJsb2NrX2lkGAEgASgJUgdibG9ja0lkEi8KCXJldmlzaW9ucxgCIAEoCzIRLlJlcGVhdGVkUmV2aXNpb25SCXJldmlzaW9ucw==');
 @$core.Deprecated('Use blockDeltaDescriptor instead')
 const BlockDelta$json = const {
   '1': 'BlockDelta',
   '2': const [
     const {'1': 'block_id', '3': 1, '4': 1, '5': 9, '10': 'blockId'},
-    const {'1': 'delta_json', '3': 2, '4': 1, '5': 9, '10': 'deltaJson'},
+    const {'1': 'delta_str', '3': 2, '4': 1, '5': 9, '10': 'deltaStr'},
   ],
 };
 
 /// Descriptor for `BlockDelta`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List blockDeltaDescriptor = $convert.base64Decode('CgpCbG9ja0RlbHRhEhkKCGJsb2NrX2lkGAEgASgJUgdibG9ja0lkEh0KCmRlbHRhX2pzb24YAiABKAlSCWRlbHRhSnNvbg==');
+final $typed_data.Uint8List blockDeltaDescriptor = $convert.base64Decode('CgpCbG9ja0RlbHRhEhkKCGJsb2NrX2lkGAEgASgJUgdibG9ja0lkEhsKCWRlbHRhX3N0chgCIAEoCVIIZGVsdGFTdHI=');
 @$core.Deprecated('Use newDocUserDescriptor instead')
 const NewDocUser$json = const {
   '1': 'NewDocUser',

+ 0 - 1
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/protobuf.dart

@@ -1,5 +1,4 @@
 // Auto-generated, do not edit 
-export './share.pb.dart';
 export './app.pb.dart';
 export './view.pb.dart';
 export './trash.pb.dart';

+ 3 - 3
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/view.pb.dart

@@ -20,7 +20,7 @@ class View extends $pb.GeneratedMessage {
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongToId')
     ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
     ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
-    ..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.RichText, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
+    ..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.Block, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
     ..aInt64(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'version')
     ..aOM<RepeatedView>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongings', subBuilder: RepeatedView.create)
     ..aInt64(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'modifiedTime')
@@ -274,7 +274,7 @@ class CreateViewPayload extends $pb.GeneratedMessage {
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
     ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
     ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail')
-    ..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.RichText, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
+    ..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.Block, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
     ..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'extData')
     ..a<$core.int>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pluginType', $pb.PbFieldType.O3)
     ..hasRequiredFields = false
@@ -408,7 +408,7 @@ class CreateViewParams extends $pb.GeneratedMessage {
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
     ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
     ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail')
-    ..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.RichText, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
+    ..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.Block, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
     ..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'extData')
     ..aOS(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewId')
     ..aOS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')

+ 4 - 4
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/view.pbenum.dart

@@ -10,12 +10,12 @@ import 'dart:core' as $core;
 import 'package:protobuf/protobuf.dart' as $pb;
 
 class ViewDataType extends $pb.ProtobufEnum {
-  static const ViewDataType RichText = ViewDataType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RichText');
-  static const ViewDataType PlainText = ViewDataType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PlainText');
+  static const ViewDataType Block = ViewDataType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Block');
+  static const ViewDataType Grid = ViewDataType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Grid');
 
   static const $core.List<ViewDataType> values = <ViewDataType> [
-    RichText,
-    PlainText,
+    Block,
+    Grid,
   ];
 
   static final $core.Map<$core.int, ViewDataType> _byValue = $pb.ProtobufEnum.initByValue(values);

+ 3 - 3
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/view.pbjson.dart

@@ -12,13 +12,13 @@ import 'dart:typed_data' as $typed_data;
 const ViewDataType$json = const {
   '1': 'ViewDataType',
   '2': const [
-    const {'1': 'RichText', '2': 0},
-    const {'1': 'PlainText', '2': 1},
+    const {'1': 'Block', '2': 0},
+    const {'1': 'Grid', '2': 1},
   ],
 };
 
 /// Descriptor for `ViewDataType`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List viewDataTypeDescriptor = $convert.base64Decode('CgxWaWV3RGF0YVR5cGUSDAoIUmljaFRleHQQABINCglQbGFpblRleHQQAQ==');
+final $typed_data.Uint8List viewDataTypeDescriptor = $convert.base64Decode('CgxWaWV3RGF0YVR5cGUSCQoFQmxvY2sQABIICgRHcmlkEAE=');
 @$core.Deprecated('Use viewDescriptor instead')
 const View$json = const {
   '1': 'View',

+ 2 - 2
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder/event_map.pbenum.dart

@@ -26,7 +26,7 @@ class FolderEvent extends $pb.ProtobufEnum {
   static const FolderEvent DeleteView = FolderEvent._(204, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteView');
   static const FolderEvent DuplicateView = FolderEvent._(205, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateView');
   static const FolderEvent CopyLink = FolderEvent._(206, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CopyLink');
-  static const FolderEvent OpenView = FolderEvent._(207, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'OpenView');
+  static const FolderEvent SetLatestView = FolderEvent._(207, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SetLatestView');
   static const FolderEvent CloseView = FolderEvent._(208, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CloseView');
   static const FolderEvent ReadTrash = FolderEvent._(300, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadTrash');
   static const FolderEvent PutbackTrash = FolderEvent._(301, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PutbackTrash');
@@ -53,7 +53,7 @@ class FolderEvent extends $pb.ProtobufEnum {
     DeleteView,
     DuplicateView,
     CopyLink,
-    OpenView,
+    SetLatestView,
     CloseView,
     ReadTrash,
     PutbackTrash,

+ 2 - 2
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder/event_map.pbjson.dart

@@ -28,7 +28,7 @@ const FolderEvent$json = const {
     const {'1': 'DeleteView', '2': 204},
     const {'1': 'DuplicateView', '2': 205},
     const {'1': 'CopyLink', '2': 206},
-    const {'1': 'OpenView', '2': 207},
+    const {'1': 'SetLatestView', '2': 207},
     const {'1': 'CloseView', '2': 208},
     const {'1': 'ReadTrash', '2': 300},
     const {'1': 'PutbackTrash', '2': 301},
@@ -41,4 +41,4 @@ const FolderEvent$json = const {
 };
 
 /// Descriptor for `FolderEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List folderEventDescriptor = $convert.base64Decode('CgtGb2xkZXJFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIUChBSZWFkQ3VyV29ya3NwYWNlEAESEgoOUmVhZFdvcmtzcGFjZXMQAhITCg9EZWxldGVXb3Jrc3BhY2UQAxIRCg1PcGVuV29ya3NwYWNlEAQSFQoRUmVhZFdvcmtzcGFjZUFwcHMQBRINCglDcmVhdGVBcHAQZRINCglEZWxldGVBcHAQZhILCgdSZWFkQXBwEGcSDQoJVXBkYXRlQXBwEGgSDwoKQ3JlYXRlVmlldxDJARINCghSZWFkVmlldxDKARIPCgpVcGRhdGVWaWV3EMsBEg8KCkRlbGV0ZVZpZXcQzAESEgoNRHVwbGljYXRlVmlldxDNARINCghDb3B5TGluaxDOARINCghPcGVuVmlldxDPARIOCglDbG9zZVZpZXcQ0AESDgoJUmVhZFRyYXNoEKwCEhEKDFB1dGJhY2tUcmFzaBCtAhIQCgtEZWxldGVUcmFzaBCuAhIUCg9SZXN0b3JlQWxsVHJhc2gQrwISEwoORGVsZXRlQWxsVHJhc2gQsAISEgoNQXBwbHlEb2NEZWx0YRCQAxITCg5FeHBvcnREb2N1bWVudBD0Aw==');
+final $typed_data.Uint8List folderEventDescriptor = $convert.base64Decode('CgtGb2xkZXJFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIUChBSZWFkQ3VyV29ya3NwYWNlEAESEgoOUmVhZFdvcmtzcGFjZXMQAhITCg9EZWxldGVXb3Jrc3BhY2UQAxIRCg1PcGVuV29ya3NwYWNlEAQSFQoRUmVhZFdvcmtzcGFjZUFwcHMQBRINCglDcmVhdGVBcHAQZRINCglEZWxldGVBcHAQZhILCgdSZWFkQXBwEGcSDQoJVXBkYXRlQXBwEGgSDwoKQ3JlYXRlVmlldxDJARINCghSZWFkVmlldxDKARIPCgpVcGRhdGVWaWV3EMsBEg8KCkRlbGV0ZVZpZXcQzAESEgoNRHVwbGljYXRlVmlldxDNARINCghDb3B5TGluaxDOARISCg1TZXRMYXRlc3RWaWV3EM8BEg4KCUNsb3NlVmlldxDQARIOCglSZWFkVHJhc2gQrAISEQoMUHV0YmFja1RyYXNoEK0CEhAKC0RlbGV0ZVRyYXNoEK4CEhQKD1Jlc3RvcmVBbGxUcmFzaBCvAhITCg5EZWxldGVBbGxUcmFzaBCwAhISCg1BcHBseURvY0RlbHRhEJADEhMKDkV4cG9ydERvY3VtZW50EPQD');

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

@@ -0,0 +1,1187 @@
+///
+//  Generated code. Do not modify.
+//  source: grid.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'grid.pbenum.dart';
+
+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<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,
+    RepeatedFieldOrder? fieldOrders,
+    RepeatedRowOrder? rowOrders,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    if (fieldOrders != null) {
+      _result.fieldOrders = fieldOrders;
+    }
+    if (rowOrders != null) {
+      _result.rowOrders = rowOrders;
+    }
+    return _result;
+  }
+  factory Grid.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory Grid.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')
+  Grid clone() => Grid()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  Grid copyWith(void Function(Grid) updates) => super.copyWith((message) => updates(message as Grid)) as Grid; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static Grid create() => Grid._();
+  Grid createEmptyInstance() => create();
+  static $pb.PbList<Grid> createRepeated() => $pb.PbList<Grid>();
+  @$core.pragma('dart2js:noInline')
+  static Grid getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Grid>(create);
+  static Grid? _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)
+  RepeatedFieldOrder get fieldOrders => $_getN(1);
+  @$pb.TagNumber(2)
+  set fieldOrders(RepeatedFieldOrder v) { setField(2, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasFieldOrders() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearFieldOrders() => clearField(2);
+  @$pb.TagNumber(2)
+  RepeatedFieldOrder ensureFieldOrders() => $_ensure(1);
+
+  @$pb.TagNumber(3)
+  RepeatedRowOrder get rowOrders => $_getN(2);
+  @$pb.TagNumber(3)
+  set rowOrders(RepeatedRowOrder v) { setField(3, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasRowOrders() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearRowOrders() => clearField(3);
+  @$pb.TagNumber(3)
+  RepeatedRowOrder ensureRowOrders() => $_ensure(2);
+}
+
+class FieldOrder extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'FieldOrder', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
+    ..aOB(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'visibility')
+    ..hasRequiredFields = false
+  ;
+
+  FieldOrder._() : super();
+  factory FieldOrder({
+    $core.String? fieldId,
+    $core.bool? visibility,
+  }) {
+    final _result = create();
+    if (fieldId != null) {
+      _result.fieldId = fieldId;
+    }
+    if (visibility != null) {
+      _result.visibility = visibility;
+    }
+    return _result;
+  }
+  factory FieldOrder.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory FieldOrder.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')
+  FieldOrder clone() => FieldOrder()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  FieldOrder copyWith(void Function(FieldOrder) updates) => super.copyWith((message) => updates(message as FieldOrder)) as FieldOrder; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static FieldOrder create() => FieldOrder._();
+  FieldOrder createEmptyInstance() => create();
+  static $pb.PbList<FieldOrder> createRepeated() => $pb.PbList<FieldOrder>();
+  @$core.pragma('dart2js:noInline')
+  static FieldOrder getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FieldOrder>(create);
+  static FieldOrder? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get fieldId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set fieldId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasFieldId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearFieldId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.bool get visibility => $_getBF(1);
+  @$pb.TagNumber(2)
+  set visibility($core.bool v) { $_setBool(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasVisibility() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearVisibility() => clearField(2);
+}
+
+class RepeatedFieldOrder extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RepeatedFieldOrder', createEmptyInstance: create)
+    ..pc<FieldOrder>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: FieldOrder.create)
+    ..hasRequiredFields = false
+  ;
+
+  RepeatedFieldOrder._() : super();
+  factory RepeatedFieldOrder({
+    $core.Iterable<FieldOrder>? items,
+  }) {
+    final _result = create();
+    if (items != null) {
+      _result.items.addAll(items);
+    }
+    return _result;
+  }
+  factory RepeatedFieldOrder.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RepeatedFieldOrder.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')
+  RepeatedFieldOrder clone() => RepeatedFieldOrder()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  RepeatedFieldOrder copyWith(void Function(RepeatedFieldOrder) updates) => super.copyWith((message) => updates(message as RepeatedFieldOrder)) as RepeatedFieldOrder; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static RepeatedFieldOrder create() => RepeatedFieldOrder._();
+  RepeatedFieldOrder createEmptyInstance() => create();
+  static $pb.PbList<RepeatedFieldOrder> createRepeated() => $pb.PbList<RepeatedFieldOrder>();
+  @$core.pragma('dart2js:noInline')
+  static RepeatedFieldOrder getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RepeatedFieldOrder>(create);
+  static RepeatedFieldOrder? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<FieldOrder> get items => $_getList(0);
+}
+
+class Field extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Field', 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')
+    ..e<FieldType>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldType', $pb.PbFieldType.OE, defaultOrMaker: FieldType.RichText, valueOf: FieldType.valueOf, enumValues: FieldType.values)
+    ..aOB(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'frozen')
+    ..a<$core.int>(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'width', $pb.PbFieldType.O3)
+    ..aOM<AnyData>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptions', subBuilder: AnyData.create)
+    ..hasRequiredFields = false
+  ;
+
+  Field._() : super();
+  factory Field({
+    $core.String? id,
+    $core.String? name,
+    $core.String? desc,
+    FieldType? fieldType,
+    $core.bool? frozen,
+    $core.int? width,
+    AnyData? typeOptions,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    if (name != null) {
+      _result.name = name;
+    }
+    if (desc != null) {
+      _result.desc = desc;
+    }
+    if (fieldType != null) {
+      _result.fieldType = fieldType;
+    }
+    if (frozen != null) {
+      _result.frozen = frozen;
+    }
+    if (width != null) {
+      _result.width = width;
+    }
+    if (typeOptions != null) {
+      _result.typeOptions = typeOptions;
+    }
+    return _result;
+  }
+  factory Field.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory Field.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')
+  Field clone() => Field()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  Field copyWith(void Function(Field) updates) => super.copyWith((message) => updates(message as Field)) as Field; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static Field create() => Field._();
+  Field createEmptyInstance() => create();
+  static $pb.PbList<Field> createRepeated() => $pb.PbList<Field>();
+  @$core.pragma('dart2js:noInline')
+  static Field getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Field>(create);
+  static Field? _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);
+
+  @$pb.TagNumber(4)
+  FieldType get fieldType => $_getN(3);
+  @$pb.TagNumber(4)
+  set fieldType(FieldType v) { setField(4, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasFieldType() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearFieldType() => clearField(4);
+
+  @$pb.TagNumber(5)
+  $core.bool get frozen => $_getBF(4);
+  @$pb.TagNumber(5)
+  set frozen($core.bool v) { $_setBool(4, v); }
+  @$pb.TagNumber(5)
+  $core.bool hasFrozen() => $_has(4);
+  @$pb.TagNumber(5)
+  void clearFrozen() => clearField(5);
+
+  @$pb.TagNumber(6)
+  $core.int get width => $_getIZ(5);
+  @$pb.TagNumber(6)
+  set width($core.int v) { $_setSignedInt32(5, v); }
+  @$pb.TagNumber(6)
+  $core.bool hasWidth() => $_has(5);
+  @$pb.TagNumber(6)
+  void clearWidth() => clearField(6);
+
+  @$pb.TagNumber(7)
+  AnyData get typeOptions => $_getN(6);
+  @$pb.TagNumber(7)
+  set typeOptions(AnyData v) { setField(7, v); }
+  @$pb.TagNumber(7)
+  $core.bool hasTypeOptions() => $_has(6);
+  @$pb.TagNumber(7)
+  void clearTypeOptions() => clearField(7);
+  @$pb.TagNumber(7)
+  AnyData ensureTypeOptions() => $_ensure(6);
+}
+
+class RepeatedField extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RepeatedField', createEmptyInstance: create)
+    ..pc<Field>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: Field.create)
+    ..hasRequiredFields = false
+  ;
+
+  RepeatedField._() : super();
+  factory RepeatedField({
+    $core.Iterable<Field>? items,
+  }) {
+    final _result = create();
+    if (items != null) {
+      _result.items.addAll(items);
+    }
+    return _result;
+  }
+  factory RepeatedField.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RepeatedField.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')
+  RepeatedField clone() => RepeatedField()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  RepeatedField copyWith(void Function(RepeatedField) updates) => super.copyWith((message) => updates(message as RepeatedField)) as RepeatedField; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static RepeatedField create() => RepeatedField._();
+  RepeatedField createEmptyInstance() => create();
+  static $pb.PbList<RepeatedField> createRepeated() => $pb.PbList<RepeatedField>();
+  @$core.pragma('dart2js:noInline')
+  static RepeatedField getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RepeatedField>(create);
+  static RepeatedField? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<Field> get items => $_getList(0);
+}
+
+class AnyData extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'AnyData', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeId')
+    ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'value', $pb.PbFieldType.OY)
+    ..hasRequiredFields = false
+  ;
+
+  AnyData._() : super();
+  factory AnyData({
+    $core.String? typeId,
+    $core.List<$core.int>? value,
+  }) {
+    final _result = create();
+    if (typeId != null) {
+      _result.typeId = typeId;
+    }
+    if (value != null) {
+      _result.value = value;
+    }
+    return _result;
+  }
+  factory AnyData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory AnyData.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')
+  AnyData clone() => AnyData()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  AnyData copyWith(void Function(AnyData) updates) => super.copyWith((message) => updates(message as AnyData)) as AnyData; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static AnyData create() => AnyData._();
+  AnyData createEmptyInstance() => create();
+  static $pb.PbList<AnyData> createRepeated() => $pb.PbList<AnyData>();
+  @$core.pragma('dart2js:noInline')
+  static AnyData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<AnyData>(create);
+  static AnyData? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get typeId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set typeId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasTypeId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearTypeId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.List<$core.int> get value => $_getN(1);
+  @$pb.TagNumber(2)
+  set value($core.List<$core.int> v) { $_setBytes(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasValue() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearValue() => clearField(2);
+}
+
+class RowOrder extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RowOrder', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowId')
+    ..aOB(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'visibility')
+    ..hasRequiredFields = false
+  ;
+
+  RowOrder._() : super();
+  factory RowOrder({
+    $core.String? gridId,
+    $core.String? rowId,
+    $core.bool? visibility,
+  }) {
+    final _result = create();
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
+    if (rowId != null) {
+      _result.rowId = rowId;
+    }
+    if (visibility != null) {
+      _result.visibility = visibility;
+    }
+    return _result;
+  }
+  factory RowOrder.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RowOrder.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')
+  RowOrder clone() => RowOrder()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  RowOrder copyWith(void Function(RowOrder) updates) => super.copyWith((message) => updates(message as RowOrder)) as RowOrder; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static RowOrder create() => RowOrder._();
+  RowOrder createEmptyInstance() => create();
+  static $pb.PbList<RowOrder> createRepeated() => $pb.PbList<RowOrder>();
+  @$core.pragma('dart2js:noInline')
+  static RowOrder getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RowOrder>(create);
+  static RowOrder? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get gridId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set gridId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasGridId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearGridId() => 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.bool get visibility => $_getBF(2);
+  @$pb.TagNumber(3)
+  set visibility($core.bool v) { $_setBool(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasVisibility() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearVisibility() => clearField(3);
+}
+
+class RepeatedRowOrder extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RepeatedRowOrder', createEmptyInstance: create)
+    ..pc<RowOrder>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: RowOrder.create)
+    ..hasRequiredFields = false
+  ;
+
+  RepeatedRowOrder._() : super();
+  factory RepeatedRowOrder({
+    $core.Iterable<RowOrder>? items,
+  }) {
+    final _result = create();
+    if (items != null) {
+      _result.items.addAll(items);
+    }
+    return _result;
+  }
+  factory RepeatedRowOrder.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RepeatedRowOrder.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')
+  RepeatedRowOrder clone() => RepeatedRowOrder()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  RepeatedRowOrder copyWith(void Function(RepeatedRowOrder) updates) => super.copyWith((message) => updates(message as RepeatedRowOrder)) as RepeatedRowOrder; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static RepeatedRowOrder create() => RepeatedRowOrder._();
+  RepeatedRowOrder createEmptyInstance() => create();
+  static $pb.PbList<RepeatedRowOrder> createRepeated() => $pb.PbList<RepeatedRowOrder>();
+  @$core.pragma('dart2js:noInline')
+  static RepeatedRowOrder getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RepeatedRowOrder>(create);
+  static RepeatedRowOrder? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<RowOrder> get items => $_getList(0);
+}
+
+class RawRow extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RawRow', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
+    ..m<$core.String, RawCell>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'cellByFieldId', entryClassName: 'RawRow.CellByFieldIdEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.OM, valueCreator: RawCell.create)
+    ..hasRequiredFields = false
+  ;
+
+  RawRow._() : super();
+  factory RawRow({
+    $core.String? id,
+    $core.String? gridId,
+    $core.Map<$core.String, RawCell>? cellByFieldId,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
+    if (cellByFieldId != null) {
+      _result.cellByFieldId.addAll(cellByFieldId);
+    }
+    return _result;
+  }
+  factory RawRow.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RawRow.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')
+  RawRow clone() => RawRow()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  RawRow copyWith(void Function(RawRow) updates) => super.copyWith((message) => updates(message as RawRow)) as RawRow; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static RawRow create() => RawRow._();
+  RawRow createEmptyInstance() => create();
+  static $pb.PbList<RawRow> createRepeated() => $pb.PbList<RawRow>();
+  @$core.pragma('dart2js:noInline')
+  static RawRow getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RawRow>(create);
+  static RawRow? _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 gridId => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set gridId($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasGridId() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearGridId() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.Map<$core.String, RawCell> get cellByFieldId => $_getMap(2);
+}
+
+class RawCell extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RawCell', 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')
+    ..aOM<AnyData>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', subBuilder: AnyData.create)
+    ..hasRequiredFields = false
+  ;
+
+  RawCell._() : super();
+  factory RawCell({
+    $core.String? id,
+    $core.String? rowId,
+    $core.String? fieldId,
+    AnyData? 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 RawCell.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RawCell.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')
+  RawCell clone() => RawCell()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  RawCell copyWith(void Function(RawCell) updates) => super.copyWith((message) => updates(message as RawCell)) as RawCell; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static RawCell create() => RawCell._();
+  RawCell createEmptyInstance() => create();
+  static $pb.PbList<RawCell> createRepeated() => $pb.PbList<RawCell>();
+  @$core.pragma('dart2js:noInline')
+  static RawCell getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RawCell>(create);
+  static RawCell? _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)
+  AnyData get data => $_getN(3);
+  @$pb.TagNumber(4)
+  set data(AnyData v) { setField(4, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasData() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearData() => clearField(4);
+  @$pb.TagNumber(4)
+  AnyData ensureData() => $_ensure(3);
+}
+
+class RepeatedRow extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RepeatedRow', createEmptyInstance: create)
+    ..pc<Row>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: Row.create)
+    ..hasRequiredFields = false
+  ;
+
+  RepeatedRow._() : super();
+  factory RepeatedRow({
+    $core.Iterable<Row>? items,
+  }) {
+    final _result = create();
+    if (items != null) {
+      _result.items.addAll(items);
+    }
+    return _result;
+  }
+  factory RepeatedRow.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RepeatedRow.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')
+  RepeatedRow clone() => RepeatedRow()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  RepeatedRow copyWith(void Function(RepeatedRow) updates) => super.copyWith((message) => updates(message as RepeatedRow)) as RepeatedRow; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static RepeatedRow create() => RepeatedRow._();
+  RepeatedRow createEmptyInstance() => create();
+  static $pb.PbList<RepeatedRow> createRepeated() => $pb.PbList<RepeatedRow>();
+  @$core.pragma('dart2js:noInline')
+  static RepeatedRow getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RepeatedRow>(create);
+  static RepeatedRow? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<Row> get items => $_getList(0);
+}
+
+class Row extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Row', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
+    ..m<$core.String, Cell>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'cellByFieldId', entryClassName: 'Row.CellByFieldIdEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.OM, valueCreator: Cell.create)
+    ..hasRequiredFields = false
+  ;
+
+  Row._() : super();
+  factory Row({
+    $core.String? id,
+    $core.Map<$core.String, Cell>? cellByFieldId,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    if (cellByFieldId != null) {
+      _result.cellByFieldId.addAll(cellByFieldId);
+    }
+    return _result;
+  }
+  factory Row.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory Row.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')
+  Row clone() => Row()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  Row copyWith(void Function(Row) updates) => super.copyWith((message) => updates(message as Row)) as Row; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static Row create() => Row._();
+  Row createEmptyInstance() => create();
+  static $pb.PbList<Row> createRepeated() => $pb.PbList<Row>();
+  @$core.pragma('dart2js:noInline')
+  static Row getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Row>(create);
+  static Row? _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.Map<$core.String, Cell> get cellByFieldId => $_getMap(1);
+}
+
+class Cell extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Cell', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'content')
+    ..hasRequiredFields = false
+  ;
+
+  Cell._() : super();
+  factory Cell({
+    $core.String? id,
+    $core.String? fieldId,
+    $core.String? content,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    if (fieldId != null) {
+      _result.fieldId = fieldId;
+    }
+    if (content != null) {
+      _result.content = content;
+    }
+    return _result;
+  }
+  factory Cell.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory Cell.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')
+  Cell clone() => Cell()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  Cell copyWith(void Function(Cell) updates) => super.copyWith((message) => updates(message as Cell)) as Cell; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static Cell create() => Cell._();
+  Cell createEmptyInstance() => create();
+  static $pb.PbList<Cell> createRepeated() => $pb.PbList<Cell>();
+  @$core.pragma('dart2js:noInline')
+  static Cell getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Cell>(create);
+  static Cell? _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 fieldId => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set fieldId($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasFieldId() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearFieldId() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.String get content => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set content($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasContent() => $_has(2);
+  @$pb.TagNumber(3)
+  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')
+    ..hasRequiredFields = false
+  ;
+
+  CreateGridPayload._() : super();
+  factory CreateGridPayload({
+    $core.String? name,
+  }) {
+    final _result = create();
+    if (name != null) {
+      _result.name = name;
+    }
+    return _result;
+  }
+  factory CreateGridPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory CreateGridPayload.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')
+  CreateGridPayload clone() => CreateGridPayload()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  CreateGridPayload copyWith(void Function(CreateGridPayload) updates) => super.copyWith((message) => updates(message as CreateGridPayload)) as CreateGridPayload; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static CreateGridPayload create() => CreateGridPayload._();
+  CreateGridPayload createEmptyInstance() => create();
+  static $pb.PbList<CreateGridPayload> createRepeated() => $pb.PbList<CreateGridPayload>();
+  @$core.pragma('dart2js:noInline')
+  static CreateGridPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreateGridPayload>(create);
+  static CreateGridPayload? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get name => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set name($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasName() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearName() => clearField(1);
+}
+
+class GridId extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridId', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'value')
+    ..hasRequiredFields = false
+  ;
+
+  GridId._() : super();
+  factory GridId({
+    $core.String? value,
+  }) {
+    final _result = create();
+    if (value != null) {
+      _result.value = value;
+    }
+    return _result;
+  }
+  factory GridId.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory GridId.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')
+  GridId clone() => GridId()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  GridId copyWith(void Function(GridId) updates) => super.copyWith((message) => updates(message as GridId)) as GridId; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static GridId create() => GridId._();
+  GridId createEmptyInstance() => create();
+  static $pb.PbList<GridId> createRepeated() => $pb.PbList<GridId>();
+  @$core.pragma('dart2js:noInline')
+  static GridId getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<GridId>(create);
+  static GridId? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get value => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set value($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasValue() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearValue() => clearField(1);
+}
+
+class QueryFieldPayload extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'QueryFieldPayload', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
+    ..aOM<RepeatedFieldOrder>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldOrders', subBuilder: RepeatedFieldOrder.create)
+    ..hasRequiredFields = false
+  ;
+
+  QueryFieldPayload._() : super();
+  factory QueryFieldPayload({
+    $core.String? gridId,
+    RepeatedFieldOrder? fieldOrders,
+  }) {
+    final _result = create();
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
+    if (fieldOrders != null) {
+      _result.fieldOrders = fieldOrders;
+    }
+    return _result;
+  }
+  factory QueryFieldPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory QueryFieldPayload.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')
+  QueryFieldPayload clone() => QueryFieldPayload()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  QueryFieldPayload copyWith(void Function(QueryFieldPayload) updates) => super.copyWith((message) => updates(message as QueryFieldPayload)) as QueryFieldPayload; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static QueryFieldPayload create() => QueryFieldPayload._();
+  QueryFieldPayload createEmptyInstance() => create();
+  static $pb.PbList<QueryFieldPayload> createRepeated() => $pb.PbList<QueryFieldPayload>();
+  @$core.pragma('dart2js:noInline')
+  static QueryFieldPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<QueryFieldPayload>(create);
+  static QueryFieldPayload? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get gridId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set gridId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasGridId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearGridId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  RepeatedFieldOrder get fieldOrders => $_getN(1);
+  @$pb.TagNumber(2)
+  set fieldOrders(RepeatedFieldOrder v) { setField(2, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasFieldOrders() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearFieldOrders() => clearField(2);
+  @$pb.TagNumber(2)
+  RepeatedFieldOrder ensureFieldOrders() => $_ensure(1);
+}
+
+class QueryRowPayload extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'QueryRowPayload', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
+    ..aOM<RepeatedRowOrder>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowOrders', subBuilder: RepeatedRowOrder.create)
+    ..hasRequiredFields = false
+  ;
+
+  QueryRowPayload._() : super();
+  factory QueryRowPayload({
+    $core.String? gridId,
+    RepeatedRowOrder? rowOrders,
+  }) {
+    final _result = create();
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
+    if (rowOrders != null) {
+      _result.rowOrders = rowOrders;
+    }
+    return _result;
+  }
+  factory QueryRowPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory QueryRowPayload.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')
+  QueryRowPayload clone() => QueryRowPayload()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  QueryRowPayload copyWith(void Function(QueryRowPayload) updates) => super.copyWith((message) => updates(message as QueryRowPayload)) as QueryRowPayload; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static QueryRowPayload create() => QueryRowPayload._();
+  QueryRowPayload createEmptyInstance() => create();
+  static $pb.PbList<QueryRowPayload> createRepeated() => $pb.PbList<QueryRowPayload>();
+  @$core.pragma('dart2js:noInline')
+  static QueryRowPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<QueryRowPayload>(create);
+  static QueryRowPayload? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get gridId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set gridId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasGridId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearGridId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  RepeatedRowOrder get rowOrders => $_getN(1);
+  @$pb.TagNumber(2)
+  set rowOrders(RepeatedRowOrder v) { setField(2, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasRowOrders() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearRowOrders() => clearField(2);
+  @$pb.TagNumber(2)
+  RepeatedRowOrder ensureRowOrders() => $_ensure(1);
+}
+

+ 34 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbenum.dart

@@ -0,0 +1,34 @@
+///
+//  Generated code. Do not modify.
+//  source: grid.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+// ignore_for_file: UNDEFINED_SHOWN_NAME
+import 'dart:core' as $core;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class FieldType extends $pb.ProtobufEnum {
+  static const FieldType RichText = FieldType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RichText');
+  static const FieldType Number = FieldType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Number');
+  static const FieldType DateTime = FieldType._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DateTime');
+  static const FieldType SingleSelect = FieldType._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SingleSelect');
+  static const FieldType MultiSelect = FieldType._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MultiSelect');
+  static const FieldType Checkbox = FieldType._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Checkbox');
+
+  static const $core.List<FieldType> values = <FieldType> [
+    RichText,
+    Number,
+    DateTime,
+    SingleSelect,
+    MultiSelect,
+    Checkbox,
+  ];
+
+  static final $core.Map<$core.int, FieldType> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static FieldType? valueOf($core.int value) => _byValue[value];
+
+  const FieldType._($core.int v, $core.String n) : super(v, n);
+}
+

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

@@ -0,0 +1,252 @@
+///
+//  Generated code. Do not modify.
+//  source: grid.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+import 'dart:core' as $core;
+import 'dart:convert' as $convert;
+import 'dart:typed_data' as $typed_data;
+@$core.Deprecated('Use fieldTypeDescriptor instead')
+const FieldType$json = const {
+  '1': 'FieldType',
+  '2': const [
+    const {'1': 'RichText', '2': 0},
+    const {'1': 'Number', '2': 1},
+    const {'1': 'DateTime', '2': 2},
+    const {'1': 'SingleSelect', '2': 3},
+    const {'1': 'MultiSelect', '2': 4},
+    const {'1': 'Checkbox', '2': 5},
+  ],
+};
+
+/// Descriptor for `FieldType`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List fieldTypeDescriptor = $convert.base64Decode('CglGaWVsZFR5cGUSDAoIUmljaFRleHQQABIKCgZOdW1iZXIQARIMCghEYXRlVGltZRACEhAKDFNpbmdsZVNlbGVjdBADEg8KC011bHRpU2VsZWN0EAQSDAoIQ2hlY2tib3gQBQ==');
+@$core.Deprecated('Use gridDescriptor instead')
+const Grid$json = const {
+  '1': 'Grid',
+  '2': const [
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+    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('CgRHcmlkEg4KAmlkGAEgASgJUgJpZBI2CgxmaWVsZF9vcmRlcnMYAiABKAsyEy5SZXBlYXRlZEZpZWxkT3JkZXJSC2ZpZWxkT3JkZXJzEjAKCnJvd19vcmRlcnMYAyABKAsyES5SZXBlYXRlZFJvd09yZGVyUglyb3dPcmRlcnM=');
+@$core.Deprecated('Use fieldOrderDescriptor instead')
+const FieldOrder$json = const {
+  '1': 'FieldOrder',
+  '2': const [
+    const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'},
+    const {'1': 'visibility', '3': 2, '4': 1, '5': 8, '10': 'visibility'},
+  ],
+};
+
+/// Descriptor for `FieldOrder`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List fieldOrderDescriptor = $convert.base64Decode('CgpGaWVsZE9yZGVyEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEh4KCnZpc2liaWxpdHkYAiABKAhSCnZpc2liaWxpdHk=');
+@$core.Deprecated('Use repeatedFieldOrderDescriptor instead')
+const RepeatedFieldOrder$json = const {
+  '1': 'RepeatedFieldOrder',
+  '2': const [
+    const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.FieldOrder', '10': 'items'},
+  ],
+};
+
+/// Descriptor for `RepeatedFieldOrder`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List repeatedFieldOrderDescriptor = $convert.base64Decode('ChJSZXBlYXRlZEZpZWxkT3JkZXISIQoFaXRlbXMYASADKAsyCy5GaWVsZE9yZGVyUgVpdGVtcw==');
+@$core.Deprecated('Use fieldDescriptor instead')
+const Field$json = const {
+  '1': 'Field',
+  '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'},
+    const {'1': 'field_type', '3': 4, '4': 1, '5': 14, '6': '.FieldType', '10': 'fieldType'},
+    const {'1': 'frozen', '3': 5, '4': 1, '5': 8, '10': 'frozen'},
+    const {'1': 'width', '3': 6, '4': 1, '5': 5, '10': 'width'},
+    const {'1': 'type_options', '3': 7, '4': 1, '5': 11, '6': '.AnyData', '10': 'typeOptions'},
+  ],
+};
+
+/// Descriptor for `Field`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List fieldDescriptor = $convert.base64Decode('CgVGaWVsZBIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRISCgRkZXNjGAMgASgJUgRkZXNjEikKCmZpZWxkX3R5cGUYBCABKA4yCi5GaWVsZFR5cGVSCWZpZWxkVHlwZRIWCgZmcm96ZW4YBSABKAhSBmZyb3plbhIUCgV3aWR0aBgGIAEoBVIFd2lkdGgSKwoMdHlwZV9vcHRpb25zGAcgASgLMgguQW55RGF0YVILdHlwZU9wdGlvbnM=');
+@$core.Deprecated('Use repeatedFieldDescriptor instead')
+const RepeatedField$json = const {
+  '1': 'RepeatedField',
+  '2': const [
+    const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.Field', '10': 'items'},
+  ],
+};
+
+/// Descriptor for `RepeatedField`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List repeatedFieldDescriptor = $convert.base64Decode('Cg1SZXBlYXRlZEZpZWxkEhwKBWl0ZW1zGAEgAygLMgYuRmllbGRSBWl0ZW1z');
+@$core.Deprecated('Use anyDataDescriptor instead')
+const AnyData$json = const {
+  '1': 'AnyData',
+  '2': const [
+    const {'1': 'type_id', '3': 1, '4': 1, '5': 9, '10': 'typeId'},
+    const {'1': 'value', '3': 2, '4': 1, '5': 12, '10': 'value'},
+  ],
+};
+
+/// Descriptor for `AnyData`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List anyDataDescriptor = $convert.base64Decode('CgdBbnlEYXRhEhcKB3R5cGVfaWQYASABKAlSBnR5cGVJZBIUCgV2YWx1ZRgCIAEoDFIFdmFsdWU=');
+@$core.Deprecated('Use rowOrderDescriptor instead')
+const RowOrder$json = const {
+  '1': 'RowOrder',
+  '2': const [
+    const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
+    const {'1': 'row_id', '3': 2, '4': 1, '5': 9, '10': 'rowId'},
+    const {'1': 'visibility', '3': 3, '4': 1, '5': 8, '10': 'visibility'},
+  ],
+};
+
+/// Descriptor for `RowOrder`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List rowOrderDescriptor = $convert.base64Decode('CghSb3dPcmRlchIXCgdncmlkX2lkGAEgASgJUgZncmlkSWQSFQoGcm93X2lkGAIgASgJUgVyb3dJZBIeCgp2aXNpYmlsaXR5GAMgASgIUgp2aXNpYmlsaXR5');
+@$core.Deprecated('Use repeatedRowOrderDescriptor instead')
+const RepeatedRowOrder$json = const {
+  '1': 'RepeatedRowOrder',
+  '2': const [
+    const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.RowOrder', '10': 'items'},
+  ],
+};
+
+/// Descriptor for `RepeatedRowOrder`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List repeatedRowOrderDescriptor = $convert.base64Decode('ChBSZXBlYXRlZFJvd09yZGVyEh8KBWl0ZW1zGAEgAygLMgkuUm93T3JkZXJSBWl0ZW1z');
+@$core.Deprecated('Use rawRowDescriptor instead')
+const RawRow$json = const {
+  '1': 'RawRow',
+  '2': const [
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+    const {'1': 'grid_id', '3': 2, '4': 1, '5': 9, '10': 'gridId'},
+    const {'1': 'cell_by_field_id', '3': 3, '4': 3, '5': 11, '6': '.RawRow.CellByFieldIdEntry', '10': 'cellByFieldId'},
+  ],
+  '3': const [RawRow_CellByFieldIdEntry$json],
+};
+
+@$core.Deprecated('Use rawRowDescriptor instead')
+const RawRow_CellByFieldIdEntry$json = const {
+  '1': 'CellByFieldIdEntry',
+  '2': const [
+    const {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'},
+    const {'1': 'value', '3': 2, '4': 1, '5': 11, '6': '.RawCell', '10': 'value'},
+  ],
+  '7': const {'7': true},
+};
+
+/// Descriptor for `RawRow`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List rawRowDescriptor = $convert.base64Decode('CgZSYXdSb3cSDgoCaWQYASABKAlSAmlkEhcKB2dyaWRfaWQYAiABKAlSBmdyaWRJZBJDChBjZWxsX2J5X2ZpZWxkX2lkGAMgAygLMhouUmF3Um93LkNlbGxCeUZpZWxkSWRFbnRyeVINY2VsbEJ5RmllbGRJZBpKChJDZWxsQnlGaWVsZElkRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSHgoFdmFsdWUYAiABKAsyCC5SYXdDZWxsUgV2YWx1ZToCOAE=');
+@$core.Deprecated('Use rawCellDescriptor instead')
+const RawCell$json = const {
+  '1': 'RawCell',
+  '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': 11, '6': '.AnyData', '10': 'data'},
+  ],
+};
+
+/// Descriptor for `RawCell`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List rawCellDescriptor = $convert.base64Decode('CgdSYXdDZWxsEg4KAmlkGAEgASgJUgJpZBIVCgZyb3dfaWQYAiABKAlSBXJvd0lkEhkKCGZpZWxkX2lkGAMgASgJUgdmaWVsZElkEhwKBGRhdGEYBCABKAsyCC5BbnlEYXRhUgRkYXRh');
+@$core.Deprecated('Use repeatedRowDescriptor instead')
+const RepeatedRow$json = const {
+  '1': 'RepeatedRow',
+  '2': const [
+    const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.Row', '10': 'items'},
+  ],
+};
+
+/// Descriptor for `RepeatedRow`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List repeatedRowDescriptor = $convert.base64Decode('CgtSZXBlYXRlZFJvdxIaCgVpdGVtcxgBIAMoCzIELlJvd1IFaXRlbXM=');
+@$core.Deprecated('Use rowDescriptor instead')
+const Row$json = const {
+  '1': 'Row',
+  '2': const [
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+    const {'1': 'cell_by_field_id', '3': 2, '4': 3, '5': 11, '6': '.Row.CellByFieldIdEntry', '10': 'cellByFieldId'},
+  ],
+  '3': const [Row_CellByFieldIdEntry$json],
+};
+
+@$core.Deprecated('Use rowDescriptor instead')
+const Row_CellByFieldIdEntry$json = const {
+  '1': 'CellByFieldIdEntry',
+  '2': const [
+    const {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'},
+    const {'1': 'value', '3': 2, '4': 1, '5': 11, '6': '.Cell', '10': 'value'},
+  ],
+  '7': const {'7': true},
+};
+
+/// Descriptor for `Row`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List rowDescriptor = $convert.base64Decode('CgNSb3cSDgoCaWQYASABKAlSAmlkEkAKEGNlbGxfYnlfZmllbGRfaWQYAiADKAsyFy5Sb3cuQ2VsbEJ5RmllbGRJZEVudHJ5Ug1jZWxsQnlGaWVsZElkGkcKEkNlbGxCeUZpZWxkSWRFbnRyeRIQCgNrZXkYASABKAlSA2tleRIbCgV2YWx1ZRgCIAEoCzIFLkNlbGxSBXZhbHVlOgI4AQ==');
+@$core.Deprecated('Use cellDescriptor instead')
+const Cell$json = const {
+  '1': 'Cell',
+  '2': const [
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+    const {'1': 'field_id', '3': 2, '4': 1, '5': 9, '10': 'fieldId'},
+    const {'1': 'content', '3': 3, '4': 1, '5': 9, '10': 'content'},
+  ],
+};
+
+/// 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',
+  '2': const [
+    const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
+  ],
+};
+
+/// Descriptor for `CreateGridPayload`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List createGridPayloadDescriptor = $convert.base64Decode('ChFDcmVhdGVHcmlkUGF5bG9hZBISCgRuYW1lGAEgASgJUgRuYW1l');
+@$core.Deprecated('Use gridIdDescriptor instead')
+const GridId$json = const {
+  '1': 'GridId',
+  '2': const [
+    const {'1': 'value', '3': 1, '4': 1, '5': 9, '10': 'value'},
+  ],
+};
+
+/// Descriptor for `GridId`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List gridIdDescriptor = $convert.base64Decode('CgZHcmlkSWQSFAoFdmFsdWUYASABKAlSBXZhbHVl');
+@$core.Deprecated('Use queryFieldPayloadDescriptor instead')
+const QueryFieldPayload$json = const {
+  '1': 'QueryFieldPayload',
+  '2': const [
+    const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
+    const {'1': 'field_orders', '3': 2, '4': 1, '5': 11, '6': '.RepeatedFieldOrder', '10': 'fieldOrders'},
+  ],
+};
+
+/// Descriptor for `QueryFieldPayload`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List queryFieldPayloadDescriptor = $convert.base64Decode('ChFRdWVyeUZpZWxkUGF5bG9hZBIXCgdncmlkX2lkGAEgASgJUgZncmlkSWQSNgoMZmllbGRfb3JkZXJzGAIgASgLMhMuUmVwZWF0ZWRGaWVsZE9yZGVyUgtmaWVsZE9yZGVycw==');
+@$core.Deprecated('Use queryRowPayloadDescriptor instead')
+const QueryRowPayload$json = const {
+  '1': 'QueryRowPayload',
+  '2': const [
+    const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
+    const {'1': 'row_orders', '3': 2, '4': 1, '5': 11, '6': '.RepeatedRowOrder', '10': 'rowOrders'},
+  ],
+};
+
+/// Descriptor for `QueryRowPayload`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List queryRowPayloadDescriptor = $convert.base64Decode('Cg9RdWVyeVJvd1BheWxvYWQSFwoHZ3JpZF9pZBgBIAEoCVIGZ3JpZElkEjAKCnJvd19vcmRlcnMYAiABKAsyES5SZXBlYXRlZFJvd09yZGVyUglyb3dPcmRlcnM=');

+ 2 - 2
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/share.pbserver.dart → frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbserver.dart

@@ -1,9 +1,9 @@
 ///
 //  Generated code. Do not modify.
-//  source: share.proto
+//  source: grid.proto
 //
 // @dart = 2.12
 // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
 
-export 'share.pb.dart';
+export 'grid.pb.dart';
 

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

@@ -0,0 +1,2 @@
+// Auto-generated, do not edit 
+export './grid.pb.dart';

+ 458 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_data.pb.dart

@@ -0,0 +1,458 @@
+///
+//  Generated code. Do not modify.
+//  source: cell_data.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'cell_data.pbenum.dart';
+
+export 'cell_data.pbenum.dart';
+
+class RichTextDescription extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RichTextDescription', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'format')
+    ..hasRequiredFields = false
+  ;
+
+  RichTextDescription._() : super();
+  factory RichTextDescription({
+    $core.String? format,
+  }) {
+    final _result = create();
+    if (format != null) {
+      _result.format = format;
+    }
+    return _result;
+  }
+  factory RichTextDescription.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RichTextDescription.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')
+  RichTextDescription clone() => RichTextDescription()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  RichTextDescription copyWith(void Function(RichTextDescription) updates) => super.copyWith((message) => updates(message as RichTextDescription)) as RichTextDescription; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static RichTextDescription create() => RichTextDescription._();
+  RichTextDescription createEmptyInstance() => create();
+  static $pb.PbList<RichTextDescription> createRepeated() => $pb.PbList<RichTextDescription>();
+  @$core.pragma('dart2js:noInline')
+  static RichTextDescription getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RichTextDescription>(create);
+  static RichTextDescription? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get format => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set format($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasFormat() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearFormat() => clearField(1);
+}
+
+class CheckboxDescription extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CheckboxDescription', createEmptyInstance: create)
+    ..aOB(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'isSelected')
+    ..hasRequiredFields = false
+  ;
+
+  CheckboxDescription._() : super();
+  factory CheckboxDescription({
+    $core.bool? isSelected,
+  }) {
+    final _result = create();
+    if (isSelected != null) {
+      _result.isSelected = isSelected;
+    }
+    return _result;
+  }
+  factory CheckboxDescription.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory CheckboxDescription.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')
+  CheckboxDescription clone() => CheckboxDescription()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  CheckboxDescription copyWith(void Function(CheckboxDescription) updates) => super.copyWith((message) => updates(message as CheckboxDescription)) as CheckboxDescription; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static CheckboxDescription create() => CheckboxDescription._();
+  CheckboxDescription createEmptyInstance() => create();
+  static $pb.PbList<CheckboxDescription> createRepeated() => $pb.PbList<CheckboxDescription>();
+  @$core.pragma('dart2js:noInline')
+  static CheckboxDescription getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CheckboxDescription>(create);
+  static CheckboxDescription? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.bool get isSelected => $_getBF(0);
+  @$pb.TagNumber(1)
+  set isSelected($core.bool v) { $_setBool(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasIsSelected() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearIsSelected() => clearField(1);
+}
+
+class DateDescription extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DateDescription', createEmptyInstance: create)
+    ..e<DateFormat>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dateFormat', $pb.PbFieldType.OE, defaultOrMaker: DateFormat.Local, valueOf: DateFormat.valueOf, enumValues: DateFormat.values)
+    ..e<TimeFormat>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'timeFormat', $pb.PbFieldType.OE, defaultOrMaker: TimeFormat.TwelveHour, valueOf: TimeFormat.valueOf, enumValues: TimeFormat.values)
+    ..hasRequiredFields = false
+  ;
+
+  DateDescription._() : super();
+  factory DateDescription({
+    DateFormat? dateFormat,
+    TimeFormat? timeFormat,
+  }) {
+    final _result = create();
+    if (dateFormat != null) {
+      _result.dateFormat = dateFormat;
+    }
+    if (timeFormat != null) {
+      _result.timeFormat = timeFormat;
+    }
+    return _result;
+  }
+  factory DateDescription.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory DateDescription.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')
+  DateDescription clone() => DateDescription()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  DateDescription copyWith(void Function(DateDescription) updates) => super.copyWith((message) => updates(message as DateDescription)) as DateDescription; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static DateDescription create() => DateDescription._();
+  DateDescription createEmptyInstance() => create();
+  static $pb.PbList<DateDescription> createRepeated() => $pb.PbList<DateDescription>();
+  @$core.pragma('dart2js:noInline')
+  static DateDescription getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DateDescription>(create);
+  static DateDescription? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  DateFormat get dateFormat => $_getN(0);
+  @$pb.TagNumber(1)
+  set dateFormat(DateFormat v) { setField(1, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasDateFormat() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearDateFormat() => clearField(1);
+
+  @$pb.TagNumber(2)
+  TimeFormat get timeFormat => $_getN(1);
+  @$pb.TagNumber(2)
+  set timeFormat(TimeFormat v) { setField(2, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasTimeFormat() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearTimeFormat() => clearField(2);
+}
+
+class SingleSelect extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SingleSelect', createEmptyInstance: create)
+    ..pc<SelectOption>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'options', $pb.PbFieldType.PM, subBuilder: SelectOption.create)
+    ..aOB(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'disableColor')
+    ..hasRequiredFields = false
+  ;
+
+  SingleSelect._() : super();
+  factory SingleSelect({
+    $core.Iterable<SelectOption>? options,
+    $core.bool? disableColor,
+  }) {
+    final _result = create();
+    if (options != null) {
+      _result.options.addAll(options);
+    }
+    if (disableColor != null) {
+      _result.disableColor = disableColor;
+    }
+    return _result;
+  }
+  factory SingleSelect.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory SingleSelect.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')
+  SingleSelect clone() => SingleSelect()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  SingleSelect copyWith(void Function(SingleSelect) updates) => super.copyWith((message) => updates(message as SingleSelect)) as SingleSelect; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static SingleSelect create() => SingleSelect._();
+  SingleSelect createEmptyInstance() => create();
+  static $pb.PbList<SingleSelect> createRepeated() => $pb.PbList<SingleSelect>();
+  @$core.pragma('dart2js:noInline')
+  static SingleSelect getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SingleSelect>(create);
+  static SingleSelect? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<SelectOption> get options => $_getList(0);
+
+  @$pb.TagNumber(2)
+  $core.bool get disableColor => $_getBF(1);
+  @$pb.TagNumber(2)
+  set disableColor($core.bool v) { $_setBool(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasDisableColor() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearDisableColor() => clearField(2);
+}
+
+class MultiSelect extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'MultiSelect', createEmptyInstance: create)
+    ..pc<SelectOption>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'options', $pb.PbFieldType.PM, subBuilder: SelectOption.create)
+    ..aOB(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'disableColor')
+    ..hasRequiredFields = false
+  ;
+
+  MultiSelect._() : super();
+  factory MultiSelect({
+    $core.Iterable<SelectOption>? options,
+    $core.bool? disableColor,
+  }) {
+    final _result = create();
+    if (options != null) {
+      _result.options.addAll(options);
+    }
+    if (disableColor != null) {
+      _result.disableColor = disableColor;
+    }
+    return _result;
+  }
+  factory MultiSelect.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory MultiSelect.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')
+  MultiSelect clone() => MultiSelect()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  MultiSelect copyWith(void Function(MultiSelect) updates) => super.copyWith((message) => updates(message as MultiSelect)) as MultiSelect; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static MultiSelect create() => MultiSelect._();
+  MultiSelect createEmptyInstance() => create();
+  static $pb.PbList<MultiSelect> createRepeated() => $pb.PbList<MultiSelect>();
+  @$core.pragma('dart2js:noInline')
+  static MultiSelect getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<MultiSelect>(create);
+  static MultiSelect? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<SelectOption> get options => $_getList(0);
+
+  @$pb.TagNumber(2)
+  $core.bool get disableColor => $_getBF(1);
+  @$pb.TagNumber(2)
+  set disableColor($core.bool v) { $_setBool(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasDisableColor() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearDisableColor() => clearField(2);
+}
+
+class SelectOption extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SelectOption', 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') ? '' : 'color')
+    ..hasRequiredFields = false
+  ;
+
+  SelectOption._() : super();
+  factory SelectOption({
+    $core.String? id,
+    $core.String? name,
+    $core.String? color,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    if (name != null) {
+      _result.name = name;
+    }
+    if (color != null) {
+      _result.color = color;
+    }
+    return _result;
+  }
+  factory SelectOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory SelectOption.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')
+  SelectOption clone() => SelectOption()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  SelectOption copyWith(void Function(SelectOption) updates) => super.copyWith((message) => updates(message as SelectOption)) as SelectOption; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static SelectOption create() => SelectOption._();
+  SelectOption createEmptyInstance() => create();
+  static $pb.PbList<SelectOption> createRepeated() => $pb.PbList<SelectOption>();
+  @$core.pragma('dart2js:noInline')
+  static SelectOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SelectOption>(create);
+  static SelectOption? _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 color => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set color($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasColor() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearColor() => clearField(3);
+}
+
+class NumberDescription extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'NumberDescription', createEmptyInstance: create)
+    ..e<FlowyMoney>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'money', $pb.PbFieldType.OE, defaultOrMaker: FlowyMoney.CNY, valueOf: FlowyMoney.valueOf, enumValues: FlowyMoney.values)
+    ..a<$core.int>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'scale', $pb.PbFieldType.OU3)
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'symbol')
+    ..aOB(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'signPositive')
+    ..aOS(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
+    ..hasRequiredFields = false
+  ;
+
+  NumberDescription._() : super();
+  factory NumberDescription({
+    FlowyMoney? money,
+    $core.int? scale,
+    $core.String? symbol,
+    $core.bool? signPositive,
+    $core.String? name,
+  }) {
+    final _result = create();
+    if (money != null) {
+      _result.money = money;
+    }
+    if (scale != null) {
+      _result.scale = scale;
+    }
+    if (symbol != null) {
+      _result.symbol = symbol;
+    }
+    if (signPositive != null) {
+      _result.signPositive = signPositive;
+    }
+    if (name != null) {
+      _result.name = name;
+    }
+    return _result;
+  }
+  factory NumberDescription.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory NumberDescription.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')
+  NumberDescription clone() => NumberDescription()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  NumberDescription copyWith(void Function(NumberDescription) updates) => super.copyWith((message) => updates(message as NumberDescription)) as NumberDescription; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static NumberDescription create() => NumberDescription._();
+  NumberDescription createEmptyInstance() => create();
+  static $pb.PbList<NumberDescription> createRepeated() => $pb.PbList<NumberDescription>();
+  @$core.pragma('dart2js:noInline')
+  static NumberDescription getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<NumberDescription>(create);
+  static NumberDescription? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  FlowyMoney get money => $_getN(0);
+  @$pb.TagNumber(1)
+  set money(FlowyMoney v) { setField(1, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasMoney() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearMoney() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.int get scale => $_getIZ(1);
+  @$pb.TagNumber(2)
+  set scale($core.int v) { $_setUnsignedInt32(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasScale() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearScale() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.String get symbol => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set symbol($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasSymbol() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearSymbol() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $core.bool get signPositive => $_getBF(3);
+  @$pb.TagNumber(4)
+  set signPositive($core.bool v) { $_setBool(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasSignPositive() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearSignPositive() => clearField(4);
+
+  @$pb.TagNumber(5)
+  $core.String get name => $_getSZ(4);
+  @$pb.TagNumber(5)
+  set name($core.String v) { $_setString(4, v); }
+  @$pb.TagNumber(5)
+  $core.bool hasName() => $_has(4);
+  @$pb.TagNumber(5)
+  void clearName() => clearField(5);
+}
+

+ 62 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_data.pbenum.dart

@@ -0,0 +1,62 @@
+///
+//  Generated code. Do not modify.
+//  source: cell_data.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+// ignore_for_file: UNDEFINED_SHOWN_NAME
+import 'dart:core' as $core;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class DateFormat extends $pb.ProtobufEnum {
+  static const DateFormat Local = DateFormat._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Local');
+  static const DateFormat US = DateFormat._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'US');
+  static const DateFormat ISO = DateFormat._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ISO');
+  static const DateFormat Friendly = DateFormat._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Friendly');
+
+  static const $core.List<DateFormat> values = <DateFormat> [
+    Local,
+    US,
+    ISO,
+    Friendly,
+  ];
+
+  static final $core.Map<$core.int, DateFormat> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static DateFormat? valueOf($core.int value) => _byValue[value];
+
+  const DateFormat._($core.int v, $core.String n) : super(v, n);
+}
+
+class TimeFormat extends $pb.ProtobufEnum {
+  static const TimeFormat TwelveHour = TimeFormat._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TwelveHour');
+  static const TimeFormat TwentyFourHour = TimeFormat._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TwentyFourHour');
+
+  static const $core.List<TimeFormat> values = <TimeFormat> [
+    TwelveHour,
+    TwentyFourHour,
+  ];
+
+  static final $core.Map<$core.int, TimeFormat> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static TimeFormat? valueOf($core.int value) => _byValue[value];
+
+  const TimeFormat._($core.int v, $core.String n) : super(v, n);
+}
+
+class FlowyMoney extends $pb.ProtobufEnum {
+  static const FlowyMoney CNY = FlowyMoney._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CNY');
+  static const FlowyMoney EUR = FlowyMoney._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EUR');
+  static const FlowyMoney USD = FlowyMoney._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'USD');
+
+  static const $core.List<FlowyMoney> values = <FlowyMoney> [
+    CNY,
+    EUR,
+    USD,
+  ];
+
+  static final $core.Map<$core.int, FlowyMoney> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static FlowyMoney? valueOf($core.int value) => _byValue[value];
+
+  const FlowyMoney._($core.int v, $core.String n) : super(v, n);
+}
+

+ 125 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_data.pbjson.dart

@@ -0,0 +1,125 @@
+///
+//  Generated code. Do not modify.
+//  source: cell_data.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+import 'dart:core' as $core;
+import 'dart:convert' as $convert;
+import 'dart:typed_data' as $typed_data;
+@$core.Deprecated('Use dateFormatDescriptor instead')
+const DateFormat$json = const {
+  '1': 'DateFormat',
+  '2': const [
+    const {'1': 'Local', '2': 0},
+    const {'1': 'US', '2': 1},
+    const {'1': 'ISO', '2': 2},
+    const {'1': 'Friendly', '2': 3},
+  ],
+};
+
+/// Descriptor for `DateFormat`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List dateFormatDescriptor = $convert.base64Decode('CgpEYXRlRm9ybWF0EgkKBUxvY2FsEAASBgoCVVMQARIHCgNJU08QAhIMCghGcmllbmRseRAD');
+@$core.Deprecated('Use timeFormatDescriptor instead')
+const TimeFormat$json = const {
+  '1': 'TimeFormat',
+  '2': const [
+    const {'1': 'TwelveHour', '2': 0},
+    const {'1': 'TwentyFourHour', '2': 1},
+  ],
+};
+
+/// Descriptor for `TimeFormat`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List timeFormatDescriptor = $convert.base64Decode('CgpUaW1lRm9ybWF0Eg4KClR3ZWx2ZUhvdXIQABISCg5Ud2VudHlGb3VySG91chAB');
+@$core.Deprecated('Use flowyMoneyDescriptor instead')
+const FlowyMoney$json = const {
+  '1': 'FlowyMoney',
+  '2': const [
+    const {'1': 'CNY', '2': 0},
+    const {'1': 'EUR', '2': 1},
+    const {'1': 'USD', '2': 2},
+  ],
+};
+
+/// Descriptor for `FlowyMoney`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List flowyMoneyDescriptor = $convert.base64Decode('CgpGbG93eU1vbmV5EgcKA0NOWRAAEgcKA0VVUhABEgcKA1VTRBAC');
+@$core.Deprecated('Use richTextDescriptionDescriptor instead')
+const RichTextDescription$json = const {
+  '1': 'RichTextDescription',
+  '2': const [
+    const {'1': 'format', '3': 1, '4': 1, '5': 9, '10': 'format'},
+  ],
+};
+
+/// Descriptor for `RichTextDescription`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List richTextDescriptionDescriptor = $convert.base64Decode('ChNSaWNoVGV4dERlc2NyaXB0aW9uEhYKBmZvcm1hdBgBIAEoCVIGZm9ybWF0');
+@$core.Deprecated('Use checkboxDescriptionDescriptor instead')
+const CheckboxDescription$json = const {
+  '1': 'CheckboxDescription',
+  '2': const [
+    const {'1': 'is_selected', '3': 1, '4': 1, '5': 8, '10': 'isSelected'},
+  ],
+};
+
+/// Descriptor for `CheckboxDescription`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List checkboxDescriptionDescriptor = $convert.base64Decode('ChNDaGVja2JveERlc2NyaXB0aW9uEh8KC2lzX3NlbGVjdGVkGAEgASgIUgppc1NlbGVjdGVk');
+@$core.Deprecated('Use dateDescriptionDescriptor instead')
+const DateDescription$json = const {
+  '1': 'DateDescription',
+  '2': const [
+    const {'1': 'date_format', '3': 1, '4': 1, '5': 14, '6': '.DateFormat', '10': 'dateFormat'},
+    const {'1': 'time_format', '3': 2, '4': 1, '5': 14, '6': '.TimeFormat', '10': 'timeFormat'},
+  ],
+};
+
+/// Descriptor for `DateDescription`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List dateDescriptionDescriptor = $convert.base64Decode('Cg9EYXRlRGVzY3JpcHRpb24SLAoLZGF0ZV9mb3JtYXQYASABKA4yCy5EYXRlRm9ybWF0UgpkYXRlRm9ybWF0EiwKC3RpbWVfZm9ybWF0GAIgASgOMgsuVGltZUZvcm1hdFIKdGltZUZvcm1hdA==');
+@$core.Deprecated('Use singleSelectDescriptor instead')
+const SingleSelect$json = const {
+  '1': 'SingleSelect',
+  '2': const [
+    const {'1': 'options', '3': 1, '4': 3, '5': 11, '6': '.SelectOption', '10': 'options'},
+    const {'1': 'disable_color', '3': 2, '4': 1, '5': 8, '10': 'disableColor'},
+  ],
+};
+
+/// Descriptor for `SingleSelect`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List singleSelectDescriptor = $convert.base64Decode('CgxTaW5nbGVTZWxlY3QSJwoHb3B0aW9ucxgBIAMoCzINLlNlbGVjdE9wdGlvblIHb3B0aW9ucxIjCg1kaXNhYmxlX2NvbG9yGAIgASgIUgxkaXNhYmxlQ29sb3I=');
+@$core.Deprecated('Use multiSelectDescriptor instead')
+const MultiSelect$json = const {
+  '1': 'MultiSelect',
+  '2': const [
+    const {'1': 'options', '3': 1, '4': 3, '5': 11, '6': '.SelectOption', '10': 'options'},
+    const {'1': 'disable_color', '3': 2, '4': 1, '5': 8, '10': 'disableColor'},
+  ],
+};
+
+/// Descriptor for `MultiSelect`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List multiSelectDescriptor = $convert.base64Decode('CgtNdWx0aVNlbGVjdBInCgdvcHRpb25zGAEgAygLMg0uU2VsZWN0T3B0aW9uUgdvcHRpb25zEiMKDWRpc2FibGVfY29sb3IYAiABKAhSDGRpc2FibGVDb2xvcg==');
+@$core.Deprecated('Use selectOptionDescriptor instead')
+const SelectOption$json = const {
+  '1': 'SelectOption',
+  '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': 'color', '3': 3, '4': 1, '5': 9, '10': 'color'},
+  ],
+};
+
+/// Descriptor for `SelectOption`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List selectOptionDescriptor = $convert.base64Decode('CgxTZWxlY3RPcHRpb24SDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSFAoFY29sb3IYAyABKAlSBWNvbG9y');
+@$core.Deprecated('Use numberDescriptionDescriptor instead')
+const NumberDescription$json = const {
+  '1': 'NumberDescription',
+  '2': const [
+    const {'1': 'money', '3': 1, '4': 1, '5': 14, '6': '.FlowyMoney', '10': 'money'},
+    const {'1': 'scale', '3': 2, '4': 1, '5': 13, '10': 'scale'},
+    const {'1': 'symbol', '3': 3, '4': 1, '5': 9, '10': 'symbol'},
+    const {'1': 'sign_positive', '3': 4, '4': 1, '5': 8, '10': 'signPositive'},
+    const {'1': 'name', '3': 5, '4': 1, '5': 9, '10': 'name'},
+  ],
+};
+
+/// Descriptor for `NumberDescription`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List numberDescriptionDescriptor = $convert.base64Decode('ChFOdW1iZXJEZXNjcmlwdGlvbhIhCgVtb25leRgBIAEoDjILLkZsb3d5TW9uZXlSBW1vbmV5EhQKBXNjYWxlGAIgASgNUgVzY2FsZRIWCgZzeW1ib2wYAyABKAlSBnN5bWJvbBIjCg1zaWduX3Bvc2l0aXZlGAQgASgIUgxzaWduUG9zaXRpdmUSEgoEbmFtZRgFIAEoCVIEbmFtZQ==');

+ 9 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_data.pbserver.dart

@@ -0,0 +1,9 @@
+///
+//  Generated code. Do not modify.
+//  source: cell_data.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+export 'cell_data.pb.dart';
+

+ 11 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pb.dart

@@ -0,0 +1,11 @@
+///
+//  Generated code. Do not modify.
+//  source: event_map.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+import 'dart:core' as $core;
+
+export 'event_map.pbenum.dart';
+

+ 30 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart

@@ -0,0 +1,30 @@
+///
+//  Generated code. Do not modify.
+//  source: event_map.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+// ignore_for_file: UNDEFINED_SHOWN_NAME
+import 'dart:core' as $core;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class GridEvent extends $pb.ProtobufEnum {
+  static const GridEvent GetGridData = GridEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetGridData');
+  static const GridEvent GetRows = GridEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetRows');
+  static const GridEvent GetFields = GridEvent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetFields');
+  static const GridEvent CreateRow = GridEvent._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateRow');
+
+  static const $core.List<GridEvent> values = <GridEvent> [
+    GetGridData,
+    GetRows,
+    GetFields,
+    CreateRow,
+  ];
+
+  static final $core.Map<$core.int, GridEvent> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static GridEvent? valueOf($core.int value) => _byValue[value];
+
+  const GridEvent._($core.int v, $core.String n) : super(v, n);
+}
+

+ 23 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart

@@ -0,0 +1,23 @@
+///
+//  Generated code. Do not modify.
+//  source: event_map.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+import 'dart:core' as $core;
+import 'dart:convert' as $convert;
+import 'dart:typed_data' as $typed_data;
+@$core.Deprecated('Use gridEventDescriptor instead')
+const GridEvent$json = const {
+  '1': 'GridEvent',
+  '2': const [
+    const {'1': 'GetGridData', '2': 0},
+    const {'1': 'GetRows', '2': 1},
+    const {'1': 'GetFields', '2': 2},
+    const {'1': 'CreateRow', '2': 3},
+  ],
+};
+
+/// Descriptor for `GridEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABILCgdHZXRSb3dzEAESDQoJR2V0RmllbGRzEAISDQoJQ3JlYXRlUm93EAM=');

+ 9 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbserver.dart

@@ -0,0 +1,9 @@
+///
+//  Generated code. Do not modify.
+//  source: event_map.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+export 'event_map.pb.dart';
+

+ 3 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/protobuf.dart

@@ -0,0 +1,3 @@
+// Auto-generated, do not edit 
+export './cell_data.pb.dart';
+export './event_map.pb.dart';

+ 2 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/lib-ws/msg.pbenum.dart

@@ -12,10 +12,12 @@ import 'package:protobuf/protobuf.dart' as $pb;
 class WSChannel extends $pb.ProtobufEnum {
   static const WSChannel Document = WSChannel._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Document');
   static const WSChannel Folder = WSChannel._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Folder');
+  static const WSChannel Grid = WSChannel._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Grid');
 
   static const $core.List<WSChannel> values = <WSChannel> [
     Document,
     Folder,
+    Grid,
   ];
 
   static final $core.Map<$core.int, WSChannel> _byValue = $pb.ProtobufEnum.initByValue(values);

+ 2 - 1
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/lib-ws/msg.pbjson.dart

@@ -14,11 +14,12 @@ const WSChannel$json = const {
   '2': const [
     const {'1': 'Document', '2': 0},
     const {'1': 'Folder', '2': 1},
+    const {'1': 'Grid', '2': 2},
   ],
 };
 
 /// Descriptor for `WSChannel`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List wSChannelDescriptor = $convert.base64Decode('CglXU0NoYW5uZWwSDAoIRG9jdW1lbnQQABIKCgZGb2xkZXIQAQ==');
+final $typed_data.Uint8List wSChannelDescriptor = $convert.base64Decode('CglXU0NoYW5uZWwSDAoIRG9jdW1lbnQQABIKCgZGb2xkZXIQARIICgRHcmlkEAI=');
 @$core.Deprecated('Use webSocketRawMessageDescriptor instead')
 const WebSocketRawMessage$json = const {
   '1': 'WebSocketRawMessage',

+ 16 - 2
frontend/app_flowy/packages/flowy_sdk/pubspec.lock

@@ -214,6 +214,13 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  freezed:
+    dependency: "direct dev"
+    description:
+      name: freezed
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.14.1+2"
   freezed_annotation:
     dependency: "direct main"
     description:
@@ -339,7 +346,7 @@ packages:
       name: path
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.8.0"
+    version: "1.8.1"
   pedantic:
     dependency: transitive
     description:
@@ -394,6 +401,13 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.99"
+  source_gen:
+    dependency: transitive
+    description:
+      name: source_gen
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.0"
   source_span:
     dependency: transitive
     description:
@@ -442,7 +456,7 @@ packages:
       name: test_api
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.4.8"
+    version: "0.4.9"
   timing:
     dependency: transitive
     description:

+ 88 - 1
frontend/rust-lib/Cargo.lock

@@ -57,6 +57,12 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
 
+[[package]]
+name = "arrayvec"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
+
 [[package]]
 name = "async-stream"
 version = "0.3.2"
@@ -903,6 +909,7 @@ dependencies = [
  "dissimilar",
  "flowy-derive",
  "flowy-folder-data-model",
+ "flowy-grid-data-model",
  "futures",
  "lib-infra",
  "lib-ot",
@@ -985,6 +992,7 @@ dependencies = [
  "crossbeam",
  "crossbeam-utils",
  "dart-notify",
+ "dashmap",
  "derive_more",
  "diesel",
  "diesel_derives",
@@ -1038,6 +1046,53 @@ dependencies = [
  "uuid",
 ]
 
+[[package]]
+name = "flowy-grid"
+version = "0.1.0"
+dependencies = [
+ "bytes",
+ "chrono",
+ "dart-notify",
+ "dashmap",
+ "diesel",
+ "flowy-collaboration",
+ "flowy-database",
+ "flowy-derive",
+ "flowy-error",
+ "flowy-grid-data-model",
+ "flowy-sync",
+ "lazy_static",
+ "lib-dispatch",
+ "lib-infra",
+ "lib-ot",
+ "lib-sqlite",
+ "parking_lot",
+ "protobuf",
+ "rayon",
+ "rust_decimal",
+ "rusty-money",
+ "strum",
+ "strum_macros",
+ "tokio",
+ "tracing",
+ "uuid",
+]
+
+[[package]]
+name = "flowy-grid-data-model"
+version = "0.1.0"
+dependencies = [
+ "bytes",
+ "flowy-derive",
+ "lib-infra",
+ "protobuf",
+ "serde",
+ "serde_json",
+ "strum",
+ "strum_macros",
+ "uuid",
+]
+
 [[package]]
 name = "flowy-net"
 version = "0.1.0"
@@ -1087,6 +1142,7 @@ dependencies = [
  "flowy-collaboration",
  "flowy-database",
  "flowy-folder",
+ "flowy-grid",
  "flowy-net",
  "flowy-sync",
  "flowy-user",
@@ -1676,7 +1732,7 @@ version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
 dependencies = [
- "arrayvec",
+ "arrayvec 0.5.2",
  "bitflags",
  "cfg-if",
  "ryu",
@@ -2726,6 +2782,27 @@ dependencies = [
  "winreg",
 ]
 
+[[package]]
+name = "rust_decimal"
+version = "1.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d37baa70cf8662d2ba1c1868c5983dda16ef32b105cce41fb5c47e72936a90b3"
+dependencies = [
+ "arrayvec 0.7.2",
+ "num-traits",
+ "serde",
+]
+
+[[package]]
+name = "rust_decimal_macros"
+version = "1.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "184abaf7b434800e1a5a8aad3ebc8cd7498df33af72d65371d797a264713a59b"
+dependencies = [
+ "quote",
+ "rust_decimal",
+]
+
 [[package]]
 name = "rustc-demangle"
 version = "0.1.21"
@@ -2747,6 +2824,16 @@ version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
 
+[[package]]
+name = "rusty-money"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b28f881005eac7ad8d46b6f075da5f322bd7f4f83a38720fc069694ddadd683"
+dependencies = [
+ "rust_decimal",
+ "rust_decimal_macros",
+]
+
 [[package]]
 name = "ryu"
 version = "1.0.9"

+ 1 - 0
frontend/rust-lib/Cargo.toml

@@ -14,6 +14,7 @@ members = [
   "flowy-block",
   "flowy-error",
   "flowy-sync",
+  "flowy-grid",
 ]
 
 [profile.dev]

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

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

+ 5 - 1
frontend/rust-lib/flowy-block/Cargo.toml

@@ -52,6 +52,10 @@ color-eyre = { version = "0.5", default-features = false }
 criterion = "0.3"
 rand = "0.7.3"
 
+[build-dependencies]
+lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file_gen", "proto_gen"] }
+
 [features]
 http_server = []
-flowy_unit_test = ["lib-ot/flowy_unit_test", "flowy-sync/flowy_unit_test"]
+flowy_unit_test = ["lib-ot/flowy_unit_test", "flowy-sync/flowy_unit_test"]
+dart = ["lib-infra/dart"]

+ 3 - 0
frontend/rust-lib/flowy-block/Flowy.toml

@@ -0,0 +1,3 @@
+
+proto_crates = ["src/event_map.rs", "src/entities.rs"]
+event_files = ["src/event_map.rs"]

+ 9 - 0
frontend/rust-lib/flowy-block/build.rs

@@ -0,0 +1,9 @@
+use lib_infra::code_gen;
+
+fn main() {
+    let crate_name = env!("CARGO_PKG_NAME");
+    code_gen::protobuf_file::gen(crate_name, "./src/protobuf/proto");
+
+    #[cfg(feature = "dart")]
+    code_gen::dart_event::gen(crate_name);
+}

+ 5 - 5
frontend/rust-lib/flowy-block/src/block_editor.rs

@@ -140,9 +140,9 @@ impl ClientBlockEditor {
         Ok(())
     }
 
-    pub async fn block_json(&self) -> FlowyResult<String> {
+    pub async fn delta_str(&self) -> FlowyResult<String> {
         let (ret, rx) = oneshot::channel::<CollaborateResult<String>>();
-        let msg = EditorCommand::ReadBlockJson { ret };
+        let msg = EditorCommand::ReadDeltaStr { ret };
         let _ = self.edit_cmd_tx.send(msg).await;
         let json = rx.await.map_err(internal_error)??;
         Ok(json)
@@ -196,7 +196,7 @@ fn spawn_edit_queue(
 impl ClientBlockEditor {
     pub async fn doc_json(&self) -> FlowyResult<String> {
         let (ret, rx) = oneshot::channel::<CollaborateResult<String>>();
-        let msg = EditorCommand::ReadBlockJson { ret };
+        let msg = EditorCommand::ReadDeltaStr { ret };
         let _ = self.edit_cmd_tx.send(msg).await;
         let s = rx.await.map_err(internal_error)??;
         Ok(s)
@@ -225,8 +225,8 @@ impl RevisionObjectBuilder for BlockInfoBuilder {
         correct_delta(&mut delta);
 
         Result::<BlockInfo, FlowyError>::Ok(BlockInfo {
-            doc_id: object_id.to_owned(),
-            text: delta.to_delta_json(),
+            block_id: object_id.to_owned(),
+            text: delta.to_delta_str(),
             rev_id,
             base_rev_id,
         })

+ 0 - 0
shared-lib/flowy-folder-data-model/src/entities/share.rs → frontend/rust-lib/flowy-block/src/entities.rs


+ 42 - 0
frontend/rust-lib/flowy-block/src/event_handler.rs

@@ -0,0 +1,42 @@
+use crate::entities::{ExportData, ExportParams, ExportPayload};
+use crate::BlockManager;
+use flowy_collaboration::entities::document_info::{BlockDelta, BlockId};
+use flowy_error::FlowyError;
+use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
+use std::convert::TryInto;
+use std::sync::Arc;
+
+pub(crate) async fn get_block_data_handler(
+    data: Data<BlockId>,
+    manager: AppData<Arc<BlockManager>>,
+) -> DataResult<BlockDelta, FlowyError> {
+    let block_id: BlockId = data.into_inner();
+    let editor = manager.open_block(&block_id).await?;
+    let delta_str = editor.delta_str().await?;
+    data_result(BlockDelta {
+        block_id: block_id.into(),
+        delta_str,
+    })
+}
+
+pub(crate) async fn apply_delta_handler(
+    data: Data<BlockDelta>,
+    manager: AppData<Arc<BlockManager>>,
+) -> DataResult<BlockDelta, FlowyError> {
+    let block_delta = manager.receive_local_delta(data.into_inner()).await?;
+    data_result(block_delta)
+}
+
+#[tracing::instrument(skip(data, manager), err)]
+pub(crate) async fn export_handler(
+    data: Data<ExportPayload>,
+    manager: AppData<Arc<BlockManager>>,
+) -> DataResult<ExportData, FlowyError> {
+    let params: ExportParams = data.into_inner().try_into()?;
+    let editor = manager.open_block(&params.view_id).await?;
+    let delta_json = editor.delta_str().await?;
+    data_result(ExportData {
+        data: delta_json,
+        export_type: params.export_type,
+    })
+}

+ 30 - 0
frontend/rust-lib/flowy-block/src/event_map.rs

@@ -0,0 +1,30 @@
+use crate::event_handler::*;
+use crate::BlockManager;
+use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
+use lib_dispatch::prelude::Module;
+use std::sync::Arc;
+use strum_macros::Display;
+
+pub fn create(block_manager: Arc<BlockManager>) -> Module {
+    let mut module = Module::new().name(env!("CARGO_PKG_NAME")).data(block_manager);
+
+    module = module
+        .event(BlockEvent::GetBlockData, get_block_data_handler)
+        .event(BlockEvent::ApplyDelta, apply_delta_handler)
+        .event(BlockEvent::ExportDocument, export_handler);
+
+    module
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
+#[event_err = "FlowyError"]
+pub enum BlockEvent {
+    #[event(input = "BlockId", output = "BlockDelta")]
+    GetBlockData = 0,
+
+    #[event(input = "BlockDelta", output = "BlockDelta")]
+    ApplyDelta = 1,
+
+    #[event(input = "ExportPayload", output = "ExportData")]
+    ExportDocument = 2,
+}

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

@@ -1,8 +1,12 @@
 pub mod block_editor;
+mod entities;
+mod event_handler;
+pub mod event_map;
 pub mod manager;
 mod queue;
 mod web_socket;
 
+pub mod protobuf;
 pub use manager::*;
 pub mod errors {
     pub use flowy_error::{internal_error, ErrorCode, FlowyError};
@@ -11,7 +15,7 @@ pub mod errors {
 pub const DOCUMENT_SYNC_INTERVAL_IN_MILLIS: u64 = 1000;
 
 use crate::errors::FlowyError;
-use flowy_collaboration::entities::document_info::{BlockId, BlockInfo, CreateBlockParams, ResetDocumentParams};
+use flowy_collaboration::entities::document_info::{BlockId, BlockInfo, CreateBlockParams, ResetBlockParams};
 use lib_infra::future::FutureResult;
 
 pub trait BlockCloudService: Send + Sync {
@@ -19,5 +23,5 @@ pub trait BlockCloudService: Send + Sync {
 
     fn read_block(&self, token: &str, params: BlockId) -> FutureResult<Option<BlockInfo>, FlowyError>;
 
-    fn update_block(&self, token: &str, params: ResetDocumentParams) -> FutureResult<(), FlowyError>;
+    fn update_block(&self, token: &str, params: ResetBlockParams) -> FutureResult<(), FlowyError>;
 }

+ 17 - 11
frontend/rust-lib/flowy-block/src/manager.rs

@@ -32,11 +32,11 @@ impl BlockManager {
         block_user: Arc<dyn BlockUser>,
         rev_web_socket: Arc<dyn RevisionWebSocket>,
     ) -> Self {
-        let block_handlers = Arc::new(BlockEditors::new());
+        let block_editors = Arc::new(BlockEditors::new());
         Self {
             cloud_service,
             rev_web_socket,
-            block_editors: block_handlers,
+            block_editors,
             block_user,
         }
     }
@@ -63,7 +63,7 @@ impl BlockManager {
     }
 
     #[tracing::instrument(level = "debug", skip(self, doc_id), fields(doc_id), err)]
-    pub fn delete<T: AsRef<str>>(&self, doc_id: T) -> Result<(), FlowyError> {
+    pub fn delete_block<T: AsRef<str>>(&self, doc_id: T) -> Result<(), FlowyError> {
         let doc_id = doc_id.as_ref();
         tracing::Span::current().record("doc_id", &doc_id);
         self.block_editors.remove(doc_id);
@@ -73,11 +73,11 @@ impl BlockManager {
     #[tracing::instrument(level = "debug", skip(self, delta), fields(doc_id = %delta.block_id), err)]
     pub async fn receive_local_delta(&self, delta: BlockDelta) -> Result<BlockDelta, FlowyError> {
         let editor = self.get_block_editor(&delta.block_id).await?;
-        let _ = editor.compose_local_delta(Bytes::from(delta.delta_json)).await?;
-        let document_json = editor.block_json().await?;
+        let _ = editor.compose_local_delta(Bytes::from(delta.delta_str)).await?;
+        let document_json = editor.delta_str().await?;
         Ok(BlockDelta {
             block_id: delta.block_id.clone(),
-            delta_json: document_json,
+            delta_str: document_json,
         })
     }
 
@@ -85,7 +85,7 @@ impl BlockManager {
         let doc_id = doc_id.as_ref().to_owned();
         let db_pool = self.block_user.db_pool()?;
         // Maybe we could save the block to disk without creating the RevisionManager
-        let rev_manager = self.make_rev_manager(&doc_id, db_pool)?;
+        let rev_manager = self.make_block_rev_manager(&doc_id, db_pool)?;
         let _ = rev_manager.reset_object(revisions).await?;
         Ok(())
     }
@@ -125,7 +125,7 @@ impl BlockManager {
     ) -> Result<Arc<ClientBlockEditor>, FlowyError> {
         let user = self.block_user.clone();
         let token = self.block_user.token()?;
-        let rev_manager = self.make_rev_manager(block_id, pool.clone())?;
+        let rev_manager = self.make_block_rev_manager(block_id, pool.clone())?;
         let cloud_service = Arc::new(BlockRevisionCloudService {
             token,
             server: self.cloud_service.clone(),
@@ -136,7 +136,7 @@ impl BlockManager {
         Ok(doc_editor)
     }
 
-    fn make_rev_manager(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<RevisionManager, FlowyError> {
+    fn make_block_rev_manager(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<RevisionManager, FlowyError> {
         let user_id = self.block_user.user_id()?;
         let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, doc_id, pool));
         Ok(RevisionManager::new(&user_id, doc_id, rev_persistence))
@@ -162,8 +162,14 @@ impl RevisionCloudService for BlockRevisionCloudService {
                 Some(doc) => {
                     let delta_data = Bytes::from(doc.text.clone());
                     let doc_md5 = md5(&delta_data);
-                    let revision =
-                        Revision::new(&doc.doc_id, doc.base_rev_id, doc.rev_id, delta_data, &user_id, doc_md5);
+                    let revision = Revision::new(
+                        &doc.block_id,
+                        doc.base_rev_id,
+                        doc.rev_id,
+                        delta_data,
+                        &user_id,
+                        doc_md5,
+                    );
                     Ok(vec![revision])
                 }
             }

+ 4 - 0
frontend/rust-lib/flowy-block/src/protobuf/mod.rs

@@ -0,0 +1,4 @@
+#![cfg_attr(rustfmt, rustfmt::skip)]
+// Auto-generated, do not edit
+mod model;
+pub use model::*;

+ 7 - 7
shared-lib/flowy-folder-data-model/src/protobuf/model/share.rs → frontend/rust-lib/flowy-block/src/protobuf/model/entities.rs

@@ -17,7 +17,7 @@
 #![allow(trivial_casts)]
 #![allow(unused_imports)]
 #![allow(unused_results)]
-//! Generated file from `share.proto`
+//! Generated file from `entities.proto`
 
 /// Generated files are compatible only with the same version
 /// of protobuf runtime.
@@ -457,12 +457,12 @@ impl ::protobuf::reflect::ProtobufValue for ExportType {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0bshare.proto\"V\n\rExportPayload\x12\x17\n\x07view_id\x18\x01\x20\
-    \x01(\tR\x06viewId\x12,\n\x0bexport_type\x18\x02\x20\x01(\x0e2\x0b.Expor\
-    tTypeR\nexportType\"N\n\nExportData\x12\x12\n\x04data\x18\x01\x20\x01(\t\
-    R\x04data\x12,\n\x0bexport_type\x18\x02\x20\x01(\x0e2\x0b.ExportTypeR\ne\
-    xportType*.\n\nExportType\x12\x08\n\x04Text\x10\0\x12\x0c\n\x08Markdown\
-    \x10\x01\x12\x08\n\x04Link\x10\x02b\x06proto3\
+    \n\x0eentities.proto\"V\n\rExportPayload\x12\x17\n\x07view_id\x18\x01\
+    \x20\x01(\tR\x06viewId\x12,\n\x0bexport_type\x18\x02\x20\x01(\x0e2\x0b.E\
+    xportTypeR\nexportType\"N\n\nExportData\x12\x12\n\x04data\x18\x01\x20\
+    \x01(\tR\x04data\x12,\n\x0bexport_type\x18\x02\x20\x01(\x0e2\x0b.ExportT\
+    ypeR\nexportType*.\n\nExportType\x12\x08\n\x04Text\x10\0\x12\x0c\n\x08Ma\
+    rkdown\x10\x01\x12\x08\n\x04Link\x10\x02b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 95 - 0
frontend/rust-lib/flowy-block/src/protobuf/model/event_map.rs

@@ -0,0 +1,95 @@
+// This file is generated by rust-protobuf 2.25.2. Do not edit
+// @generated
+
+// https://github.com/rust-lang/rust-clippy/issues/702
+#![allow(unknown_lints)]
+#![allow(clippy::all)]
+
+#![allow(unused_attributes)]
+#![cfg_attr(rustfmt, rustfmt::skip)]
+
+#![allow(box_pointers)]
+#![allow(dead_code)]
+#![allow(missing_docs)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(non_upper_case_globals)]
+#![allow(trivial_casts)]
+#![allow(unused_imports)]
+#![allow(unused_results)]
+//! Generated file from `event_map.proto`
+
+/// Generated files are compatible only with the same version
+/// of protobuf runtime.
+// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
+
+#[derive(Clone,PartialEq,Eq,Debug,Hash)]
+pub enum BlockEvent {
+    GetBlockData = 0,
+    ApplyDelta = 1,
+    ExportDocument = 2,
+}
+
+impl ::protobuf::ProtobufEnum for BlockEvent {
+    fn value(&self) -> i32 {
+        *self as i32
+    }
+
+    fn from_i32(value: i32) -> ::std::option::Option<BlockEvent> {
+        match value {
+            0 => ::std::option::Option::Some(BlockEvent::GetBlockData),
+            1 => ::std::option::Option::Some(BlockEvent::ApplyDelta),
+            2 => ::std::option::Option::Some(BlockEvent::ExportDocument),
+            _ => ::std::option::Option::None
+        }
+    }
+
+    fn values() -> &'static [Self] {
+        static values: &'static [BlockEvent] = &[
+            BlockEvent::GetBlockData,
+            BlockEvent::ApplyDelta,
+            BlockEvent::ExportDocument,
+        ];
+        values
+    }
+
+    fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            ::protobuf::reflect::EnumDescriptor::new_pb_name::<BlockEvent>("BlockEvent", file_descriptor_proto())
+        })
+    }
+}
+
+impl ::std::marker::Copy for BlockEvent {
+}
+
+impl ::std::default::Default for BlockEvent {
+    fn default() -> Self {
+        BlockEvent::GetBlockData
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for BlockEvent {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
+    }
+}
+
+static file_descriptor_proto_data: &'static [u8] = b"\
+    \n\x0fevent_map.proto*B\n\nBlockEvent\x12\x10\n\x0cGetBlockData\x10\0\
+    \x12\x0e\n\nApplyDelta\x10\x01\x12\x12\n\x0eExportDocument\x10\x02b\x06p\
+    roto3\
+";
+
+static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
+
+fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
+    ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
+}
+
+pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
+    file_descriptor_proto_lazy.get(|| {
+        parse_descriptor_proto()
+    })
+}

+ 8 - 0
frontend/rust-lib/flowy-block/src/protobuf/model/mod.rs

@@ -0,0 +1,8 @@
+#![cfg_attr(rustfmt, rustfmt::skip)]
+// Auto-generated, do not edit
+
+mod entities;
+pub use entities::*;
+
+mod event_map;
+pub use event_map::*;

+ 0 - 0
shared-lib/flowy-folder-data-model/src/protobuf/proto/share.proto → frontend/rust-lib/flowy-block/src/protobuf/proto/entities.proto


+ 7 - 0
frontend/rust-lib/flowy-block/src/protobuf/proto/event_map.proto

@@ -0,0 +1,7 @@
+syntax = "proto3";
+
+enum BlockEvent {
+    GetBlockData = 0;
+    ApplyDelta = 1;
+    ExportDocument = 2;
+}

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

@@ -161,8 +161,8 @@ impl EditBlockQueue {
                 let _ = self.save_local_delta(delta, md5).await?;
                 let _ = ret.send(Ok(()));
             }
-            EditorCommand::ReadBlockJson { ret } => {
-                let data = self.document.read().await.to_json();
+            EditorCommand::ReadDeltaStr { ret } => {
+                let data = self.document.read().await.delta_str();
                 let _ = ret.send(Ok(data));
             }
             EditorCommand::ReadBlockDelta { ret } => {
@@ -265,7 +265,7 @@ pub(crate) enum EditorCommand {
     Redo {
         ret: Ret<()>,
     },
-    ReadBlockJson {
+    ReadDeltaStr {
         ret: Ret<String>,
     },
     #[allow(dead_code)]
@@ -289,7 +289,7 @@ impl std::fmt::Debug for EditorCommand {
             EditorCommand::CanRedo { .. } => "CanRedo",
             EditorCommand::Undo { .. } => "Undo",
             EditorCommand::Redo { .. } => "Redo",
-            EditorCommand::ReadBlockJson { .. } => "ReadDocumentAsJson",
+            EditorCommand::ReadDeltaStr { .. } => "ReadDeltaStr",
             EditorCommand::ReadBlockDelta { .. } => "ReadDocumentAsDelta",
         };
         f.write_str(s)

+ 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-block/tests/document/edit_script.rs

@@ -27,7 +27,7 @@ impl EditorTest {
         let sdk = FlowySDKTest::default();
         let _ = sdk.init_user().await;
         let test = ViewTest::new(&sdk).await;
-        let editor = sdk.document_manager.open_block(&test.view.id).await.unwrap();
+        let editor = sdk.block_manager.open_block(&test.view.id).await.unwrap();
         Self { sdk, editor }
     }
 
@@ -77,7 +77,7 @@ impl EditorTest {
                 let delta = self.editor.doc_delta().await.unwrap();
                 if expected_delta != delta {
                     eprintln!("✅ expect: {}", expected,);
-                    eprintln!("❌ receive: {}", delta.to_delta_json());
+                    eprintln!("❌ receive: {}", delta.to_delta_str());
                 }
                 assert_eq!(expected_delta, delta);
             }

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

@@ -774,7 +774,7 @@ fn delta_compose() {
         delta = delta.compose(&d).unwrap();
     }
     assert_eq!(
-        delta.to_delta_json(),
+        delta.to_delta_str(),
         r#"[{"insert":"a"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"\n"}]"#
     );
 

+ 14 - 14
frontend/rust-lib/flowy-block/tests/editor/mod.rs

@@ -108,20 +108,20 @@ impl TestBuilder {
             TestOp::Insert(delta_i, s, index) => {
                 let document = &mut self.documents[*delta_i];
                 let delta = document.insert(*index, s).unwrap();
-                tracing::debug!("Insert delta: {}", delta.to_delta_json());
+                tracing::debug!("Insert delta: {}", delta.to_delta_str());
 
                 self.deltas.insert(*delta_i, Some(delta));
             }
             TestOp::Delete(delta_i, iv) => {
                 let document = &mut self.documents[*delta_i];
                 let delta = document.replace(*iv, "").unwrap();
-                tracing::trace!("Delete delta: {}", delta.to_delta_json());
+                tracing::trace!("Delete delta: {}", delta.to_delta_str());
                 self.deltas.insert(*delta_i, Some(delta));
             }
             TestOp::Replace(delta_i, iv, s) => {
                 let document = &mut self.documents[*delta_i];
                 let delta = document.replace(*iv, s).unwrap();
-                tracing::trace!("Replace delta: {}", delta.to_delta_json());
+                tracing::trace!("Replace delta: {}", delta.to_delta_str());
                 self.deltas.insert(*delta_i, Some(delta));
             }
             TestOp::InsertBold(delta_i, s, iv) => {
@@ -133,7 +133,7 @@ impl TestBuilder {
                 let document = &mut self.documents[*delta_i];
                 let attribute = RichTextAttribute::Bold(*enable);
                 let delta = document.format(*iv, attribute).unwrap();
-                tracing::trace!("Bold delta: {}", delta.to_delta_json());
+                tracing::trace!("Bold delta: {}", delta.to_delta_str());
                 self.deltas.insert(*delta_i, Some(delta));
             }
             TestOp::Italic(delta_i, iv, enable) => {
@@ -143,28 +143,28 @@ impl TestBuilder {
                     false => RichTextAttribute::Italic(false),
                 };
                 let delta = document.format(*iv, attribute).unwrap();
-                tracing::trace!("Italic delta: {}", delta.to_delta_json());
+                tracing::trace!("Italic delta: {}", delta.to_delta_str());
                 self.deltas.insert(*delta_i, Some(delta));
             }
             TestOp::Header(delta_i, iv, level) => {
                 let document = &mut self.documents[*delta_i];
                 let attribute = RichTextAttribute::Header(*level);
                 let delta = document.format(*iv, attribute).unwrap();
-                tracing::trace!("Header delta: {}", delta.to_delta_json());
+                tracing::trace!("Header delta: {}", delta.to_delta_str());
                 self.deltas.insert(*delta_i, Some(delta));
             }
             TestOp::Link(delta_i, iv, link) => {
                 let document = &mut self.documents[*delta_i];
                 let attribute = RichTextAttribute::Link(link.to_owned());
                 let delta = document.format(*iv, attribute).unwrap();
-                tracing::trace!("Link delta: {}", delta.to_delta_json());
+                tracing::trace!("Link delta: {}", delta.to_delta_str());
                 self.deltas.insert(*delta_i, Some(delta));
             }
             TestOp::Bullet(delta_i, iv, enable) => {
                 let document = &mut self.documents[*delta_i];
                 let attribute = RichTextAttribute::Bullet(*enable);
                 let delta = document.format(*iv, attribute).unwrap();
-                tracing::debug!("Bullet delta: {}", delta.to_delta_json());
+                tracing::debug!("Bullet delta: {}", delta.to_delta_str());
 
                 self.deltas.insert(*delta_i, Some(delta));
             }
@@ -194,15 +194,15 @@ impl TestBuilder {
                 let delta_a = &self.documents[*delta_a_i].delta();
                 let delta_b = &self.documents[*delta_b_i].delta();
                 tracing::debug!("Invert: ");
-                tracing::debug!("a: {}", delta_a.to_delta_json());
-                tracing::debug!("b: {}", delta_b.to_delta_json());
+                tracing::debug!("a: {}", delta_a.to_delta_str());
+                tracing::debug!("b: {}", delta_b.to_delta_str());
 
                 let (_, b_prime) = delta_a.transform(delta_b).unwrap();
                 let undo = b_prime.invert(delta_a);
 
                 let new_delta = delta_a.compose(&b_prime).unwrap();
-                tracing::debug!("new delta: {}", new_delta.to_delta_json());
-                tracing::debug!("undo delta: {}", undo.to_delta_json());
+                tracing::debug!("new delta: {}", new_delta.to_delta_str());
+                tracing::debug!("undo delta: {}", undo.to_delta_str());
 
                 let new_delta_after_undo = new_delta.compose(&undo).unwrap();
 
@@ -226,7 +226,7 @@ impl TestBuilder {
             }
 
             TestOp::AssertDocJson(delta_i, expected) => {
-                let delta_json = self.documents[*delta_i].to_json();
+                let delta_json = self.documents[*delta_i].delta_str();
                 let expected_delta: RichTextDelta = serde_json::from_str(expected).unwrap();
                 let target_delta: RichTextDelta = serde_json::from_str(&delta_json).unwrap();
 
@@ -238,7 +238,7 @@ impl TestBuilder {
             }
 
             TestOp::AssertPrimeJson(doc_i, expected) => {
-                let prime_json = self.primes[*doc_i].as_ref().unwrap().to_delta_json();
+                let prime_json = self.primes[*doc_i].as_ref().unwrap().to_delta_str();
                 let expected_prime: RichTextDelta = serde_json::from_str(expected).unwrap();
                 let target_prime: RichTextDelta = serde_json::from_str(&prime_json).unwrap();
 

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

@@ -92,7 +92,7 @@ fn delta_deserialize_null_test() {
     attribute.value = RichTextAttributeValue(None);
     let delta2 = DeltaBuilder::new().retain_with_attributes(7, attribute.into()).build();
 
-    assert_eq!(delta2.to_delta_json(), r#"[{"retain":7,"attributes":{"bold":""}}]"#);
+    assert_eq!(delta2.to_delta_str(), r#"[{"retain":7,"attributes":{"bold":""}}]"#);
     assert_eq!(delta1, delta2);
 }
 
@@ -108,10 +108,10 @@ fn document_insert_serde_test() {
     let mut document = ClientDocument::new::<PlainDoc>();
     document.insert(0, "\n").unwrap();
     document.insert(0, "123").unwrap();
-    let json = document.to_json();
+    let json = document.delta_str();
     assert_eq!(r#"[{"insert":"123\n"}]"#, json);
     assert_eq!(
         r#"[{"insert":"123\n"}]"#,
-        ClientDocument::from_json(&json).unwrap().to_json()
+        ClientDocument::from_json(&json).unwrap().delta_str()
     );
 }

+ 2 - 1
frontend/rust-lib/flowy-database/migrations/2021-09-22-074638_flowy-doc-op/down.sql

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

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

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

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

@@ -0,0 +1,5 @@
+-- Your SQL goes here
+CREATE TABLE kv_table (
+   key TEXT NOT NULL PRIMARY KEY,
+   value BLOB NOT NULL DEFAULT (x'')
+);

+ 3 - 5
frontend/rust-lib/flowy-database/src/lib.rs

@@ -6,12 +6,10 @@ pub mod kv;
 
 use lib_sqlite::PoolConfig;
 pub use lib_sqlite::{ConnectionPool, DBConnection, Database};
-
 pub mod schema;
 
 #[macro_use]
 pub mod macros;
-
 #[macro_use]
 extern crate diesel;
 #[macro_use]
@@ -20,11 +18,11 @@ extern crate diesel_derives;
 extern crate diesel_migrations;
 
 pub type Error = diesel::result::Error;
-
 pub mod prelude {
-    pub use diesel::{query_dsl::*, BelongingToDsl, ExpressionMethods, RunQueryDsl};
-
     pub use super::UserDatabaseConnection;
+    pub use crate::*;
+    pub use diesel::SqliteConnection;
+    pub use diesel::{query_dsl::*, BelongingToDsl, ExpressionMethods, RunQueryDsl};
 }
 
 embed_migrations!("../flowy-database/migrations/");

部分文件因为文件数量过多而无法显示