浏览代码

chore: save text cell data & insert row

appflowy 3 年之前
父节点
当前提交
8c148e3b67
共有 44 个文件被更改,包括 759 次插入231 次删除
  1. 16 27
      frontend/app_flowy/lib/startup/home_deps_resolver.dart
  2. 29 1
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/cell_service.dart
  3. 1 5
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/checkbox_cell_bloc.dart
  4. 1 5
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/date_cell_bloc.dart
  5. 1 5
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/number_cell_bloc.dart
  6. 1 5
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart
  7. 4 7
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/text_cell_bloc.dart
  8. 5 0
      frontend/app_flowy/lib/workspace/application/grid/data.dart
  9. 4 3
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  10. 0 0
      frontend/app_flowy/lib/workspace/application/grid/grid_listener.dart
  11. 4 2
      frontend/app_flowy/lib/workspace/application/grid/grid_service.dart
  12. 4 2
      frontend/app_flowy/lib/workspace/application/grid/row_bloc.dart
  13. 11 2
      frontend/app_flowy/lib/workspace/application/grid/row_service.dart
  14. 9 9
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_builder.dart
  15. 5 7
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/checkbox_cell.dart
  16. 5 7
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/date_cell.dart
  17. 13 6
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_row.dart
  18. 4 6
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/number_cell.dart
  19. 8 13
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/selection_cell.dart
  20. 5 8
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/text_cell.dart
  21. 5 5
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/footer/grid_footer.dart
  22. 4 4
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart
  23. 4 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbenum.dart
  24. 3 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbjson.dart
  25. 74 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
  26. 14 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
  27. 31 17
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pb.dart
  28. 5 4
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pbjson.dart
  29. 13 12
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  30. 2 2
      frontend/rust-lib/flowy-grid/src/event_map.rs
  31. 5 5
      frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs
  32. 17 8
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  33. 7 7
      frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs
  34. 1 0
      frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs
  35. 2 1
      frontend/rust-lib/flowy-grid/src/util.rs
  36. 13 8
      frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs
  37. 2 2
      frontend/rust-lib/flowy-grid/tests/grid/script.rs
  38. 92 4
      shared-lib/flowy-collaboration/src/client_grid/block_pad.rs
  39. 9 0
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  40. 5 2
      shared-lib/flowy-grid-data-model/src/entities/meta.rs
  41. 249 5
      shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
  42. 64 21
      shared-lib/flowy-grid-data-model/src/protobuf/model/meta.rs
  43. 4 0
      shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto
  44. 4 3
      shared-lib/flowy-grid-data-model/src/protobuf/proto/meta.proto

+ 16 - 27
frontend/app_flowy/lib/startup/home_deps_resolver.dart

@@ -98,8 +98,7 @@ class HomeDepsResolver {
 
     getIt.registerFactoryParam<RowBloc, GridRowData, void>(
       (data, _) => RowBloc(
-        data: data,
-        service: RowService(),
+        service: RowService(data),
       ),
     );
 
@@ -110,43 +109,33 @@ class HomeDepsResolver {
       ),
     );
 
-    getIt.registerFactoryParam<TextCellBloc, Field, Cell?>(
-      (field, cell) => TextCellBloc(
-        field: field,
-        cell: cell,
-        service: CellService(),
+    getIt.registerFactoryParam<TextCellBloc, CellContext, void>(
+      (context, _) => TextCellBloc(
+        service: CellService(context),
       ),
     );
 
-    getIt.registerFactoryParam<SelectionCellBloc, Field, Cell?>(
-      (field, cell) => SelectionCellBloc(
-        field: field,
-        cell: cell,
-        service: CellService(),
+    getIt.registerFactoryParam<SelectionCellBloc, CellContext, void>(
+      (context, _) => SelectionCellBloc(
+        service: CellService(context),
       ),
     );
 
-    getIt.registerFactoryParam<NumberCellBloc, Field, Cell?>(
-      (field, cell) => NumberCellBloc(
-        field: field,
-        cell: cell,
-        service: CellService(),
+    getIt.registerFactoryParam<NumberCellBloc, CellContext, void>(
+      (context, _) => NumberCellBloc(
+        service: CellService(context),
       ),
     );
 
-    getIt.registerFactoryParam<DateCellBloc, Field, Cell?>(
-      (field, cell) => DateCellBloc(
-        field: field,
-        cell: cell,
-        service: CellService(),
+    getIt.registerFactoryParam<DateCellBloc, CellContext, void>(
+      (context, _) => DateCellBloc(
+        service: CellService(context),
       ),
     );
 
-    getIt.registerFactoryParam<CheckboxCellBloc, Field, Cell?>(
-      (field, cell) => CheckboxCellBloc(
-        field: field,
-        cell: cell,
-        service: CellService(),
+    getIt.registerFactoryParam<CheckboxCellBloc, CellContext, void>(
+      (context, _) => CheckboxCellBloc(
+        service: CellService(context),
       ),
     );
 

+ 29 - 1
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/cell_service.dart

@@ -1 +1,29 @@
-class CellService {}
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart';
+
+class CellService {
+  final CellContext context;
+
+  CellService(this.context);
+
+  Future<Either<void, FlowyError>> updateCell({required String data}) {
+    final payload = CellMetaChangeset.create()
+      ..gridId = context.gridId
+      ..fieldId = context.field.id
+      ..rowId = context.rowId
+      ..data = data;
+    return GridEventUpdateCell(payload).send();
+  }
+}
+
+class CellContext {
+  final String gridId;
+  final String rowId;
+  final Field field;
+  final Cell? cell;
+
+  CellContext({required this.rowId, required this.gridId, required this.field, required this.cell});
+}

+ 1 - 5
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/checkbox_cell_bloc.dart

@@ -7,15 +7,11 @@ import 'cell_service.dart';
 part 'checkbox_cell_bloc.freezed.dart';
 
 class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
-  final Field field;
-  final Cell? cell;
   final CellService service;
 
   CheckboxCellBloc({
-    required this.field,
-    required this.cell,
     required this.service,
-  }) : super(CheckboxCellState.initial(cell)) {
+  }) : super(CheckboxCellState.initial(service.context.cell)) {
     on<CheckboxCellEvent>(
       (event, emit) async {
         await event.map(

+ 1 - 5
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/date_cell_bloc.dart

@@ -7,15 +7,11 @@ import 'cell_service.dart';
 part 'date_cell_bloc.freezed.dart';
 
 class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
-  final Field field;
-  final Cell? cell;
   final CellService service;
 
   DateCellBloc({
-    required this.field,
-    required this.cell,
     required this.service,
-  }) : super(DateCellState.initial(cell)) {
+  }) : super(DateCellState.initial(service.context.cell)) {
     on<DateCellEvent>(
       (event, emit) async {
         await event.map(

+ 1 - 5
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/number_cell_bloc.dart

@@ -7,15 +7,11 @@ import 'cell_service.dart';
 part 'number_cell_bloc.freezed.dart';
 
 class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
-  final Field field;
-  final Cell? cell;
   final CellService service;
 
   NumberCellBloc({
-    required this.field,
-    required this.cell,
     required this.service,
-  }) : super(NumberCellState.initial(cell)) {
+  }) : super(NumberCellState.initial(service.context.cell)) {
     on<NumberCellEvent>(
       (event, emit) async {
         await event.map(

+ 1 - 5
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart

@@ -7,15 +7,11 @@ import 'cell_service.dart';
 part 'selection_cell_bloc.freezed.dart';
 
 class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
-  final Field field;
-  final Cell? cell;
   final CellService service;
 
   SelectionCellBloc({
-    required this.field,
-    required this.cell,
     required this.service,
-  }) : super(SelectionCellState.initial(cell)) {
+  }) : super(SelectionCellState.initial(service.context.cell)) {
     on<SelectionCellEvent>(
       (event, emit) async {
         await event.map(

+ 4 - 7
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/text_cell_bloc.dart

@@ -1,4 +1,3 @@
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
@@ -7,20 +6,18 @@ import 'cell_service.dart';
 part 'text_cell_bloc.freezed.dart';
 
 class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
-  final Field field;
-  final Cell? cell;
   final CellService service;
 
   TextCellBloc({
-    required this.field,
-    required this.cell,
     required this.service,
-  }) : super(TextCellState.initial(cell?.content ?? "")) {
+  }) : super(TextCellState.initial(service.context.cell?.content ?? "")) {
     on<TextCellEvent>(
       (event, emit) async {
         await event.map(
           initial: (_InitialCell value) async {},
-          updateText: (_UpdateText value) {},
+          updateText: (_UpdateText value) {
+            service.updateCell(data: value.text);
+          },
         );
       },
     );

+ 5 - 0
frontend/app_flowy/lib/workspace/application/grid/data.dart

@@ -2,10 +2,12 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:equatable/equatable.dart';
 
 class GridInfo {
+  final String gridId;
   List<Row> rows;
   List<Field> fields;
 
   GridInfo({
+    required this.gridId,
     required this.rows,
     required this.fields,
   });
@@ -13,6 +15,7 @@ class GridInfo {
   GridRowData rowAtIndex(int index) {
     final row = rows[index];
     return GridRowData(
+      gridId: gridId,
       row: row,
       fields: fields,
       cellMap: row.cellByFieldId,
@@ -25,10 +28,12 @@ class GridInfo {
 }
 
 class GridRowData extends Equatable {
+  final String gridId;
   final Row row;
   final List<Field> fields;
   final Map<String, Cell> cellMap;
   const GridRowData({
+    required this.gridId,
     required this.row,
     required this.fields,
     required this.cellMap,

+ 4 - 3
frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart

@@ -70,11 +70,12 @@ class GridBloc extends Bloc<GridEvent, GridState> {
   }
 
   Future<void> _loadGridInfo(Emitter<GridState> emit) async {
-    if (_grid != null && _fields != null) {
-      final result = await service.getRows(gridId: _grid!.id, rowOrders: _grid!.rowOrders);
+    final grid = _grid;
+    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!);
+        final gridInfo = GridInfo(gridId: grid.id, rows: rows, fields: _fields!);
         emit(
           state.copyWith(loadingState: GridLoadingState.finish(left(unit)), gridInfo: some(left(gridInfo))),
         );

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


+ 4 - 2
frontend/app_flowy/lib/workspace/application/grid/grid_service.dart

@@ -12,8 +12,10 @@ class GridService {
     return GridEventGetGridData(payload).send();
   }
 
-  Future<Either<void, FlowyError>> createRow({required String gridId}) {
-    return GridEventCreateRow(GridId(value: gridId)).send();
+  Future<Either<Row, FlowyError>> createRow({required String gridId, Option<String>? upperRowId}) {
+    CreateRowPayload payload = CreateRowPayload.create()..gridId = gridId;
+    upperRowId?.fold(() => null, (id) => payload.upperRowId = id);
+    return GridEventCreateRow(payload).send();
   }
 
   Future<Either<RepeatedRow, FlowyError>> getRows({required String gridId, required List<RowOrder> rowOrders}) {

+ 4 - 2
frontend/app_flowy/lib/workspace/application/grid/row_bloc.dart

@@ -9,12 +9,14 @@ part 'row_bloc.freezed.dart';
 class RowBloc extends Bloc<RowEvent, RowState> {
   final RowService service;
 
-  RowBloc({required GridRowData data, required this.service}) : super(RowState.initial(data)) {
+  RowBloc({required this.service}) : super(RowState.initial(service.rowData)) {
     on<RowEvent>(
       (event, emit) async {
         await event.map(
           initial: (_InitialRow value) async {},
-          createRow: (_CreateRow value) {},
+          createRow: (_CreateRow value) {
+            service.createRow();
+          },
           activeRow: (_ActiveRow value) {
             emit(state.copyWith(active: true));
           },

+ 11 - 2
frontend/app_flowy/lib/workspace/application/grid/row_service.dart

@@ -1,10 +1,19 @@
+import 'package:app_flowy/workspace/application/grid/data.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 
 class RowService {
-  Future<Either<void, FlowyError>> createRow({required String gridId}) {
-    return GridEventCreateRow(GridId(value: gridId)).send();
+  final GridRowData rowData;
+
+  RowService(this.rowData);
+
+  Future<Either<Row, FlowyError>> createRow() {
+    CreateRowPayload payload = CreateRowPayload.create()
+      ..gridId = rowData.gridId
+      ..upperRowId = rowData.row.id;
+
+    return GridEventCreateRow(payload).send();
   }
 }

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

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'package:app_flowy/workspace/application/grid/cell_bloc/cell_service.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart';
 import 'package:flutter/widgets.dart';
 import 'checkbox_cell.dart';
@@ -7,20 +7,20 @@ import 'number_cell.dart';
 import 'selection_cell.dart';
 import 'text_cell.dart';
 
-Widget buildGridCell(Field field, Cell? cell) {
-  switch (field.fieldType) {
+Widget buildGridCell(CellContext cellContext) {
+  switch (cellContext.field.fieldType) {
     case FieldType.Checkbox:
-      return CheckboxCell(field: field, cell: cell);
+      return CheckboxCell(cellContext: cellContext);
     case FieldType.DateTime:
-      return DateCell(field: field, cell: cell);
+      return DateCell(cellContext: cellContext);
     case FieldType.MultiSelect:
-      return MultiSelectCell(field: field, cell: cell);
+      return MultiSelectCell(cellContext: cellContext);
     case FieldType.Number:
-      return NumberCell(field: field, cell: cell);
+      return NumberCell(cellContext: cellContext);
     case FieldType.RichText:
-      return GridTextCell(field: field, cell: cell);
+      return GridTextCell(cellContext: cellContext);
     case FieldType.SingleSelect:
-      return SingleSelectCell(field: field, cell: cell);
+      return SingleSelectCell(cellContext: cellContext);
     default:
       return const BlankCell();
   }

+ 5 - 7
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/checkbox_cell.dart

@@ -1,16 +1,14 @@
 import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/grid/cell_bloc/cell_service.dart';
 import 'package:app_flowy/workspace/application/grid/cell_bloc/checkbox_cell_bloc.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 class CheckboxCell extends StatefulWidget {
-  final Field field;
-  final Cell? cell;
+  final CellContext cellContext;
 
   const CheckboxCell({
-    required this.field,
-    required this.cell,
+    required this.cellContext,
     Key? key,
   }) : super(key: key);
 
@@ -23,7 +21,7 @@ class _CheckboxCellState extends State<CheckboxCell> {
 
   @override
   void initState() {
-    _cellBloc = getIt<CheckboxCellBloc>(param1: widget.field, param2: widget.cell);
+    _cellBloc = getIt<CheckboxCellBloc>(param1: widget.cellContext);
     super.initState();
   }
 
@@ -41,7 +39,7 @@ class _CheckboxCellState extends State<CheckboxCell> {
 
   @override
   Future<void> dispose() async {
-    await _cellBloc.close();
+    _cellBloc.close();
     super.dispose();
   }
 }

+ 5 - 7
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/date_cell.dart

@@ -1,16 +1,14 @@
 import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/grid/cell_bloc/cell_service.dart';
 import 'package:app_flowy/workspace/application/grid/cell_bloc/date_cell_bloc.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 class DateCell extends StatefulWidget {
-  final Field field;
-  final Cell? cell;
+  final CellContext cellContext;
 
   const DateCell({
-    required this.field,
-    required this.cell,
+    required this.cellContext,
     Key? key,
   }) : super(key: key);
 
@@ -23,7 +21,7 @@ class _DateCellState extends State<DateCell> {
 
   @override
   void initState() {
-    _cellBloc = getIt<DateCellBloc>(param1: widget.field, param2: widget.cell);
+    _cellBloc = getIt<DateCellBloc>(param1: widget.cellContext);
     super.initState();
   }
 
@@ -41,7 +39,7 @@ class _DateCellState extends State<DateCell> {
 
   @override
   Future<void> dispose() async {
-    await _cellBloc.close();
+    _cellBloc.close();
     super.dispose();
   }
 }

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

@@ -54,7 +54,7 @@ class _GridRowWidgetState extends State<GridRowWidget> {
 
   @override
   Future<void> dispose() async {
-    await _rowBloc.close();
+    _rowBloc.close();
     super.dispose();
   }
 
@@ -66,10 +66,17 @@ class _GridRowWidgetState extends State<GridRowWidget> {
           key: ValueKey(state.data.row.id),
           children: state.data.fields.map(
             (field) {
-              final cellData = state.data.cellMap[field.id];
+              final cell = state.data.cellMap[field.id];
               return CellContainer(
                 width: field.width.toDouble(),
-                child: buildGridCell(field, cellData),
+                child: buildGridCell(
+                  CellContext(
+                    gridId: state.data.gridId,
+                    rowId: state.data.row.id,
+                    field: field,
+                    cell: cell,
+                  ),
+                ),
               );
             },
           ).toList(),
@@ -93,7 +100,7 @@ class LeadingRow extends StatelessWidget {
               ? Row(
                   mainAxisAlignment: MainAxisAlignment.center,
                   children: const [
-                    CreateRowButton(),
+                    AppendRowButton(),
                   ],
                 )
               : null,
@@ -125,8 +132,8 @@ class TrailingRow extends StatelessWidget {
   }
 }
 
-class CreateRowButton extends StatelessWidget {
-  const CreateRowButton({Key? key}) : super(key: key);
+class AppendRowButton extends StatelessWidget {
+  const AppendRowButton({Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {

+ 4 - 6
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/number_cell.dart

@@ -1,16 +1,14 @@
 import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/grid/cell_bloc/cell_service.dart';
 import 'package:app_flowy/workspace/application/grid/cell_bloc/number_cell_bloc.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 class NumberCell extends StatefulWidget {
-  final Field field;
-  final Cell? cell;
+  final CellContext cellContext;
 
   const NumberCell({
-    required this.field,
-    required this.cell,
+    required this.cellContext,
     Key? key,
   }) : super(key: key);
 
@@ -23,7 +21,7 @@ class _NumberCellState extends State<NumberCell> {
 
   @override
   void initState() {
-    _cellBloc = getIt<NumberCellBloc>(param1: widget.field, param2: widget.cell);
+    _cellBloc = getIt<NumberCellBloc>(param1: widget.cellContext);
     super.initState();
   }
 

+ 8 - 13
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/selection_cell.dart

@@ -1,15 +1,12 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter/material.dart';
 
 class SingleSelectCell extends StatefulWidget {
-  final Field field;
-  final Cell? cell;
+  final CellContext cellContext;
 
   const SingleSelectCell({
-    required this.field,
-    required this.cell,
+    required this.cellContext,
     Key? key,
   }) : super(key: key);
 
@@ -22,7 +19,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
 
   @override
   void initState() {
-    _cellBloc = getIt<SelectionCellBloc>(param1: widget.field, param2: widget.cell);
+    _cellBloc = getIt<SelectionCellBloc>(param1: widget.cellContext);
     super.initState();
   }
 
@@ -33,19 +30,17 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
 
   @override
   Future<void> dispose() async {
-    await _cellBloc.close();
+    _cellBloc.close();
     super.dispose();
   }
 }
 
 //----------------------------------------------------------------
 class MultiSelectCell extends StatefulWidget {
-  final Field field;
-  final Cell? cell;
+  final CellContext cellContext;
 
   const MultiSelectCell({
-    required this.field,
-    required this.cell,
+    required this.cellContext,
     Key? key,
   }) : super(key: key);
 
@@ -58,7 +53,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
 
   @override
   void initState() {
-    _cellBloc = getIt<SelectionCellBloc>(param1: widget.field, param2: widget.cell);
+    _cellBloc = getIt<SelectionCellBloc>(param1: widget.cellContext);
     super.initState();
   }
 
@@ -69,7 +64,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
 
   @override
   Future<void> dispose() async {
-    await _cellBloc.close();
+    _cellBloc.close();
     super.dispose();
   }
 }

+ 5 - 8
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/text_cell.dart

@@ -1,18 +1,15 @@
 import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/grid/cell_bloc/cell_service.dart';
 import 'package:app_flowy/workspace/application/grid/cell_bloc/text_cell_bloc.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 /// The interface of base cell.
 
 class GridTextCell extends StatefulWidget {
-  final Field field;
-  final Cell? cell;
-
+  final CellContext cellContext;
   const GridTextCell({
-    required this.field,
-    required this.cell,
+    required this.cellContext,
     Key? key,
   }) : super(key: key);
 
@@ -27,7 +24,7 @@ class _GridTextCellState extends State<GridTextCell> {
 
   @override
   void initState() {
-    _cellBloc = getIt<TextCellBloc>(param1: widget.field, param2: widget.cell);
+    _cellBloc = getIt<TextCellBloc>(param1: widget.cellContext);
     _controller = TextEditingController(text: _cellBloc.state.content);
     _focusNode.addListener(_focusChanged);
     super.initState();
@@ -58,7 +55,7 @@ class _GridTextCellState extends State<GridTextCell> {
 
   @override
   Future<void> dispose() async {
-    await _cellBloc.close();
+    _cellBloc.close();
     _focusNode.removeListener(_focusChanged);
     _focusNode.dispose();
     super.dispose();

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

@@ -1,4 +1,4 @@
-import 'package:app_flowy/workspace/application/grid/row_bloc.dart';
+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_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
@@ -20,7 +20,7 @@ class GridFooter extends StatelessWidget {
           child: Row(
             children: [
               SizedBox(width: GridSize.leadingHeaderPadding),
-              const SizedBox(width: 120, child: AddRowButton()),
+              const SizedBox(width: 120, child: _AddRowButton()),
             ],
           ),
         ),
@@ -29,8 +29,8 @@ class GridFooter extends StatelessWidget {
   }
 }
 
-class AddRowButton extends StatelessWidget {
-  const AddRowButton({Key? key}) : super(key: key);
+class _AddRowButton extends StatelessWidget {
+  const _AddRowButton({Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -38,7 +38,7 @@ class AddRowButton extends StatelessWidget {
     return FlowyButton(
       text: const FlowyText.medium('New row', fontSize: 12),
       hoverColor: theme.hover,
-      onTap: () => context.read<RowBloc>().add(const RowEvent.createRow()),
+      onTap: () => context.read<GridBloc>().add(const GridEvent.createRow()),
       icon: svg("home/add"),
     );
   }

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

@@ -53,24 +53,24 @@ class GridEventGetFields {
 }
 
 class GridEventCreateRow {
-     GridId request;
+     CreateRowPayload request;
      GridEventCreateRow(this.request);
 
-    Future<Either<Unit, FlowyError>> send() {
+    Future<Either<Row, 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),
+           (okBytes) => left(Row.fromBuffer(okBytes)),
            (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
     }
 }
 
 class GridEventUpdateCell {
-     Cell request;
+     CellMetaChangeset request;
      GridEventUpdateCell(this.request);
 
     Future<Either<Unit, FlowyError>> send() {

+ 4 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbenum.dart

@@ -40,6 +40,8 @@ class ErrorCode extends $pb.ProtobufEnum {
   static const ErrorCode UserNameIsEmpty = ErrorCode._(310, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNameIsEmpty');
   static const ErrorCode UserIdInvalid = ErrorCode._(311, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserIdInvalid');
   static const ErrorCode UserNotExist = ErrorCode._(312, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNotExist');
+  static const ErrorCode TextTooLong = ErrorCode._(400, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TextTooLong');
+  static const ErrorCode InvalidData = ErrorCode._(401, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InvalidData');
 
   static const $core.List<ErrorCode> values = <ErrorCode> [
     Internal,
@@ -72,6 +74,8 @@ class ErrorCode extends $pb.ProtobufEnum {
     UserNameIsEmpty,
     UserIdInvalid,
     UserNotExist,
+    TextTooLong,
+    InvalidData,
   ];
 
   static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);

+ 3 - 1
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbjson.dart

@@ -42,8 +42,10 @@ const ErrorCode$json = const {
     const {'1': 'UserNameIsEmpty', '2': 310},
     const {'1': 'UserIdInvalid', '2': 311},
     const {'1': 'UserNotExist', '2': 312},
+    const {'1': 'TextTooLong', '2': 400},
+    const {'1': 'InvalidData', '2': 401},
   ],
 };
 
 /// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSDAoISW50ZXJuYWwQABIUChBVc2VyVW5hdXRob3JpemVkEAISEgoOUmVjb3JkTm90Rm91bmQQAxIYChRXb3Jrc3BhY2VOYW1lSW52YWxpZBBkEhYKEldvcmtzcGFjZUlkSW52YWxpZBBlEhgKFEFwcENvbG9yU3R5bGVJbnZhbGlkEGYSGAoUV29ya3NwYWNlRGVzY1Rvb0xvbmcQZxIYChRXb3Jrc3BhY2VOYW1lVG9vTG9uZxBoEhAKDEFwcElkSW52YWxpZBBuEhIKDkFwcE5hbWVJbnZhbGlkEG8SEwoPVmlld05hbWVJbnZhbGlkEHgSGAoUVmlld1RodW1ibmFpbEludmFsaWQQeRIRCg1WaWV3SWRJbnZhbGlkEHoSEwoPVmlld0Rlc2NUb29Mb25nEHsSEwoPVmlld0RhdGFJbnZhbGlkEHwSEwoPVmlld05hbWVUb29Mb25nEH0SEQoMQ29ubmVjdEVycm9yEMgBEhEKDEVtYWlsSXNFbXB0eRCsAhIXChJFbWFpbEZvcm1hdEludmFsaWQQrQISFwoSRW1haWxBbHJlYWR5RXhpc3RzEK4CEhQKD1Bhc3N3b3JkSXNFbXB0eRCvAhIUCg9QYXNzd29yZFRvb0xvbmcQsAISJQogUGFzc3dvcmRDb250YWluc0ZvcmJpZENoYXJhY3RlcnMQsQISGgoVUGFzc3dvcmRGb3JtYXRJbnZhbGlkELICEhUKEFBhc3N3b3JkTm90TWF0Y2gQswISFAoPVXNlck5hbWVUb29Mb25nELQCEicKIlVzZXJOYW1lQ29udGFpbkZvcmJpZGRlbkNoYXJhY3RlcnMQtQISFAoPVXNlck5hbWVJc0VtcHR5ELYCEhIKDVVzZXJJZEludmFsaWQQtwISEQoMVXNlck5vdEV4aXN0ELgC');
+final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSDAoISW50ZXJuYWwQABIUChBVc2VyVW5hdXRob3JpemVkEAISEgoOUmVjb3JkTm90Rm91bmQQAxIYChRXb3Jrc3BhY2VOYW1lSW52YWxpZBBkEhYKEldvcmtzcGFjZUlkSW52YWxpZBBlEhgKFEFwcENvbG9yU3R5bGVJbnZhbGlkEGYSGAoUV29ya3NwYWNlRGVzY1Rvb0xvbmcQZxIYChRXb3Jrc3BhY2VOYW1lVG9vTG9uZxBoEhAKDEFwcElkSW52YWxpZBBuEhIKDkFwcE5hbWVJbnZhbGlkEG8SEwoPVmlld05hbWVJbnZhbGlkEHgSGAoUVmlld1RodW1ibmFpbEludmFsaWQQeRIRCg1WaWV3SWRJbnZhbGlkEHoSEwoPVmlld0Rlc2NUb29Mb25nEHsSEwoPVmlld0RhdGFJbnZhbGlkEHwSEwoPVmlld05hbWVUb29Mb25nEH0SEQoMQ29ubmVjdEVycm9yEMgBEhEKDEVtYWlsSXNFbXB0eRCsAhIXChJFbWFpbEZvcm1hdEludmFsaWQQrQISFwoSRW1haWxBbHJlYWR5RXhpc3RzEK4CEhQKD1Bhc3N3b3JkSXNFbXB0eRCvAhIUCg9QYXNzd29yZFRvb0xvbmcQsAISJQogUGFzc3dvcmRDb250YWluc0ZvcmJpZENoYXJhY3RlcnMQsQISGgoVUGFzc3dvcmRGb3JtYXRJbnZhbGlkELICEhUKEFBhc3N3b3JkTm90TWF0Y2gQswISFAoPVXNlck5hbWVUb29Mb25nELQCEicKIlVzZXJOYW1lQ29udGFpbkZvcmJpZGRlbkNoYXJhY3RlcnMQtQISFAoPVXNlck5hbWVJc0VtcHR5ELYCEhIKDVVzZXJJZEludmFsaWQQtwISEQoMVXNlck5vdEV4aXN0ELgCEhAKC1RleHRUb29Mb25nEJADEhAKC0ludmFsaWREYXRhEJED');

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

@@ -701,6 +701,80 @@ class GridId extends $pb.GeneratedMessage {
   void clearValue() => clearField(1);
 }
 
+enum CreateRowPayload_OneOfUpperRowId {
+  upperRowId, 
+  notSet
+}
+
+class CreateRowPayload extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, CreateRowPayload_OneOfUpperRowId> _CreateRowPayload_OneOfUpperRowIdByTag = {
+    2 : CreateRowPayload_OneOfUpperRowId.upperRowId,
+    0 : CreateRowPayload_OneOfUpperRowId.notSet
+  };
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CreateRowPayload', createEmptyInstance: create)
+    ..oo(0, [2])
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'upperRowId')
+    ..hasRequiredFields = false
+  ;
+
+  CreateRowPayload._() : super();
+  factory CreateRowPayload({
+    $core.String? gridId,
+    $core.String? upperRowId,
+  }) {
+    final _result = create();
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
+    if (upperRowId != null) {
+      _result.upperRowId = upperRowId;
+    }
+    return _result;
+  }
+  factory CreateRowPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory CreateRowPayload.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')
+  CreateRowPayload clone() => CreateRowPayload()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  CreateRowPayload copyWith(void Function(CreateRowPayload) updates) => super.copyWith((message) => updates(message as CreateRowPayload)) as CreateRowPayload; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static CreateRowPayload create() => CreateRowPayload._();
+  CreateRowPayload createEmptyInstance() => create();
+  static $pb.PbList<CreateRowPayload> createRepeated() => $pb.PbList<CreateRowPayload>();
+  @$core.pragma('dart2js:noInline')
+  static CreateRowPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreateRowPayload>(create);
+  static CreateRowPayload? _defaultInstance;
+
+  CreateRowPayload_OneOfUpperRowId whichOneOfUpperRowId() => _CreateRowPayload_OneOfUpperRowIdByTag[$_whichOneof(0)]!;
+  void clearOneOfUpperRowId() => clearField($_whichOneof(0));
+
+  @$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 upperRowId => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set upperRowId($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasUpperRowId() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearUpperRowId() => clearField(2);
+}
+
 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')

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

@@ -151,6 +151,20 @@ const GridId$json = const {
 
 /// Descriptor for `GridId`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List gridIdDescriptor = $convert.base64Decode('CgZHcmlkSWQSFAoFdmFsdWUYASABKAlSBXZhbHVl');
+@$core.Deprecated('Use createRowPayloadDescriptor instead')
+const CreateRowPayload$json = const {
+  '1': 'CreateRowPayload',
+  '2': const [
+    const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
+    const {'1': 'upper_row_id', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'upperRowId'},
+  ],
+  '8': const [
+    const {'1': 'one_of_upper_row_id'},
+  ],
+};
+
+/// Descriptor for `CreateRowPayload`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List createRowPayloadDescriptor = $convert.base64Decode('ChBDcmVhdGVSb3dQYXlsb2FkEhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIiCgx1cHBlcl9yb3dfaWQYAiABKAlIAFIKdXBwZXJSb3dJZEIVChNvbmVfb2ZfdXBwZXJfcm93X2lk');
 @$core.Deprecated('Use queryFieldPayloadDescriptor instead')
 const QueryFieldPayload$json = const {
   '1': 'QueryFieldPayload',

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

@@ -922,24 +922,29 @@ enum CellMetaChangeset_OneOfData {
 
 class CellMetaChangeset extends $pb.GeneratedMessage {
   static const $core.Map<$core.int, CellMetaChangeset_OneOfData> _CellMetaChangeset_OneOfDataByTag = {
-    3 : CellMetaChangeset_OneOfData.data,
+    4 : CellMetaChangeset_OneOfData.data,
     0 : CellMetaChangeset_OneOfData.notSet
   };
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CellMetaChangeset', createEmptyInstance: create)
-    ..oo(0, [3])
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowId')
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
-    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
+    ..oo(0, [4])
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
+    ..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
   ;
 
   CellMetaChangeset._() : super();
   factory CellMetaChangeset({
+    $core.String? gridId,
     $core.String? rowId,
     $core.String? fieldId,
     $core.String? data,
   }) {
     final _result = create();
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
     if (rowId != null) {
       _result.rowId = rowId;
     }
@@ -976,31 +981,40 @@ class CellMetaChangeset extends $pb.GeneratedMessage {
   void clearOneOfData() => clearField($_whichOneof(0));
 
   @$pb.TagNumber(1)
-  $core.String get rowId => $_getSZ(0);
+  $core.String get gridId => $_getSZ(0);
   @$pb.TagNumber(1)
-  set rowId($core.String v) { $_setString(0, v); }
+  set gridId($core.String v) { $_setString(0, v); }
   @$pb.TagNumber(1)
-  $core.bool hasRowId() => $_has(0);
+  $core.bool hasGridId() => $_has(0);
   @$pb.TagNumber(1)
-  void clearRowId() => clearField(1);
+  void clearGridId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.String get fieldId => $_getSZ(1);
+  $core.String get rowId => $_getSZ(1);
   @$pb.TagNumber(2)
-  set fieldId($core.String v) { $_setString(1, v); }
+  set rowId($core.String v) { $_setString(1, v); }
   @$pb.TagNumber(2)
-  $core.bool hasFieldId() => $_has(1);
+  $core.bool hasRowId() => $_has(1);
   @$pb.TagNumber(2)
-  void clearFieldId() => clearField(2);
+  void clearRowId() => clearField(2);
 
   @$pb.TagNumber(3)
-  $core.String get data => $_getSZ(2);
+  $core.String get fieldId => $_getSZ(2);
   @$pb.TagNumber(3)
-  set data($core.String v) { $_setString(2, v); }
+  set fieldId($core.String v) { $_setString(2, v); }
   @$pb.TagNumber(3)
-  $core.bool hasData() => $_has(2);
+  $core.bool hasFieldId() => $_has(2);
   @$pb.TagNumber(3)
-  void clearData() => clearField(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 BuildGridContext extends $pb.GeneratedMessage {

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

@@ -180,9 +180,10 @@ final $typed_data.Uint8List cellMetaDescriptor = $convert.base64Decode('CghDZWxs
 const CellMetaChangeset$json = const {
   '1': 'CellMetaChangeset',
   '2': const [
-    const {'1': 'row_id', '3': 1, '4': 1, '5': 9, '10': 'rowId'},
-    const {'1': 'field_id', '3': 2, '4': 1, '5': 9, '10': 'fieldId'},
-    const {'1': 'data', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'data'},
+    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': 'field_id', '3': 3, '4': 1, '5': 9, '10': 'fieldId'},
+    const {'1': 'data', '3': 4, '4': 1, '5': 9, '9': 0, '10': 'data'},
   ],
   '8': const [
     const {'1': 'one_of_data'},
@@ -190,7 +191,7 @@ const CellMetaChangeset$json = const {
 };
 
 /// Descriptor for `CellMetaChangeset`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List cellMetaChangesetDescriptor = $convert.base64Decode('ChFDZWxsTWV0YUNoYW5nZXNldBIVCgZyb3dfaWQYASABKAlSBXJvd0lkEhkKCGZpZWxkX2lkGAIgASgJUgdmaWVsZElkEhQKBGRhdGEYAyABKAlIAFIEZGF0YUINCgtvbmVfb2ZfZGF0YQ==');
+final $typed_data.Uint8List cellMetaChangesetDescriptor = $convert.base64Decode('ChFDZWxsTWV0YUNoYW5nZXNldBIXCgdncmlkX2lkGAEgASgJUgZncmlkSWQSFQoGcm93X2lkGAIgASgJUgVyb3dJZBIZCghmaWVsZF9pZBgDIAEoCVIHZmllbGRJZBIUCgRkYXRhGAQgASgJSABSBGRhdGFCDQoLb25lX29mX2RhdGE=');
 @$core.Deprecated('Use buildGridContextDescriptor instead')
 const BuildGridContext$json = const {
   '1': 'BuildGridContext',

+ 13 - 12
frontend/rust-lib/flowy-grid/src/event_handler.rs

@@ -1,7 +1,8 @@
 use crate::manager::GridManager;
 use flowy_error::FlowyError;
 use flowy_grid_data_model::entities::{
-    Cell, Field, Grid, GridId, QueryFieldPayload, QueryRowPayload, RepeatedField, RepeatedRow,
+    CellMetaChangeset, CreateRowPayload, Field, Grid, GridId, QueryFieldPayload, QueryRowPayload, RepeatedField,
+    RepeatedRow, Row,
 };
 use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
 use std::sync::Arc;
@@ -42,22 +43,22 @@ pub(crate) async fn get_fields_handler(
 
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
 pub(crate) async fn create_row_handler(
-    data: Data<GridId>,
+    data: Data<CreateRowPayload>,
     manager: AppData<Arc<GridManager>>,
-) -> Result<(), FlowyError> {
-    let id: GridId = data.into_inner();
-    let editor = manager.get_grid_editor(id.as_ref())?;
-    let _ = editor.create_row().await?;
-    Ok(())
+) -> DataResult<Row, FlowyError> {
+    let payload: CreateRowPayload = data.into_inner();
+    let editor = manager.get_grid_editor(payload.grid_id.as_ref())?;
+    let row = editor.create_row(payload.upper_row_id).await?;
+    data_result(row)
 }
 
 #[tracing::instrument(level = "debug", skip_all, err)]
 pub(crate) async fn update_cell_handler(
-    data: Data<Cell>,
-    _manager: AppData<Arc<GridManager>>,
+    data: Data<CellMetaChangeset>,
+    manager: AppData<Arc<GridManager>>,
 ) -> Result<(), FlowyError> {
-    let _cell: Cell = data.into_inner();
-    // let editor = manager.get_grid_editor(id.as_ref())?;
-    // let _ = editor.create_empty_row().await?;
+    let changeset: CellMetaChangeset = data.into_inner();
+    let editor = manager.get_grid_editor(&changeset.grid_id)?;
+    let _ = editor.update_cell(changeset).await?;
     Ok(())
 }

+ 2 - 2
frontend/rust-lib/flowy-grid/src/event_map.rs

@@ -29,9 +29,9 @@ pub enum GridEvent {
     #[event(input = "QueryFieldPayload", output = "RepeatedField")]
     GetFields = 2,
 
-    #[event(input = "GridId")]
+    #[event(input = "CreateRowPayload", output = "Row")]
     CreateRow = 3,
 
-    #[event(input = "Cell")]
+    #[event(input = "CellMetaChangeset")]
     UpdateCell = 4,
 }

+ 5 - 5
frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs

@@ -56,10 +56,10 @@ impl GridBlockMetaEditorManager {
         }
     }
 
-    pub(crate) async fn create_row(&self, row: RowMeta) -> FlowyResult<i32> {
+    pub(crate) async fn create_row(&self, row: RowMeta, upper_row_id: Option<String>) -> FlowyResult<i32> {
         self.block_id_by_row_id.insert(row.id.clone(), row.block_id.clone());
         let editor = self.get_editor(&row.block_id).await?;
-        editor.create_row(row).await
+        editor.create_row(row, upper_row_id).await
     }
 
     pub(crate) async fn insert_row(
@@ -72,7 +72,7 @@ impl GridBlockMetaEditorManager {
             let mut row_count = 0;
             for row in rows {
                 self.block_id_by_row_id.insert(row.id.clone(), row.block_id.clone());
-                row_count = editor.create_row(row).await?;
+                row_count = editor.create_row(row, None).await?;
             }
             changesets.push(GridBlockChangeset::from_row_count(&block_id, row_count));
         }
@@ -215,11 +215,11 @@ impl ClientGridBlockMetaEditor {
         })
     }
 
-    async fn create_row(&self, row: RowMeta) -> FlowyResult<i32> {
+    async fn create_row(&self, row: RowMeta, upper_row_id: Option<String>) -> FlowyResult<i32> {
         let mut row_count = 0;
         let _ = self
             .modify(|pad| {
-                let change = pad.add_row(row)?;
+                let change = pad.add_row(row, upper_row_id)?;
                 row_count = pad.number_of_rows();
                 Ok(change)
             })

+ 17 - 8
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -12,8 +12,7 @@ use flowy_grid_data_model::entities::{
 use std::collections::HashMap;
 
 use crate::services::row::{
-    make_row_by_row_id, make_rows, row_meta_from_context, serialize_cell_data, CreateRowContext,
-    CreateRowContextBuilder,
+    make_row_by_row_id, make_rows, row_meta_from_context, serialize_cell_data, RowMetaContext, RowMetaContextBuilder,
 };
 use flowy_sync::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
 use lib_infra::future::FutureResult;
@@ -82,18 +81,28 @@ impl ClientGridEditor {
         Ok(())
     }
 
-    pub async fn create_row(&self) -> FlowyResult<()> {
+    pub async fn create_row(&self, upper_row_id: Option<String>) -> FlowyResult<Row> {
         let field_metas = self.grid_meta_pad.read().await.get_field_metas(None)?;
         let block_id = self.last_block_id().await?;
-        let create_row_ctx = CreateRowContextBuilder::new(&field_metas).build();
-        let row = row_meta_from_context(&block_id, create_row_ctx);
-        let row_count = self.block_meta_manager.create_row(row).await?;
+
+        // insert empty row below the row whose id is upper_row_id
+        let row_meta_ctx = RowMetaContextBuilder::new(&field_metas).build();
+        let row_meta = row_meta_from_context(&block_id, row_meta_ctx);
+
+        // insert the row
+        let row_count = self
+            .block_meta_manager
+            .create_row(row_meta.clone(), upper_row_id)
+            .await?;
+        let row = make_rows(&field_metas, vec![row_meta.into()]).pop().unwrap();
+
+        // update block row count
         let changeset = GridBlockChangeset::from_row_count(&block_id, row_count);
         let _ = self.update_block(changeset).await?;
-        Ok(())
+        Ok(row)
     }
 
-    pub async fn insert_rows(&self, contexts: Vec<CreateRowContext>) -> FlowyResult<()> {
+    pub async fn insert_rows(&self, contexts: Vec<RowMetaContext>) -> FlowyResult<()> {
         let block_id = self.last_block_id().await?;
         let mut rows_by_block_id: HashMap<String, Vec<RowMeta>> = HashMap::new();
         for ctx in contexts {

+ 7 - 7
frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs

@@ -3,19 +3,19 @@ use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{CellMeta, FieldMeta, RowMeta, DEFAULT_ROW_HEIGHT};
 use std::collections::HashMap;
 
-pub struct CreateRowContextBuilder<'a> {
+pub struct RowMetaContextBuilder<'a> {
     field_meta_map: HashMap<&'a String, &'a FieldMeta>,
-    ctx: CreateRowContext,
+    ctx: RowMetaContext,
 }
 
-impl<'a> CreateRowContextBuilder<'a> {
+impl<'a> RowMetaContextBuilder<'a> {
     pub fn new(fields: &'a [FieldMeta]) -> Self {
         let field_meta_map = fields
             .iter()
             .map(|field| (&field.id, field))
             .collect::<HashMap<&String, &FieldMeta>>();
 
-        let ctx = CreateRowContext {
+        let ctx = RowMetaContext {
             row_id: uuid::Uuid::new_v4().to_string(),
             cell_by_field_id: Default::default(),
             height: DEFAULT_ROW_HEIGHT,
@@ -52,12 +52,12 @@ impl<'a> CreateRowContextBuilder<'a> {
         self
     }
 
-    pub fn build(self) -> CreateRowContext {
+    pub fn build(self) -> RowMetaContext {
         self.ctx
     }
 }
 
-pub fn row_meta_from_context(block_id: &str, ctx: CreateRowContext) -> RowMeta {
+pub fn row_meta_from_context(block_id: &str, ctx: RowMetaContext) -> RowMeta {
     RowMeta {
         id: ctx.row_id,
         block_id: block_id.to_owned(),
@@ -67,7 +67,7 @@ pub fn row_meta_from_context(block_id: &str, ctx: CreateRowContext) -> RowMeta {
     }
 }
 
-pub struct CreateRowContext {
+pub struct RowMetaContext {
     pub row_id: String,
     pub cell_by_field_id: HashMap<String, CellMeta>,
     pub height: i32,

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

@@ -2,6 +2,7 @@ use crate::services::row::deserialize_cell_data;
 use flowy_grid_data_model::entities::{Cell, CellMeta, FieldMeta, Row, RowMeta, RowOrder};
 use rayon::iter::{IntoParallelIterator, ParallelIterator};
 use std::collections::HashMap;
+use std::ops::Deref;
 use std::sync::Arc;
 
 pub(crate) struct RowIdsPerBlock {

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

@@ -12,10 +12,11 @@ pub fn make_default_grid() -> BuildGridContext {
 
     let single_select = SingleSelectTypeOptionsBuilder::default()
         .option(SelectOption::new("Done"))
+        .option(SelectOption::new("Unknown"))
         .option(SelectOption::new("Progress"));
 
     let single_select_field = FieldBuilder::new(single_select)
-        .name("Name")
+        .name("Status")
         .visibility(true)
         .field_type(FieldType::SingleSelect)
         .build();

+ 13 - 8
frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs

@@ -2,7 +2,7 @@ use crate::grid::script::EditorScript::*;
 use crate::grid::script::*;
 use chrono::NaiveDateTime;
 use flowy_grid::services::cell::*;
-use flowy_grid::services::row::{deserialize_cell_data, serialize_cell_data, CellDataSerde, CreateRowContextBuilder};
+use flowy_grid::services::row::{deserialize_cell_data, serialize_cell_data, CellDataSerde, RowMetaContextBuilder};
 use flowy_grid_data_model::entities::{
     CellMetaChangeset, FieldChangeset, FieldType, GridBlock, GridBlockChangeset, RowMetaChangeset,
 };
@@ -179,7 +179,7 @@ async fn grid_create_row() {
 #[tokio::test]
 async fn grid_create_row2() {
     let mut test = GridEditorTest::new().await;
-    let create_row_context = CreateRowContextBuilder::new(&test.field_metas).build();
+    let create_row_context = RowMetaContextBuilder::new(&test.field_metas).build();
     let scripts = vec![
         AssertRowCount(3),
         CreateRow {
@@ -193,7 +193,7 @@ async fn grid_create_row2() {
 #[tokio::test]
 async fn grid_update_row() {
     let mut test = GridEditorTest::new().await;
-    let context = CreateRowContextBuilder::new(&test.field_metas).build();
+    let context = RowMetaContextBuilder::new(&test.field_metas).build();
     let changeset = RowMetaChangeset {
         row_id: context.row_id.clone(),
         height: None,
@@ -216,8 +216,8 @@ async fn grid_update_row() {
 #[tokio::test]
 async fn grid_delete_row() {
     let mut test = GridEditorTest::new().await;
-    let context_1 = CreateRowContextBuilder::new(&test.field_metas).build();
-    let context_2 = CreateRowContextBuilder::new(&test.field_metas).build();
+    let context_1 = RowMetaContextBuilder::new(&test.field_metas).build();
+    let context_2 = RowMetaContextBuilder::new(&test.field_metas).build();
     let row_ids = vec![context_1.row_id.clone(), context_2.row_id.clone()];
     let scripts = vec![
         AssertRowCount(3),
@@ -242,7 +242,7 @@ async fn grid_delete_row() {
 #[tokio::test]
 async fn grid_row_add_cells_test() {
     let mut test = GridEditorTest::new().await;
-    let mut builder = CreateRowContextBuilder::new(&test.field_metas);
+    let mut builder = RowMetaContextBuilder::new(&test.field_metas);
     for field in &test.field_metas {
         match field.field_type {
             FieldType::RichText => {
@@ -288,7 +288,7 @@ async fn grid_row_add_cells_test() {
 #[tokio::test]
 async fn grid_row_add_selection_cell_test() {
     let mut test = GridEditorTest::new().await;
-    let mut builder = CreateRowContextBuilder::new(&test.field_metas);
+    let mut builder = RowMetaContextBuilder::new(&test.field_metas);
     let uuid = uuid::Uuid::new_v4().to_string();
     let mut single_select_field_id = "".to_string();
     let mut multi_select_field_id = "".to_string();
@@ -343,7 +343,7 @@ async fn grid_row_add_selection_cell_test() {
 #[tokio::test]
 async fn grid_row_add_date_cell_test() {
     let mut test = GridEditorTest::new().await;
-    let mut builder = CreateRowContextBuilder::new(&test.field_metas);
+    let mut builder = RowMetaContextBuilder::new(&test.field_metas);
     let mut date_field = None;
     let timestamp = 1647390674;
     for field in &test.field_metas {
@@ -373,8 +373,11 @@ async fn grid_cell_update() {
     let mut test = GridEditorTest::new().await;
     let field_metas = &test.field_metas;
     let row_metas = &test.row_metas;
+    let grid_blocks = &test.grid_blocks;
     assert_eq!(row_metas.len(), 3);
+    assert_eq!(grid_blocks.len(), 1);
 
+    let block_id = &grid_blocks.first().unwrap().id;
     let mut scripts = vec![];
     for (index, row_meta) in row_metas.iter().enumerate() {
         for field_meta in field_metas {
@@ -396,6 +399,7 @@ async fn grid_cell_update() {
 
                 scripts.push(UpdateCell {
                     changeset: CellMetaChangeset {
+                        grid_id: block_id.to_string(),
                         row_id: row_meta.id.clone(),
                         field_id: field_meta.id.clone(),
                         data: Some(data),
@@ -416,6 +420,7 @@ async fn grid_cell_update() {
 
                 scripts.push(UpdateCell {
                     changeset: CellMetaChangeset {
+                        grid_id: block_id.to_string(),
                         row_id: row_meta.id.clone(),
                         field_id: field_meta.id.clone(),
                         data: Some(data),

+ 2 - 2
frontend/rust-lib/flowy-grid/tests/grid/script.rs

@@ -4,7 +4,7 @@ use flowy_collaboration::client_grid::GridBuilder;
 use flowy_grid::services::cell::*;
 use flowy_grid::services::field::*;
 use flowy_grid::services::grid_editor::{ClientGridEditor, GridPadBuilder};
-use flowy_grid::services::row::CreateRowContext;
+use flowy_grid::services::row::RowMetaContext;
 use flowy_grid_data_model::entities::{
     BuildGridContext, CellMetaChangeset, FieldChangeset, FieldMeta, FieldType, GridBlock, GridBlockChangeset, RowMeta,
     RowMetaChangeset,
@@ -50,7 +50,7 @@ pub enum EditorScript {
     },
     CreateEmptyRow,
     CreateRow {
-        context: CreateRowContext,
+        context: RowMetaContext,
     },
     UpdateRow {
         changeset: RowMetaChangeset,

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

@@ -36,8 +36,24 @@ impl GridBlockMetaPad {
         Self::from_delta(block_delta)
     }
 
-    pub fn add_row(&mut self, row: RowMeta) -> CollaborateResult<Option<GridBlockMetaChange>> {
+    pub fn add_row(
+        &mut self,
+        row: RowMeta,
+        upper_row_id: Option<String>,
+    ) -> CollaborateResult<Option<GridBlockMetaChange>> {
         self.modify(|rows| {
+            if let Some(upper_row_id) = upper_row_id {
+                if upper_row_id.is_empty() {
+                    rows.insert(0, Arc::new(row));
+                    return Ok(Some(()));
+                }
+
+                if let Some(index) = rows.iter().position(|row| row.id == upper_row_id) {
+                    rows.insert(index, Arc::new(row));
+                    return Ok(Some(()));
+                }
+            }
+
             rows.push(Arc::new(row));
             Ok(Some(()))
         })
@@ -202,13 +218,85 @@ mod tests {
             visibility: false,
         };
 
-        let change = pad.add_row(row).unwrap().unwrap();
+        let change = pad.add_row(row, None).unwrap().unwrap();
         assert_eq!(
             change.delta.to_delta_str(),
             r#"[{"retain":24},{"insert":"{\"id\":\"1\",\"block_id\":\"1\",\"cell_by_field_id\":{},\"height\":0,\"visibility\":false}"},{"retain":2}]"#
         );
     }
 
+    #[test]
+    fn block_meta_insert_row() {
+        let mut pad = test_pad();
+        let row_1 = test_row_meta("1", &pad);
+        let row_2 = test_row_meta("2", &pad);
+        let row_3 = test_row_meta("3", &pad);
+
+        let change = pad.add_row(row_1.clone(), None).unwrap().unwrap();
+        assert_eq!(
+            change.delta.to_delta_str(),
+            r#"[{"retain":24},{"insert":"{\"id\":\"1\",\"block_id\":\"1\",\"cell_by_field_id\":{},\"height\":0,\"visibility\":false}"},{"retain":2}]"#
+        );
+
+        let change = pad.add_row(row_2.clone(), None).unwrap().unwrap();
+        assert_eq!(
+            change.delta.to_delta_str(),
+            r#"[{"retain":101},{"insert":",{\"id\":\"2\",\"block_id\":\"1\",\"cell_by_field_id\":{},\"height\":0,\"visibility\":false}"},{"retain":2}]"#
+        );
+
+        let change = pad.add_row(row_3.clone(), Some("2".to_string())).unwrap().unwrap();
+        assert_eq!(
+            change.delta.to_delta_str(),
+            r#"[{"retain":109},{"insert":"3\",\"block_id\":\"1\",\"cell_by_field_id\":{},\"height\":0,\"visibility\":false},{\"id\":\""},{"retain":72}]"#
+        );
+
+        assert_eq!(*pad.rows[0], row_1);
+        assert_eq!(*pad.rows[1], row_3);
+        assert_eq!(*pad.rows[2], row_2);
+    }
+
+    fn test_row_meta(id: &str, pad: &GridBlockMetaPad) -> RowMeta {
+        RowMeta {
+            id: id.to_string(),
+            block_id: pad.block_id.clone(),
+            cell_by_field_id: Default::default(),
+            height: 0,
+            visibility: false,
+        }
+    }
+
+    #[test]
+    fn block_meta_insert_row2() {
+        let mut pad = test_pad();
+        let row_1 = test_row_meta("1", &pad);
+        let row_2 = test_row_meta("2", &pad);
+        let row_3 = test_row_meta("3", &pad);
+
+        let _ = pad.add_row(row_1.clone(), None).unwrap().unwrap();
+        let _ = pad.add_row(row_2.clone(), None).unwrap().unwrap();
+        let _ = pad.add_row(row_3.clone(), Some("1".to_string())).unwrap().unwrap();
+
+        assert_eq!(*pad.rows[0], row_3);
+        assert_eq!(*pad.rows[1], row_1);
+        assert_eq!(*pad.rows[2], row_2);
+    }
+
+    #[test]
+    fn block_meta_insert_row3() {
+        let mut pad = test_pad();
+        let row_1 = test_row_meta("1", &pad);
+        let row_2 = test_row_meta("2", &pad);
+        let row_3 = test_row_meta("3", &pad);
+
+        let _ = pad.add_row(row_1.clone(), None).unwrap().unwrap();
+        let _ = pad.add_row(row_2.clone(), None).unwrap().unwrap();
+        let _ = pad.add_row(row_3.clone(), Some("".to_string())).unwrap().unwrap();
+
+        assert_eq!(*pad.rows[0], row_3);
+        assert_eq!(*pad.rows[1], row_1);
+        assert_eq!(*pad.rows[2], row_2);
+    }
+
     #[test]
     fn block_meta_delete_row() {
         let mut pad = test_pad();
@@ -221,7 +309,7 @@ mod tests {
             visibility: false,
         };
 
-        let _ = pad.add_row(row.clone()).unwrap().unwrap();
+        let _ = pad.add_row(row.clone(), None).unwrap().unwrap();
         let change = pad.delete_rows(&[row.id]).unwrap().unwrap();
         assert_eq!(
             change.delta.to_delta_str(),
@@ -249,7 +337,7 @@ mod tests {
             cell_by_field_id: Default::default(),
         };
 
-        let _ = pad.add_row(row).unwrap().unwrap();
+        let _ = pad.add_row(row, None).unwrap().unwrap();
         let change = pad.update_row(changeset).unwrap().unwrap();
 
         assert_eq!(

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

@@ -222,6 +222,15 @@ impl AsRef<str> for GridId {
     }
 }
 
+#[derive(ProtoBuf, Default)]
+pub struct CreateRowPayload {
+    #[pb(index = 1)]
+    pub grid_id: String,
+
+    #[pb(index = 2, one_of)]
+    pub upper_row_id: Option<String>,
+}
+
 #[derive(ProtoBuf, Default)]
 pub struct QueryFieldPayload {
     #[pb(index = 1)]

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

@@ -290,12 +290,15 @@ impl CellMeta {
 #[derive(Debug, Clone, Default, ProtoBuf)]
 pub struct CellMetaChangeset {
     #[pb(index = 1)]
-    pub row_id: String,
+    pub grid_id: String,
 
     #[pb(index = 2)]
+    pub row_id: String,
+
+    #[pb(index = 3)]
     pub field_id: String,
 
-    #[pb(index = 3, one_of)]
+    #[pb(index = 4, one_of)]
     pub data: Option<String>,
 }
 

+ 249 - 5
shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs

@@ -2433,6 +2433,247 @@ impl ::protobuf::reflect::ProtobufValue for GridId {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct CreateRowPayload {
+    // message fields
+    pub grid_id: ::std::string::String,
+    // message oneof groups
+    pub one_of_upper_row_id: ::std::option::Option<CreateRowPayload_oneof_one_of_upper_row_id>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a CreateRowPayload {
+    fn default() -> &'a CreateRowPayload {
+        <CreateRowPayload as ::protobuf::Message>::default_instance()
+    }
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum CreateRowPayload_oneof_one_of_upper_row_id {
+    upper_row_id(::std::string::String),
+}
+
+impl CreateRowPayload {
+    pub fn new() -> CreateRowPayload {
+        ::std::default::Default::default()
+    }
+
+    // string grid_id = 1;
+
+
+    pub fn get_grid_id(&self) -> &str {
+        &self.grid_id
+    }
+    pub fn clear_grid_id(&mut self) {
+        self.grid_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_grid_id(&mut self, v: ::std::string::String) {
+        self.grid_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_grid_id(&mut self) -> &mut ::std::string::String {
+        &mut self.grid_id
+    }
+
+    // Take field
+    pub fn take_grid_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.grid_id, ::std::string::String::new())
+    }
+
+    // string upper_row_id = 2;
+
+
+    pub fn get_upper_row_id(&self) -> &str {
+        match self.one_of_upper_row_id {
+            ::std::option::Option::Some(CreateRowPayload_oneof_one_of_upper_row_id::upper_row_id(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_upper_row_id(&mut self) {
+        self.one_of_upper_row_id = ::std::option::Option::None;
+    }
+
+    pub fn has_upper_row_id(&self) -> bool {
+        match self.one_of_upper_row_id {
+            ::std::option::Option::Some(CreateRowPayload_oneof_one_of_upper_row_id::upper_row_id(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_upper_row_id(&mut self, v: ::std::string::String) {
+        self.one_of_upper_row_id = ::std::option::Option::Some(CreateRowPayload_oneof_one_of_upper_row_id::upper_row_id(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_upper_row_id(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(CreateRowPayload_oneof_one_of_upper_row_id::upper_row_id(_)) = self.one_of_upper_row_id {
+        } else {
+            self.one_of_upper_row_id = ::std::option::Option::Some(CreateRowPayload_oneof_one_of_upper_row_id::upper_row_id(::std::string::String::new()));
+        }
+        match self.one_of_upper_row_id {
+            ::std::option::Option::Some(CreateRowPayload_oneof_one_of_upper_row_id::upper_row_id(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_upper_row_id(&mut self) -> ::std::string::String {
+        if self.has_upper_row_id() {
+            match self.one_of_upper_row_id.take() {
+                ::std::option::Option::Some(CreateRowPayload_oneof_one_of_upper_row_id::upper_row_id(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+}
+
+impl ::protobuf::Message for CreateRowPayload {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?;
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_upper_row_id = ::std::option::Option::Some(CreateRowPayload_oneof_one_of_upper_row_id::upper_row_id(is.read_string()?));
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.grid_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.grid_id);
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_upper_row_id {
+            match v {
+                &CreateRowPayload_oneof_one_of_upper_row_id::upper_row_id(ref v) => {
+                    my_size += ::protobuf::rt::string_size(2, &v);
+                },
+            };
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.grid_id.is_empty() {
+            os.write_string(1, &self.grid_id)?;
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_upper_row_id {
+            match v {
+                &CreateRowPayload_oneof_one_of_upper_row_id::upper_row_id(ref v) => {
+                    os.write_string(2, v)?;
+                },
+            };
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> CreateRowPayload {
+        CreateRowPayload::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "grid_id",
+                |m: &CreateRowPayload| { &m.grid_id },
+                |m: &mut CreateRowPayload| { &mut m.grid_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "upper_row_id",
+                CreateRowPayload::has_upper_row_id,
+                CreateRowPayload::get_upper_row_id,
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CreateRowPayload>(
+                "CreateRowPayload",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static CreateRowPayload {
+        static instance: ::protobuf::rt::LazyV2<CreateRowPayload> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(CreateRowPayload::new)
+    }
+}
+
+impl ::protobuf::Clear for CreateRowPayload {
+    fn clear(&mut self) {
+        self.grid_id.clear();
+        self.one_of_upper_row_id = ::std::option::Option::None;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for CreateRowPayload {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for CreateRowPayload {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(PartialEq,Clone,Default)]
 pub struct QueryFieldPayload {
     // message fields
@@ -2890,11 +3131,14 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x18\n\x07content\
     \x18\x02\x20\x01(\tR\x07content\"'\n\x11CreateGridPayload\x12\x12\n\x04n\
     ame\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\x05value\x18\
-    \x01\x20\x01(\tR\x05value\"d\n\x11QueryFieldPayload\x12\x17\n\x07grid_id\
-    \x18\x01\x20\x01(\tR\x06gridId\x126\n\x0cfield_orders\x18\x02\x20\x01(\
-    \x0b2\x13.RepeatedFieldOrderR\x0bfieldOrders\"\\\n\x0fQueryRowPayload\
-    \x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x120\n\nrow_orders\
-    \x18\x02\x20\x01(\x0b2\x11.RepeatedRowOrderR\trowOrdersb\x06proto3\
+    \x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\n\x07grid_id\
+    \x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cupper_row_id\x18\x02\x20\x01(\
+    \tH\0R\nupperRowIdB\x15\n\x13one_of_upper_row_id\"d\n\x11QueryFieldPaylo\
+    ad\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x126\n\x0cfield_or\
+    ders\x18\x02\x20\x01(\x0b2\x13.RepeatedFieldOrderR\x0bfieldOrders\"\\\n\
+    \x0fQueryRowPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\
+    \x120\n\nrow_orders\x18\x02\x20\x01(\x0b2\x11.RepeatedRowOrderR\trowOrde\
+    rsb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 64 - 21
shared-lib/flowy-grid-data-model/src/protobuf/model/meta.rs

@@ -2793,6 +2793,7 @@ impl ::protobuf::reflect::ProtobufValue for CellMeta {
 #[derive(PartialEq,Clone,Default)]
 pub struct CellMetaChangeset {
     // message fields
+    pub grid_id: ::std::string::String,
     pub row_id: ::std::string::String,
     pub field_id: ::std::string::String,
     // message oneof groups
@@ -2818,7 +2819,33 @@ impl CellMetaChangeset {
         ::std::default::Default::default()
     }
 
-    // string row_id = 1;
+    // string grid_id = 1;
+
+
+    pub fn get_grid_id(&self) -> &str {
+        &self.grid_id
+    }
+    pub fn clear_grid_id(&mut self) {
+        self.grid_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_grid_id(&mut self, v: ::std::string::String) {
+        self.grid_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_grid_id(&mut self) -> &mut ::std::string::String {
+        &mut self.grid_id
+    }
+
+    // Take field
+    pub fn take_grid_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.grid_id, ::std::string::String::new())
+    }
+
+    // string row_id = 2;
 
 
     pub fn get_row_id(&self) -> &str {
@@ -2844,7 +2871,7 @@ impl CellMetaChangeset {
         ::std::mem::replace(&mut self.row_id, ::std::string::String::new())
     }
 
-    // string field_id = 2;
+    // string field_id = 3;
 
 
     pub fn get_field_id(&self) -> &str {
@@ -2870,7 +2897,7 @@ impl CellMetaChangeset {
         ::std::mem::replace(&mut self.field_id, ::std::string::String::new())
     }
 
-    // string data = 3;
+    // string data = 4;
 
 
     pub fn get_data(&self) -> &str {
@@ -2930,12 +2957,15 @@ impl ::protobuf::Message for CellMetaChangeset {
             let (field_number, wire_type) = is.read_tag_unpack()?;
             match field_number {
                 1 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.row_id)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?;
                 },
                 2 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.row_id)?;
                 },
                 3 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?;
+                },
+                4 => {
                     if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
                         return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
                     }
@@ -2953,16 +2983,19 @@ impl ::protobuf::Message for CellMetaChangeset {
     #[allow(unused_variables)]
     fn compute_size(&self) -> u32 {
         let mut my_size = 0;
+        if !self.grid_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.grid_id);
+        }
         if !self.row_id.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.row_id);
+            my_size += ::protobuf::rt::string_size(2, &self.row_id);
         }
         if !self.field_id.is_empty() {
-            my_size += ::protobuf::rt::string_size(2, &self.field_id);
+            my_size += ::protobuf::rt::string_size(3, &self.field_id);
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_data {
             match v {
                 &CellMetaChangeset_oneof_one_of_data::data(ref v) => {
-                    my_size += ::protobuf::rt::string_size(3, &v);
+                    my_size += ::protobuf::rt::string_size(4, &v);
                 },
             };
         }
@@ -2972,16 +3005,19 @@ impl ::protobuf::Message for CellMetaChangeset {
     }
 
     fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.grid_id.is_empty() {
+            os.write_string(1, &self.grid_id)?;
+        }
         if !self.row_id.is_empty() {
-            os.write_string(1, &self.row_id)?;
+            os.write_string(2, &self.row_id)?;
         }
         if !self.field_id.is_empty() {
-            os.write_string(2, &self.field_id)?;
+            os.write_string(3, &self.field_id)?;
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_data {
             match v {
                 &CellMetaChangeset_oneof_one_of_data::data(ref v) => {
-                    os.write_string(3, v)?;
+                    os.write_string(4, v)?;
                 },
             };
         }
@@ -3023,6 +3059,11 @@ impl ::protobuf::Message for CellMetaChangeset {
         static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
         descriptor.get(|| {
             let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "grid_id",
+                |m: &CellMetaChangeset| { &m.grid_id },
+                |m: &mut CellMetaChangeset| { &mut m.grid_id },
+            ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "row_id",
                 |m: &CellMetaChangeset| { &m.row_id },
@@ -3054,6 +3095,7 @@ impl ::protobuf::Message for CellMetaChangeset {
 
 impl ::protobuf::Clear for CellMetaChangeset {
     fn clear(&mut self) {
+        self.grid_id.clear();
         self.row_id.clear();
         self.field_id.clear();
         self.one_of_data = ::std::option::Option::None;
@@ -3455,16 +3497,17 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x03key\x12\x1f\n\x05value\x18\x02\x20\x01(\x0b2\t.CellMetaR\x05value:\
     \x028\x01B\x0f\n\rone_of_heightB\x13\n\x11one_of_visibility\"9\n\x08Cell\
     Meta\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x12\n\x04d\
-    ata\x18\x02\x20\x01(\tR\x04data\"j\n\x11CellMetaChangeset\x12\x15\n\x06r\
-    ow_id\x18\x01\x20\x01(\tR\x05rowId\x12\x19\n\x08field_id\x18\x02\x20\x01\
-    (\tR\x07fieldId\x12\x14\n\x04data\x18\x03\x20\x01(\tH\0R\x04dataB\r\n\
-    \x0bone_of_data\"\xa2\x01\n\x10BuildGridContext\x12+\n\x0bfield_metas\
-    \x18\x01\x20\x03(\x0b2\n.FieldMetaR\nfieldMetas\x12)\n\ngrid_block\x18\
-    \x02\x20\x01(\x0b2\n.GridBlockR\tgridBlock\x126\n\x0fgrid_block_meta\x18\
-    \x03\x20\x01(\x0b2\x0e.GridBlockMetaR\rgridBlockMeta*d\n\tFieldType\x12\
-    \x0c\n\x08RichText\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\
-    \x10\x02\x12\x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\
-    \x04\x12\x0c\n\x08Checkbox\x10\x05b\x06proto3\
+    ata\x18\x02\x20\x01(\tR\x04data\"\x83\x01\n\x11CellMetaChangeset\x12\x17\
+    \n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x15\n\x06row_id\x18\x02\
+    \x20\x01(\tR\x05rowId\x12\x19\n\x08field_id\x18\x03\x20\x01(\tR\x07field\
+    Id\x12\x14\n\x04data\x18\x04\x20\x01(\tH\0R\x04dataB\r\n\x0bone_of_data\
+    \"\xa2\x01\n\x10BuildGridContext\x12+\n\x0bfield_metas\x18\x01\x20\x03(\
+    \x0b2\n.FieldMetaR\nfieldMetas\x12)\n\ngrid_block\x18\x02\x20\x01(\x0b2\
+    \n.GridBlockR\tgridBlock\x126\n\x0fgrid_block_meta\x18\x03\x20\x01(\x0b2\
+    \x0e.GridBlockMetaR\rgridBlockMeta*d\n\tFieldType\x12\x0c\n\x08RichText\
+    \x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\
+    \x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08C\
+    heckbox\x10\x05b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 4 - 0
shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto

@@ -49,6 +49,10 @@ message CreateGridPayload {
 message GridId {
     string value = 1;
 }
+message CreateRowPayload {
+    string grid_id = 1;
+    oneof one_of_upper_row_id { string upper_row_id = 2; };
+}
 message QueryFieldPayload {
     string grid_id = 1;
     RepeatedFieldOrder field_orders = 2;

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

@@ -56,9 +56,10 @@ message CellMeta {
     string data = 2;
 }
 message CellMetaChangeset {
-    string row_id = 1;
-    string field_id = 2;
-    oneof one_of_data { string data = 3; };
+    string grid_id = 1;
+    string row_id = 2;
+    string field_id = 3;
+    oneof one_of_data { string data = 4; };
 }
 message BuildGridContext {
     repeated FieldMeta field_metas = 1;