Bläddra i källkod

feat: config textfield cell

appflowy 3 år sedan
förälder
incheckning
54cf451826
31 ändrade filer med 517 tillägg och 222 borttagningar
  1. 16 21
      frontend/app_flowy/lib/startup/home_deps_resolver.dart
  2. 3 0
      frontend/app_flowy/lib/workspace/application/app/prelude.dart
  3. 4 0
      frontend/app_flowy/lib/workspace/application/doc/prelude.dart
  4. 35 0
      frontend/app_flowy/lib/workspace/application/grid/data.dart
  5. 3 33
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  6. 5 0
      frontend/app_flowy/lib/workspace/application/grid/prelude.dart
  7. 50 0
      frontend/app_flowy/lib/workspace/application/grid/row_bloc.dart
  8. 10 0
      frontend/app_flowy/lib/workspace/application/grid/row_service.dart
  9. 1 0
      frontend/app_flowy/lib/workspace/application/home/prelude.dart
  10. 2 0
      frontend/app_flowy/lib/workspace/application/menu/prelude.dart
  11. 3 0
      frontend/app_flowy/lib/workspace/application/trash/prelude.dart
  12. 3 0
      frontend/app_flowy/lib/workspace/application/view/prelude.dart
  13. 3 0
      frontend/app_flowy/lib/workspace/application/workspace/prelude.dart
  14. 2 2
      frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/header.dart
  15. 8 4
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
  16. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/layout.dart
  17. 3 4
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart
  18. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_builder.dart
  19. 12 18
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_container.dart
  20. 73 19
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_cell.dart
  21. 40 46
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_row.dart
  22. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/footer/grid_footer.dart
  23. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header_cell.dart
  24. 42 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
  25. 6 3
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
  26. 2 2
      frontend/rust-lib/dart-ffi/Cargo.toml
  27. 3 25
      frontend/rust-lib/flowy-grid/src/services/grid_builder.rs
  28. 6 6
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  29. 41 8
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  30. 134 27
      shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
  31. 3 0
      shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto

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

@@ -1,26 +1,14 @@
 import 'package:app_flowy/user/application/user_listener.dart';
 import 'package:app_flowy/user/application/user_service.dart';
-import 'package:app_flowy/workspace/application/app/app_bloc.dart';
-import 'package:app_flowy/workspace/application/app/app_listener.dart';
-import 'package:app_flowy/workspace/application/app/app_service.dart';
-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';
-import 'package:app_flowy/workspace/application/trash/trash_bloc.dart';
-import 'package:app_flowy/workspace/application/trash/trash_listener.dart';
-import 'package:app_flowy/workspace/application/trash/trash_service.dart';
-import 'package:app_flowy/workspace/application/view/view_bloc.dart';
-import 'package:app_flowy/workspace/application/view/view_listener.dart';
-import 'package:app_flowy/workspace/application/view/view_service.dart';
-import 'package:app_flowy/workspace/application/workspace/welcome_bloc.dart';
-import 'package:app_flowy/workspace/application/workspace/workspace_listener.dart';
-import 'package:app_flowy/workspace/application/workspace/workspace_service.dart';
+import 'package:app_flowy/workspace/application/app/prelude.dart';
+import 'package:app_flowy/workspace/application/doc/prelude.dart';
+import 'package:app_flowy/workspace/application/grid/prelude.dart';
+import 'package:app_flowy/workspace/application/trash/prelude.dart';
+import 'package:app_flowy/workspace/application/workspace/prelude.dart';
+import 'package:app_flowy/workspace/application/view/prelude.dart';
+import 'package:app_flowy/workspace/application/home/prelude.dart';
+import 'package:app_flowy/workspace/application/menu/prelude.dart';
+
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
@@ -107,6 +95,13 @@ class HomeDepsResolver {
       ),
     );
 
+    getIt.registerFactoryParam<RowBloc, GridRowData, void>(
+      (data, _) => RowBloc(
+        data: data,
+        service: RowService(),
+      ),
+    );
+
     // trash
     getIt.registerLazySingleton<TrashService>(() => TrashService());
     getIt.registerLazySingleton<TrashListener>(() => TrashListener());

+ 3 - 0
frontend/app_flowy/lib/workspace/application/app/prelude.dart

@@ -0,0 +1,3 @@
+export 'app_bloc.dart';
+export 'app_listener.dart';
+export 'app_service.dart';

+ 4 - 0
frontend/app_flowy/lib/workspace/application/doc/prelude.dart

@@ -0,0 +1,4 @@
+export 'doc_bloc.dart';
+export 'doc_service.dart';
+export 'share_bloc.dart';
+export 'share_service.dart';

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

@@ -0,0 +1,35 @@
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+
+class GridInfo {
+  List<Row> rows;
+  List<Field> fields;
+
+  GridInfo({
+    required this.rows,
+    required this.fields,
+  });
+
+  GridRowData rowAtIndex(int index) {
+    final row = rows[index];
+    return GridRowData(
+      row: row,
+      fields: fields,
+      cellMap: row.cellByFieldId,
+    );
+  }
+
+  int numberOfRows() {
+    return rows.length;
+  }
+}
+
+class GridRowData {
+  Row row;
+  List<Field> fields;
+  Map<String, Cell> cellMap;
+  GridRowData({
+    required this.row,
+    required this.fields,
+    required this.cellMap,
+  });
+}

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

@@ -7,6 +7,7 @@ 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 'data.dart';
 import 'grid_service.dart';
 
 part 'grid_bloc.freezed.dart';
@@ -21,7 +22,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     on<GridEvent>(
       (event, emit) async {
         await event.map(
-          initial: (Initial value) async {
+          initial: (InitialGrid value) async {
             await _loadGrid(emit);
             await _loadFields(emit);
             await _loadGridInfo(emit);
@@ -88,7 +89,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
 
 @freezed
 abstract class GridEvent with _$GridEvent {
-  const factory GridEvent.initial() = Initial;
+  const factory GridEvent.initial() = InitialGrid;
   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;
@@ -113,34 +114,3 @@ 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,
-  });
-}

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

@@ -0,0 +1,5 @@
+export 'grid_bloc.dart';
+export 'row_bloc.dart';
+export 'row_service.dart';
+export 'grid_service.dart';
+export 'data.dart';

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

@@ -0,0 +1,50 @@
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:dartz/dartz.dart';
+import 'dart:async';
+import 'data.dart';
+import 'row_service.dart';
+
+part 'row_bloc.freezed.dart';
+
+class RowBloc extends Bloc<RowEvent, RowState> {
+  final RowService service;
+  final GridRowData data;
+
+  RowBloc({required this.data, required this.service}) : super(RowState.initial()) {
+    on<RowEvent>(
+      (event, emit) async {
+        await event.map(
+          initial: (_InitialRow value) async {},
+          createRow: (_CreateRow value) {},
+          highlightRow: (_HighlightRow value) {
+            emit(state.copyWith(
+              isHighlight: value.rowId.fold(() => false, (rowId) => rowId == data.row.id),
+            ));
+          },
+        );
+      },
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    return super.close();
+  }
+}
+
+@freezed
+abstract class RowEvent with _$RowEvent {
+  const factory RowEvent.initial() = _InitialRow;
+  const factory RowEvent.createRow() = _CreateRow;
+  const factory RowEvent.highlightRow(Option<String> rowId) = _HighlightRow;
+}
+
+@freezed
+abstract class RowState with _$RowState {
+  const factory RowState({
+    required bool isHighlight,
+  }) = _RowState;
+
+  factory RowState.initial() => const RowState(isHighlight: false);
+}

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

@@ -0,0 +1,10 @@
+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();
+  }
+}

+ 1 - 0
frontend/app_flowy/lib/workspace/application/home/prelude.dart

@@ -0,0 +1 @@
+export 'home_listen_bloc.dart';

+ 2 - 0
frontend/app_flowy/lib/workspace/application/menu/prelude.dart

@@ -0,0 +1,2 @@
+export 'menu_bloc.dart';
+export 'menu_user_bloc.dart';

+ 3 - 0
frontend/app_flowy/lib/workspace/application/trash/prelude.dart

@@ -0,0 +1,3 @@
+export 'trash_bloc.dart';
+export 'trash_listener.dart';
+export 'trash_service.dart';

+ 3 - 0
frontend/app_flowy/lib/workspace/application/view/prelude.dart

@@ -0,0 +1,3 @@
+export 'view_bloc.dart';
+export 'view_listener.dart';
+export 'view_service.dart';

+ 3 - 0
frontend/app_flowy/lib/workspace/application/workspace/prelude.dart

@@ -0,0 +1,3 @@
+export 'welcome_bloc.dart';
+export 'workspace_listener.dart';
+export 'workspace_service.dart';

+ 2 - 2
frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/header.dart

@@ -37,7 +37,7 @@ class MenuAppHeader extends StatelessWidget {
           _renderExpandedIcon(context, theme),
           // HSpace(MenuAppSizes.iconPadding),
           _renderTitle(context, theme),
-          _renderAddButton(context),
+          _renderCreateViewButton(context),
         ],
       ),
     );
@@ -99,7 +99,7 @@ class MenuAppHeader extends StatelessWidget {
     );
   }
 
-  Widget _renderAddButton(BuildContext context) {
+  Widget _renderCreateViewButton(BuildContext context) {
     return Tooltip(
       message: LocaleKeys.menuAppHeader_addPageTooltip.tr(),
       child: AddButton(

+ 8 - 4
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart

@@ -1,4 +1,5 @@
 import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/grid/data.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';
@@ -135,10 +136,13 @@ class _GridBodyState extends State<GridBody> {
 
   Widget _buildRows(GridInfo gridInfo) {
     return SliverList(
-      delegate: SliverChildBuilderDelegate((context, index) {
-        final rowInfo = gridInfo.rowInfoAtIndex(index);
-        return RepaintBoundary(child: GridRowWidget(rowInfo));
-      }, childCount: gridInfo.numberOfRows()),
+      delegate: SliverChildBuilderDelegate(
+        (context, index) {
+          final data = gridInfo.rowAtIndex(index);
+          return RepaintBoundary(child: GridRowWidget(data));
+        },
+        childCount: gridInfo.numberOfRows(),
+      ),
     );
   }
 

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

@@ -8,6 +8,6 @@ class GridLayout {
 
     final fieldsWidth = fields.map((field) => field.width.toDouble()).reduce((value, element) => value + element);
 
-    return fieldsWidth + GridSize.firstHeaderPadding;
+    return fieldsWidth + GridSize.startHeaderPadding;
   }
 }

+ 3 - 4
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart

@@ -1,8 +1,8 @@
 class GridInsets {
   static double scale = 1;
 
-  static double get horizontal => 6 * scale;
-  static double get vertical => 6 * scale;
+  static double get horizontal => 8 * scale;
+  static double get vertical => 8 * scale;
 }
 
 class GridSize {
@@ -10,7 +10,6 @@ class GridSize {
 
   static double get scrollBarSize => 12 * scale;
   static double get headerHeight => 50 * scale;
-  static double get rowHeight => 36 * scale;
   static double get footerHeight => 40 * scale;
-  static double get firstHeaderPadding => 20 * scale;
+  static double get startHeaderPadding => 30 * scale;
 }

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

@@ -4,7 +4,7 @@ import 'grid_cell.dart';
 class GridCellBuilder {
   static GridCellWidget buildCell(Field? field, Cell? cell) {
     if (field == null || cell == null) {
-      return const BlankCell();
+      return GridTextCell("123123123");
     }
 
     switch (field.fieldType) {

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

@@ -1,32 +1,26 @@
 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 Widget child;
   final double width;
-  const CellContainer({Key? key, required this.child, required this.width}) : super(key: key);
+  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,
+      onTap: () {},
+      child: Container(
+        constraints: BoxConstraints(
+          maxWidth: width,
         ),
+        padding: EdgeInsets.symmetric(vertical: GridInsets.vertical, horizontal: GridInsets.horizontal),
+        child: Center(child: IntrinsicHeight(child: child)),
       ),
     );
   }

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

@@ -1,6 +1,12 @@
+import 'package:app_flowy/workspace/application/grid/row_bloc.dart';
+import 'package:app_flowy/workspace/presentation/home/menu/app/header/add_button.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';
+import 'package:flowy_infra_ui/style_widget/icon_button.dart';
 import 'package:flowy_infra_ui/widget/mouse_hover_builder.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
 import 'cell_decoration.dart';
 // ignore: import_of_legacy_library_into_null_safe
@@ -13,12 +19,25 @@ abstract class GridCellWidget extends StatelessWidget {
 }
 
 class GridTextCell extends GridCellWidget {
-  final String content;
-  const GridTextCell(this.content, {Key? key}) : super(key: key);
+  late final TextEditingController _controller;
+
+  GridTextCell(String content, {Key? key}) : super(key: key) {
+    _controller = TextEditingController(text: content);
+  }
 
   @override
   Widget build(BuildContext context) {
-    return Text(content);
+    return TextField(
+      controller: _controller,
+      onChanged: (value) {},
+      maxLines: 1,
+      style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
+      decoration: const InputDecoration(
+        contentPadding: EdgeInsets.zero,
+        border: InputBorder.none,
+        isDense: true,
+      ),
+    );
   }
 }
 
@@ -72,29 +91,64 @@ class BlankCell extends GridCellWidget {
 }
 
 class RowLeading extends StatelessWidget {
-  const RowLeading({Key? key}) : super(key: key);
+  final String rowId;
+  const RowLeading({required this.rowId, Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    // return Expanded(
-    //   child: Container(
-    //     color: Colors.white10,
-    //     width: GridSize.firstHeaderPadding,
+    return BlocBuilder<RowBloc, RowState>(
+      builder: (context, state) {
+        if (state.isHighlight) {
+          return Row(
+            children: const [
+              CreateRowButton(),
+            ],
+          );
+        }
+
+        return const Spacer();
+      },
+    );
+
+    // return GestureDetector(
+    //   behavior: HitTestBehavior.translucent,
+    //   onTap: () {},
+    //   child: MouseHoverBuilder(
+    //     builder: (_, isHovered) => Container(
+    //       width: GridSize.startHeaderPadding,
+    //       decoration: CellDecoration.box(
+    //         color: isHovered ? Colors.red.withOpacity(.1) : Colors.white,
+    //       ),
+    //       padding: EdgeInsets.symmetric(vertical: GridInsets.vertical, horizontal: GridInsets.horizontal),
+    //     ),
     //   ),
     // );
+  }
+}
 
-    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),
-        ),
+class CreateRowButton extends StatelessWidget {
+  const CreateRowButton({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return Tooltip(
+      message: '',
+      child: FlowyIconButton(
+        hoverColor: theme.hover,
+        width: 22,
+        onPressed: () => context.read<RowBloc>().add(const RowEvent.createRow()),
+        icon: svg("home/add"),
       ),
     );
   }
 }
+
+class DrawRowButton extends StatelessWidget {
+  const DrawRowButton({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container();
+  }
+}

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

@@ -1,62 +1,56 @@
-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:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/grid/prelude.dart';
+import 'package:flowy_infra_ui/widget/mouse_hover_builder.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.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);
-}
+import 'package:dartz/dartz.dart';
 
 class GridRowWidget extends StatelessWidget {
-  final RowInfo rowInfo;
+  final GridRowData data;
   final Function(bool)? onHoverChange;
-  const GridRowWidget(this.rowInfo, {Key? key, this.onHoverChange}) : super(key: key);
+  const GridRowWidget(this.data, {Key? key, this.onHoverChange}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    return SizedBox(
-      height: GridSize.rowHeight,
-      child: _buildRowBody(),
+    return BlocProvider(
+      create: (context) => getIt<RowBloc>(param1: data),
+      child: BlocBuilder<RowBloc, RowState>(
+        builder: (context, state) {
+          return GestureDetector(
+            behavior: HitTestBehavior.translucent,
+            child: MouseRegion(
+              cursor: SystemMouseCursors.click,
+              onEnter: (p) => context.read<RowBloc>().add(RowEvent.highlightRow(some(data.row.id))),
+              onExit: (p) => context.read<RowBloc>().add(RowEvent.highlightRow(none())),
+              child: SizedBox(
+                height: data.row.height.toDouble(),
+                child: Row(
+                  crossAxisAlignment: CrossAxisAlignment.stretch,
+                  children: _buildCells(),
+                ),
+              ),
+            ),
+          );
+        },
+      ),
     );
   }
 
-  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;
+    return [
+      RowLeading(rowId: data.row.id),
+      ...data.fields.map(
+        (field) {
+          final cellData = data.cellMap[field.id];
+          return CellContainer(
+            width: field.width.toDouble(),
+            child: GridCellBuilder.buildCell(field, cellData),
+          );
+        },
+      )
+    ].toList();
   }
 }

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

@@ -34,7 +34,7 @@ class AddRowButton extends StatelessWidget {
       onTap: onTap,
       child: MouseHoverBuilder(
         builder: (_, isHovered) => Container(
-          width: GridSize.firstHeaderPadding,
+          width: GridSize.startHeaderPadding,
           height: GridSize.footerHeight,
           decoration: CellDecoration.box(
             color: isHovered ? Colors.red.withOpacity(.1) : Colors.white,

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

@@ -45,7 +45,7 @@ class HeaderCellLeading extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return Container(
-      width: GridSize.firstHeaderPadding,
+      width: GridSize.startHeaderPadding,
       color: GridHeaderConstants.backgroundColor,
     );
   }

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

@@ -550,6 +550,7 @@ class RawRow extends $pb.GeneratedMessage {
     ..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)
+    ..a<$core.int>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'height', $pb.PbFieldType.O3)
     ..hasRequiredFields = false
   ;
 
@@ -558,6 +559,7 @@ class RawRow extends $pb.GeneratedMessage {
     $core.String? id,
     $core.String? gridId,
     $core.Map<$core.String, RawCell>? cellByFieldId,
+    $core.int? height,
   }) {
     final _result = create();
     if (id != null) {
@@ -569,6 +571,9 @@ class RawRow extends $pb.GeneratedMessage {
     if (cellByFieldId != null) {
       _result.cellByFieldId.addAll(cellByFieldId);
     }
+    if (height != null) {
+      _result.height = height;
+    }
     return _result;
   }
   factory RawRow.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -612,6 +617,15 @@ class RawRow extends $pb.GeneratedMessage {
 
   @$pb.TagNumber(3)
   $core.Map<$core.String, RawCell> get cellByFieldId => $_getMap(2);
+
+  @$pb.TagNumber(4)
+  $core.int get height => $_getIZ(3);
+  @$pb.TagNumber(4)
+  set height($core.int v) { $_setSignedInt32(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasHeight() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearHeight() => clearField(4);
 }
 
 class RawCell extends $pb.GeneratedMessage {
@@ -620,6 +634,7 @@ class RawCell extends $pb.GeneratedMessage {
     ..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)
+    ..a<$core.int>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'height', $pb.PbFieldType.O3)
     ..hasRequiredFields = false
   ;
 
@@ -629,6 +644,7 @@ class RawCell extends $pb.GeneratedMessage {
     $core.String? rowId,
     $core.String? fieldId,
     AnyData? data,
+    $core.int? height,
   }) {
     final _result = create();
     if (id != null) {
@@ -643,6 +659,9 @@ class RawCell extends $pb.GeneratedMessage {
     if (data != null) {
       _result.data = data;
     }
+    if (height != null) {
+      _result.height = height;
+    }
     return _result;
   }
   factory RawCell.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -703,6 +722,15 @@ class RawCell extends $pb.GeneratedMessage {
   void clearData() => clearField(4);
   @$pb.TagNumber(4)
   AnyData ensureData() => $_ensure(3);
+
+  @$pb.TagNumber(5)
+  $core.int get height => $_getIZ(4);
+  @$pb.TagNumber(5)
+  set height($core.int v) { $_setSignedInt32(4, v); }
+  @$pb.TagNumber(5)
+  $core.bool hasHeight() => $_has(4);
+  @$pb.TagNumber(5)
+  void clearHeight() => clearField(5);
 }
 
 class RepeatedRow extends $pb.GeneratedMessage {
@@ -750,6 +778,7 @@ 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)
+    ..a<$core.int>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'height', $pb.PbFieldType.O3)
     ..hasRequiredFields = false
   ;
 
@@ -757,6 +786,7 @@ class Row extends $pb.GeneratedMessage {
   factory Row({
     $core.String? id,
     $core.Map<$core.String, Cell>? cellByFieldId,
+    $core.int? height,
   }) {
     final _result = create();
     if (id != null) {
@@ -765,6 +795,9 @@ class Row extends $pb.GeneratedMessage {
     if (cellByFieldId != null) {
       _result.cellByFieldId.addAll(cellByFieldId);
     }
+    if (height != null) {
+      _result.height = height;
+    }
     return _result;
   }
   factory Row.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -799,6 +832,15 @@ class Row extends $pb.GeneratedMessage {
 
   @$pb.TagNumber(2)
   $core.Map<$core.String, Cell> get cellByFieldId => $_getMap(1);
+
+  @$pb.TagNumber(3)
+  $core.int get height => $_getIZ(2);
+  @$pb.TagNumber(3)
+  set height($core.int v) { $_setSignedInt32(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasHeight() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearHeight() => clearField(3);
 }
 
 class Cell extends $pb.GeneratedMessage {

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

@@ -122,6 +122,7 @@ const RawRow$json = 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'},
+    const {'1': 'height', '3': 4, '4': 1, '5': 5, '10': 'height'},
   ],
   '3': const [RawRow_CellByFieldIdEntry$json],
 };
@@ -137,7 +138,7 @@ const RawRow_CellByFieldIdEntry$json = const {
 };
 
 /// Descriptor for `RawRow`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List rawRowDescriptor = $convert.base64Decode('CgZSYXdSb3cSDgoCaWQYASABKAlSAmlkEhcKB2dyaWRfaWQYAiABKAlSBmdyaWRJZBJDChBjZWxsX2J5X2ZpZWxkX2lkGAMgAygLMhouUmF3Um93LkNlbGxCeUZpZWxkSWRFbnRyeVINY2VsbEJ5RmllbGRJZBpKChJDZWxsQnlGaWVsZElkRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSHgoFdmFsdWUYAiABKAsyCC5SYXdDZWxsUgV2YWx1ZToCOAE=');
+final $typed_data.Uint8List rawRowDescriptor = $convert.base64Decode('CgZSYXdSb3cSDgoCaWQYASABKAlSAmlkEhcKB2dyaWRfaWQYAiABKAlSBmdyaWRJZBJDChBjZWxsX2J5X2ZpZWxkX2lkGAMgAygLMhouUmF3Um93LkNlbGxCeUZpZWxkSWRFbnRyeVINY2VsbEJ5RmllbGRJZBIWCgZoZWlnaHQYBCABKAVSBmhlaWdodBpKChJDZWxsQnlGaWVsZElkRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSHgoFdmFsdWUYAiABKAsyCC5SYXdDZWxsUgV2YWx1ZToCOAE=');
 @$core.Deprecated('Use rawCellDescriptor instead')
 const RawCell$json = const {
   '1': 'RawCell',
@@ -146,11 +147,12 @@ const RawCell$json = const {
     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'},
+    const {'1': 'height', '3': 5, '4': 1, '5': 5, '10': 'height'},
   ],
 };
 
 /// Descriptor for `RawCell`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List rawCellDescriptor = $convert.base64Decode('CgdSYXdDZWxsEg4KAmlkGAEgASgJUgJpZBIVCgZyb3dfaWQYAiABKAlSBXJvd0lkEhkKCGZpZWxkX2lkGAMgASgJUgdmaWVsZElkEhwKBGRhdGEYBCABKAsyCC5BbnlEYXRhUgRkYXRh');
+final $typed_data.Uint8List rawCellDescriptor = $convert.base64Decode('CgdSYXdDZWxsEg4KAmlkGAEgASgJUgJpZBIVCgZyb3dfaWQYAiABKAlSBXJvd0lkEhkKCGZpZWxkX2lkGAMgASgJUgdmaWVsZElkEhwKBGRhdGEYBCABKAsyCC5BbnlEYXRhUgRkYXRhEhYKBmhlaWdodBgFIAEoBVIGaGVpZ2h0');
 @$core.Deprecated('Use repeatedRowDescriptor instead')
 const RepeatedRow$json = const {
   '1': 'RepeatedRow',
@@ -167,6 +169,7 @@ const Row$json = const {
   '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'},
+    const {'1': 'height', '3': 3, '4': 1, '5': 5, '10': 'height'},
   ],
   '3': const [Row_CellByFieldIdEntry$json],
 };
@@ -182,7 +185,7 @@ const Row_CellByFieldIdEntry$json = const {
 };
 
 /// Descriptor for `Row`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List rowDescriptor = $convert.base64Decode('CgNSb3cSDgoCaWQYASABKAlSAmlkEkAKEGNlbGxfYnlfZmllbGRfaWQYAiADKAsyFy5Sb3cuQ2VsbEJ5RmllbGRJZEVudHJ5Ug1jZWxsQnlGaWVsZElkGkcKEkNlbGxCeUZpZWxkSWRFbnRyeRIQCgNrZXkYASABKAlSA2tleRIbCgV2YWx1ZRgCIAEoCzIFLkNlbGxSBXZhbHVlOgI4AQ==');
+final $typed_data.Uint8List rowDescriptor = $convert.base64Decode('CgNSb3cSDgoCaWQYASABKAlSAmlkEkAKEGNlbGxfYnlfZmllbGRfaWQYAiADKAsyFy5Sb3cuQ2VsbEJ5RmllbGRJZEVudHJ5Ug1jZWxsQnlGaWVsZElkEhYKBmhlaWdodBgDIAEoBVIGaGVpZ2h0GkcKEkNlbGxCeUZpZWxkSWRFbnRyeRIQCgNrZXkYASABKAlSA2tleRIbCgV2YWx1ZRgCIAEoCzIFLkNlbGxSBXZhbHVlOgI4AQ==');
 @$core.Deprecated('Use cellDescriptor instead')
 const Cell$json = const {
   '1': 'Cell',

+ 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 cdylib
-crate-type = ["cdylib"]
+# default staticlib
+crate-type = ["staticlib"]
 
 
 [dependencies]

+ 3 - 25
frontend/rust-lib/flowy-grid/src/services/grid_builder.rs

@@ -3,7 +3,6 @@ use flowy_collaboration::client_grid::make_grid_delta;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{Field, FieldOrder, FieldType, Grid, RawCell, RawRow, RowOrder};
 use lib_infra::uuid;
-use std::collections::HashMap;
 use std::sync::Arc;
 
 pub struct GridBuilder {
@@ -24,40 +23,19 @@ impl GridBuilder {
     }
 
     pub fn add_field(mut self, name: &str, desc: &str, field_type: FieldType) -> Self {
-        let field = Field {
-            id: uuid(),
-            name: name.to_string(),
-            desc: desc.to_string(),
-            field_type,
-            frozen: false,
-            width: 100,
-            type_options: Default::default(),
-        };
+        let field = Field::new(&uuid(), name, desc, field_type);
         self.fields.push(field);
         self
     }
 
     pub fn add_empty_row(mut self) -> Self {
-        let row = RawRow {
-            id: uuid(),
-            grid_id: self.grid_id.clone(),
-            cell_by_field_id: Default::default(),
-        };
+        let row = RawRow::new(&uuid(), &self.grid_id, vec![]);
         self.rows.push(row);
         self
     }
 
     pub fn add_row(mut self, cells: Vec<RawCell>) -> Self {
-        let cell_by_field_id = cells
-            .into_iter()
-            .map(|cell| (cell.id.clone(), cell))
-            .collect::<HashMap<String, RawCell>>();
-
-        let row = RawRow {
-            id: uuid(),
-            grid_id: self.grid_id.clone(),
-            cell_by_field_id,
-        };
+        let row = RawRow::new(&uuid(), &self.grid_id, cells);
         self.rows.push(row);
         self
     }

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

@@ -56,11 +56,7 @@ impl ClientGridEditor {
     }
 
     pub async fn create_empty_row(&self) -> FlowyResult<()> {
-        let row = RawRow {
-            id: uuid(),
-            grid_id: self.grid_id.clone(),
-            cell_by_field_id: Default::default(),
-        };
+        let row = RawRow::new(&uuid(), &self.grid_id, vec![]);
         self.create_row(row).await?;
         Ok(())
     }
@@ -121,7 +117,11 @@ impl ClientGridEditor {
         let rows = raw_rows
             .into_par_iter()
             .map(|raw_row| {
-                let mut row = Row::new(&raw_row.id);
+                let mut row = Row {
+                    id: raw_row.id.clone(),
+                    cell_by_field_id: Default::default(),
+                    height: raw_row.height,
+                };
                 row.cell_by_field_id = raw_row
                     .cell_by_field_id
                     .into_par_iter()

+ 41 - 8
shared-lib/flowy-grid-data-model/src/entities/grid.rs

@@ -3,6 +3,9 @@ use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
 use strum_macros::{Display, EnumIter, EnumString};
 
+pub const DEFAULT_ROW_HEIGHT: i32 = 36;
+pub const DEFAULT_FIELD_WIDTH: i32 = 150;
+
 pub trait GridIdentifiable {
     fn id(&self) -> &str;
 }
@@ -90,6 +93,20 @@ pub struct Field {
     pub type_options: AnyData,
 }
 
+impl Field {
+    pub fn new(id: &str, name: &str, desc: &str, field_type: FieldType) -> Self {
+        Self {
+            id: id.to_owned(),
+            name: name.to_string(),
+            desc: desc.to_string(),
+            field_type,
+            frozen: false,
+            width: DEFAULT_FIELD_WIDTH,
+            type_options: Default::default(),
+        }
+    }
+}
+
 impl GridIdentifiable for Field {
     fn id(&self) -> &str {
         &self.id
@@ -245,6 +262,25 @@ pub struct RawRow {
 
     #[pb(index = 3)]
     pub cell_by_field_id: HashMap<String, RawCell>,
+
+    #[pb(index = 4)]
+    pub height: i32,
+}
+
+impl RawRow {
+    pub fn new(id: &str, grid_id: &str, cells: Vec<RawCell>) -> Self {
+        let cell_by_field_id = cells
+            .into_iter()
+            .map(|cell| (cell.id.clone(), cell))
+            .collect::<HashMap<String, RawCell>>();
+
+        Self {
+            id: id.to_owned(),
+            grid_id: grid_id.to_owned(),
+            cell_by_field_id,
+            height: DEFAULT_ROW_HEIGHT,
+        }
+    }
 }
 
 impl GridIdentifiable for RawRow {
@@ -266,6 +302,9 @@ pub struct RawCell {
 
     #[pb(index = 4)]
     pub data: AnyData,
+
+    #[pb(index = 5)]
+    pub height: i32,
 }
 
 #[derive(Debug, Default, ProtoBuf)]
@@ -300,15 +339,9 @@ pub struct Row {
 
     #[pb(index = 2)]
     pub cell_by_field_id: HashMap<String, Cell>,
-}
 
-impl Row {
-    pub fn new(row_id: &str) -> Self {
-        Self {
-            id: row_id.to_owned(),
-            cell_by_field_id: HashMap::new(),
-        }
-    }
+    #[pb(index = 3)]
+    pub height: i32,
 }
 
 #[derive(Debug, Default, ProtoBuf)]

+ 134 - 27
shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs

@@ -1832,6 +1832,7 @@ pub struct RawRow {
     pub id: ::std::string::String,
     pub grid_id: ::std::string::String,
     pub cell_by_field_id: ::std::collections::HashMap<::std::string::String, RawCell>,
+    pub height: i32,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -1924,6 +1925,21 @@ impl RawRow {
     pub fn take_cell_by_field_id(&mut self) -> ::std::collections::HashMap<::std::string::String, RawCell> {
         ::std::mem::replace(&mut self.cell_by_field_id, ::std::collections::HashMap::new())
     }
+
+    // int32 height = 4;
+
+
+    pub fn get_height(&self) -> i32 {
+        self.height
+    }
+    pub fn clear_height(&mut self) {
+        self.height = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_height(&mut self, v: i32) {
+        self.height = v;
+    }
 }
 
 impl ::protobuf::Message for RawRow {
@@ -1944,6 +1960,13 @@ impl ::protobuf::Message for RawRow {
                 3 => {
                     ::protobuf::rt::read_map_into::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeMessage<RawCell>>(wire_type, is, &mut self.cell_by_field_id)?;
                 },
+                4 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int32()?;
+                    self.height = tmp;
+                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -1963,6 +1986,9 @@ impl ::protobuf::Message for RawRow {
             my_size += ::protobuf::rt::string_size(2, &self.grid_id);
         }
         my_size += ::protobuf::rt::compute_map_size::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeMessage<RawCell>>(3, &self.cell_by_field_id);
+        if self.height != 0 {
+            my_size += ::protobuf::rt::value_size(4, self.height, ::protobuf::wire_format::WireTypeVarint);
+        }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
         my_size
@@ -1976,6 +2002,9 @@ impl ::protobuf::Message for RawRow {
             os.write_string(2, &self.grid_id)?;
         }
         ::protobuf::rt::write_map_with_cached_sizes::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeMessage<RawCell>>(3, &self.cell_by_field_id, os)?;
+        if self.height != 0 {
+            os.write_int32(4, self.height)?;
+        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -2029,6 +2058,11 @@ impl ::protobuf::Message for RawRow {
                 |m: &RawRow| { &m.cell_by_field_id },
                 |m: &mut RawRow| { &mut m.cell_by_field_id },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt32>(
+                "height",
+                |m: &RawRow| { &m.height },
+                |m: &mut RawRow| { &mut m.height },
+            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<RawRow>(
                 "RawRow",
                 fields,
@@ -2048,6 +2082,7 @@ impl ::protobuf::Clear for RawRow {
         self.id.clear();
         self.grid_id.clear();
         self.cell_by_field_id.clear();
+        self.height = 0;
         self.unknown_fields.clear();
     }
 }
@@ -2071,6 +2106,7 @@ pub struct RawCell {
     pub row_id: ::std::string::String,
     pub field_id: ::std::string::String,
     pub data: ::protobuf::SingularPtrField<AnyData>,
+    pub height: i32,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -2197,6 +2233,21 @@ impl RawCell {
     pub fn take_data(&mut self) -> AnyData {
         self.data.take().unwrap_or_else(|| AnyData::new())
     }
+
+    // int32 height = 5;
+
+
+    pub fn get_height(&self) -> i32 {
+        self.height
+    }
+    pub fn clear_height(&mut self) {
+        self.height = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_height(&mut self, v: i32) {
+        self.height = v;
+    }
 }
 
 impl ::protobuf::Message for RawCell {
@@ -2225,6 +2276,13 @@ impl ::protobuf::Message for RawCell {
                 4 => {
                     ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.data)?;
                 },
+                5 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int32()?;
+                    self.height = tmp;
+                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -2250,6 +2308,9 @@ impl ::protobuf::Message for RawCell {
             let len = v.compute_size();
             my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
         }
+        if self.height != 0 {
+            my_size += ::protobuf::rt::value_size(5, self.height, ::protobuf::wire_format::WireTypeVarint);
+        }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
         my_size
@@ -2270,6 +2331,9 @@ impl ::protobuf::Message for RawCell {
             os.write_raw_varint32(v.get_cached_size())?;
             v.write_to_with_cached_sizes(os)?;
         }
+        if self.height != 0 {
+            os.write_int32(5, self.height)?;
+        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -2328,6 +2392,11 @@ impl ::protobuf::Message for RawCell {
                 |m: &RawCell| { &m.data },
                 |m: &mut RawCell| { &mut m.data },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt32>(
+                "height",
+                |m: &RawCell| { &m.height },
+                |m: &mut RawCell| { &mut m.height },
+            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<RawCell>(
                 "RawCell",
                 fields,
@@ -2348,6 +2417,7 @@ impl ::protobuf::Clear for RawCell {
         self.row_id.clear();
         self.field_id.clear();
         self.data.clear();
+        self.height = 0;
         self.unknown_fields.clear();
     }
 }
@@ -2535,6 +2605,7 @@ pub struct Row {
     // message fields
     pub id: ::std::string::String,
     pub cell_by_field_id: ::std::collections::HashMap<::std::string::String, Cell>,
+    pub height: i32,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -2601,6 +2672,21 @@ impl Row {
     pub fn take_cell_by_field_id(&mut self) -> ::std::collections::HashMap<::std::string::String, Cell> {
         ::std::mem::replace(&mut self.cell_by_field_id, ::std::collections::HashMap::new())
     }
+
+    // int32 height = 3;
+
+
+    pub fn get_height(&self) -> i32 {
+        self.height
+    }
+    pub fn clear_height(&mut self) {
+        self.height = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_height(&mut self, v: i32) {
+        self.height = v;
+    }
 }
 
 impl ::protobuf::Message for Row {
@@ -2618,6 +2704,13 @@ impl ::protobuf::Message for Row {
                 2 => {
                     ::protobuf::rt::read_map_into::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeMessage<Cell>>(wire_type, is, &mut self.cell_by_field_id)?;
                 },
+                3 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int32()?;
+                    self.height = tmp;
+                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -2634,6 +2727,9 @@ impl ::protobuf::Message for Row {
             my_size += ::protobuf::rt::string_size(1, &self.id);
         }
         my_size += ::protobuf::rt::compute_map_size::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeMessage<Cell>>(2, &self.cell_by_field_id);
+        if self.height != 0 {
+            my_size += ::protobuf::rt::value_size(3, self.height, ::protobuf::wire_format::WireTypeVarint);
+        }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
         my_size
@@ -2644,6 +2740,9 @@ impl ::protobuf::Message for Row {
             os.write_string(1, &self.id)?;
         }
         ::protobuf::rt::write_map_with_cached_sizes::<::protobuf::types::ProtobufTypeString, ::protobuf::types::ProtobufTypeMessage<Cell>>(2, &self.cell_by_field_id, os)?;
+        if self.height != 0 {
+            os.write_int32(3, self.height)?;
+        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -2692,6 +2791,11 @@ impl ::protobuf::Message for Row {
                 |m: &Row| { &m.cell_by_field_id },
                 |m: &mut Row| { &mut m.cell_by_field_id },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt32>(
+                "height",
+                |m: &Row| { &m.height },
+                |m: &mut Row| { &mut m.height },
+            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<Row>(
                 "Row",
                 fields,
@@ -2710,6 +2814,7 @@ impl ::protobuf::Clear for Row {
     fn clear(&mut self) {
         self.id.clear();
         self.cell_by_field_id.clear();
+        self.height = 0;
         self.unknown_fields.clear();
     }
 }
@@ -4085,35 +4190,37 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x15\n\x06row_id\x18\x02\
     \x20\x01(\tR\x05rowId\x12\x1e\n\nvisibility\x18\x03\x20\x01(\x08R\nvisib\
     ility\"3\n\x10RepeatedRowOrder\x12\x1f\n\x05items\x18\x01\x20\x03(\x0b2\
-    \t.RowOrderR\x05items\"\xc2\x01\n\x06RawRow\x12\x0e\n\x02id\x18\x01\x20\
+    \t.RowOrderR\x05items\"\xda\x01\n\x06RawRow\x12\x0e\n\x02id\x18\x01\x20\
     \x01(\tR\x02id\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\x06gridId\x12C\n\
     \x10cell_by_field_id\x18\x03\x20\x03(\x0b2\x1a.RawRow.CellByFieldIdEntry\
-    R\rcellByFieldId\x1aJ\n\x12CellByFieldIdEntry\x12\x10\n\x03key\x18\x01\
-    \x20\x01(\tR\x03key\x12\x1e\n\x05value\x18\x02\x20\x01(\x0b2\x08.RawCell\
-    R\x05value:\x028\x01\"i\n\x07RawCell\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\
-    \x02id\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08fie\
-    ld_id\x18\x03\x20\x01(\tR\x07fieldId\x12\x1c\n\x04data\x18\x04\x20\x01(\
-    \x0b2\x08.AnyDataR\x04data\")\n\x0bRepeatedRow\x12\x1a\n\x05items\x18\
-    \x01\x20\x03(\x0b2\x04.RowR\x05items\"\xa0\x01\n\x03Row\x12\x0e\n\x02id\
-    \x18\x01\x20\x01(\tR\x02id\x12@\n\x10cell_by_field_id\x18\x02\x20\x03(\
-    \x0b2\x17.Row.CellByFieldIdEntryR\rcellByFieldId\x1aG\n\x12CellByFieldId\
-    Entry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x1b\n\x05value\x18\
-    \x02\x20\x01(\x0b2\x05.CellR\x05value:\x028\x01\"K\n\x04Cell\x12\x0e\n\
-    \x02id\x18\x01\x20\x01(\tR\x02id\x12\x19\n\x08field_id\x18\x02\x20\x01(\
-    \tR\x07fieldId\x12\x18\n\x07content\x18\x03\x20\x01(\tR\x07content\"e\n\
-    \rCellChangeset\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x15\n\x06r\
-    ow_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08field_id\x18\x03\x20\x01\
-    (\tR\x07fieldId\x12\x12\n\x04data\x18\x04\x20\x01(\tR\x04data\"'\n\x11Cr\
-    eateGridPayload\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\
-    \x06GridId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"d\n\x11Query\
-    FieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x126\n\
-    \x0cfield_orders\x18\x02\x20\x01(\x0b2\x13.RepeatedFieldOrderR\x0bfieldO\
-    rders\"\\\n\x0fQueryRowPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
-    \x06gridId\x120\n\nrow_orders\x18\x02\x20\x01(\x0b2\x11.RepeatedRowOrder\
-    R\trowOrders*d\n\tFieldType\x12\x0c\n\x08RichText\x10\0\x12\n\n\x06Numbe\
-    r\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\x06\
-    proto3\
+    R\rcellByFieldId\x12\x16\n\x06height\x18\x04\x20\x01(\x05R\x06height\x1a\
+    J\n\x12CellByFieldIdEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\
+    \x12\x1e\n\x05value\x18\x02\x20\x01(\x0b2\x08.RawCellR\x05value:\x028\
+    \x01\"\x81\x01\n\x07RawCell\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\
+    \x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08field_id\
+    \x18\x03\x20\x01(\tR\x07fieldId\x12\x1c\n\x04data\x18\x04\x20\x01(\x0b2\
+    \x08.AnyDataR\x04data\x12\x16\n\x06height\x18\x05\x20\x01(\x05R\x06heigh\
+    t\")\n\x0bRepeatedRow\x12\x1a\n\x05items\x18\x01\x20\x03(\x0b2\x04.RowR\
+    \x05items\"\xb8\x01\n\x03Row\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\
+    \x12@\n\x10cell_by_field_id\x18\x02\x20\x03(\x0b2\x17.Row.CellByFieldIdE\
+    ntryR\rcellByFieldId\x12\x16\n\x06height\x18\x03\x20\x01(\x05R\x06height\
+    \x1aG\n\x12CellByFieldIdEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03ke\
+    y\x12\x1b\n\x05value\x18\x02\x20\x01(\x0b2\x05.CellR\x05value:\x028\x01\
+    \"K\n\x04Cell\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x19\n\x08fie\
+    ld_id\x18\x02\x20\x01(\tR\x07fieldId\x12\x18\n\x07content\x18\x03\x20\
+    \x01(\tR\x07content\"e\n\rCellChangeset\x12\x0e\n\x02id\x18\x01\x20\x01(\
+    \tR\x02id\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08\
+    field_id\x18\x03\x20\x01(\tR\x07fieldId\x12\x12\n\x04data\x18\x04\x20\
+    \x01(\tR\x04data\"'\n\x11CreateGridPayload\x12\x12\n\x04name\x18\x01\x20\
+    \x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\x05value\x18\x01\x20\x01(\t\
+    R\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.Repe\
+    atedFieldOrderR\x0bfieldOrders\"\\\n\x0fQueryRowPayload\x12\x17\n\x07gri\
+    d_id\x18\x01\x20\x01(\tR\x06gridId\x120\n\nrow_orders\x18\x02\x20\x01(\
+    \x0b2\x11.RepeatedRowOrderR\trowOrders*d\n\tFieldType\x12\x0c\n\x08RichT\
+    ext\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\
+    \x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\
+    \n\x08Checkbox\x10\x05b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -40,12 +40,14 @@ message RawRow {
     string id = 1;
     string grid_id = 2;
     map<string, RawCell> cell_by_field_id = 3;
+    int32 height = 4;
 }
 message RawCell {
     string id = 1;
     string row_id = 2;
     string field_id = 3;
     AnyData data = 4;
+    int32 height = 5;
 }
 message RepeatedRow {
     repeated Row items = 1;
@@ -53,6 +55,7 @@ message RepeatedRow {
 message Row {
     string id = 1;
     map<string, Cell> cell_by_field_id = 2;
+    int32 height = 3;
 }
 message Cell {
     string id = 1;