Bläddra i källkod

feat: config grid bloc

appflowy 3 år sedan
förälder
incheckning
477fa5f4f6
41 ändrade filer med 4051 tillägg och 774 borttagningar
  1. 47 35
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  2. 9 1
      frontend/app_flowy/lib/workspace/application/grid/grid_service.dart
  3. 3 7
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/grid_layout.dart
  4. 5 5
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/grid_page.dart
  5. 3 3
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/widgets/grid_content/cell_builder.dart
  6. 1 2
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/widgets/grid_content/cell_container.dart
  7. 40 8
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/widgets/grid_content/grid_cell.dart
  8. 9 11
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/widgets/grid_content/grid_row.dart
  9. 60 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/widgets/grid_header/header.dart
  10. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/widgets/grid_header/header_cell.dart
  11. 51 0
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart
  12. 148 176
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
  13. 40 41
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
  14. 458 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_data.pb.dart
  15. 62 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_data.pbenum.dart
  16. 125 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_data.pbjson.dart
  17. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_data.pbserver.dart
  18. 6 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart
  19. 4 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart
  20. 1 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/protobuf.dart
  21. 46 1
      frontend/rust-lib/Cargo.lock
  22. 6 0
      frontend/rust-lib/flowy-grid/Cargo.toml
  23. 1 1
      frontend/rust-lib/flowy-grid/Flowy.toml
  24. 469 0
      frontend/rust-lib/flowy-grid/src/cell_service/cell_data.rs
  25. 5 0
      frontend/rust-lib/flowy-grid/src/cell_service/mod.rs
  26. 30 0
      frontend/rust-lib/flowy-grid/src/cell_service/stringify.rs
  27. 129 0
      frontend/rust-lib/flowy-grid/src/cell_service/util.rs
  28. 35 3
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  29. 13 2
      frontend/rust-lib/flowy-grid/src/event_map.rs
  30. 4 0
      frontend/rust-lib/flowy-grid/src/lib.rs
  31. 64 0
      frontend/rust-lib/flowy-grid/src/macros.rs
  32. 1655 0
      frontend/rust-lib/flowy-grid/src/protobuf/model/cell_data.rs
  33. 12 2
      frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs
  34. 3 0
      frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs
  35. 47 0
      frontend/rust-lib/flowy-grid/src/protobuf/proto/cell_data.proto
  36. 3 0
      frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto
  37. 2 0
      shared-lib/Cargo.lock
  38. 2 0
      shared-lib/flowy-grid-data-model/Cargo.toml
  39. 65 25
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  40. 365 435
      shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
  41. 13 14
      shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto

+ 47 - 35
frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart

@@ -14,14 +14,17 @@ part 'grid_bloc.freezed.dart';
 class GridBloc extends Bloc<GridEvent, GridState> {
   final GridService service;
   final View view;
-  late Grid _grid;
+  late Grid? _grid;
+  late List<Field>? _fields;
 
   GridBloc({required this.view, required this.service}) : super(GridState.initial()) {
     on<GridEvent>(
       (event, emit) async {
         await event.map(
           initial: (Initial value) async {
-            await _initial(value, emit);
+            await _loadGrid(emit);
+            await _loadFields(emit);
+            await _loadGridInfo(emit);
           },
           createRow: (_CreateRow value) {
             service.createRow(gridId: view.id);
@@ -39,12 +42,11 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     return super.close();
   }
 
-  Future<void> _initial(Initial value, Emitter<GridState> emit) async {
+  Future<void> _loadGrid(Emitter<GridState> emit) async {
     final result = await service.openGrid(gridId: view.id);
     result.fold(
       (grid) {
         _grid = grid;
-        _loadGridInfo(emit);
       },
       (err) {
         emit(state.copyWith(loadingState: GridLoadingState.finish(right(err))));
@@ -52,10 +54,36 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     );
   }
 
+  Future<void> _loadFields(Emitter<GridState> emit) async {
+    if (_grid != null) {
+      final result = await service.getFields(fieldOrders: _grid!.fieldOrders);
+      result.fold(
+        (fields) {
+          _fields = fields.items;
+        },
+        (err) {
+          emit(state.copyWith(loadingState: GridLoadingState.finish(right(err))));
+        },
+      );
+    }
+  }
+
   Future<void> _loadGridInfo(Emitter<GridState> emit) async {
-    emit(
-      state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
-    );
+    if (_grid != null && _fields != null) {
+      final result = await service.getRows(rowOrders: _grid!.rowOrders);
+      result.fold((repeatedRow) {
+        final rows = repeatedRow.items;
+        final gridInfo = GridInfo(rows: rows, fields: _fields!);
+
+        emit(
+          state.copyWith(loadingState: GridLoadingState.finish(left(unit)), gridInfo: some(left(gridInfo))),
+        );
+      }, (err) {
+        emit(
+          state.copyWith(loadingState: GridLoadingState.finish(right(err)), gridInfo: none()),
+        );
+      });
+    }
   }
 }
 
@@ -87,49 +115,33 @@ class GridLoadingState with _$GridLoadingState {
   const factory GridLoadingState.finish(Either<Unit, FlowyError> successOrFail) = _Finish;
 }
 
-typedef FieldById = Map<String, Field>;
-typedef RowById = Map<String, Row>;
-typedef CellById = Map<String, DisplayCell>;
-
 class GridInfo {
-  List<RowOrder> rowOrders;
-  List<FieldOrder> fieldOrders;
-  RowById rowMap;
-  FieldById fieldMap;
+  List<GridRow> rows;
+  List<Field> fields;
 
   GridInfo({
-    required this.rowOrders,
-    required this.fieldOrders,
-    required this.fieldMap,
-    required this.rowMap,
+    required this.rows,
+    required this.fields,
   });
 
   RowInfo rowInfoAtIndex(int index) {
-    final rowOrder = rowOrders[index];
-    final Row row = rowMap[rowOrder.rowId]!;
-    final cellMap = row.cellByFieldId;
-
-    final displayCellMap = <String, DisplayCell>{};
-
+    final row = rows[index];
     return RowInfo(
-      fieldOrders: fieldOrders,
-      fieldMap: fieldMap,
-      displayCellMap: displayCellMap,
+      fields: fields,
+      cellMap: row.cellByFieldId,
     );
   }
 
   int numberOfRows() {
-    return rowOrders.length;
+    return rows.length;
   }
 }
 
 class RowInfo {
-  List<FieldOrder> fieldOrders;
-  FieldById fieldMap;
-  CellById displayCellMap;
+  List<Field> fields;
+  Map<String, GridCell> cellMap;
   RowInfo({
-    required this.fieldOrders,
-    required this.fieldMap,
-    required this.displayCellMap,
+    required this.fields,
+    required this.cellMap,
   });
 }

+ 9 - 1
frontend/app_flowy/lib/workspace/application/grid/grid_service.dart

@@ -15,6 +15,14 @@ class GridService {
   }
 
   Future<Either<void, FlowyError>> createRow({required String gridId}) {
-    throw UnimplementedError();
+    return GridEventCreateRow(GridId(value: gridId)).send();
+  }
+
+  Future<Either<RepeatedRow, FlowyError>> getRows({required RepeatedRowOrder rowOrders}) {
+    return GridEventGetRows(rowOrders).send();
+  }
+
+  Future<Either<RepeatedField, FlowyError>> getFields({required RepeatedFieldOrder fieldOrders}) {
+    return GridEventGetFields(fieldOrders).send();
   }
 }

+ 3 - 7
frontend/app_flowy/lib/workspace/presentation/plugins/grid/grid_layout.dart

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

+ 5 - 5
frontend/app_flowy/lib/workspace/presentation/plugins/grid/grid_page.dart

@@ -101,12 +101,12 @@ class _GridBodyState extends State<GridBody> {
           controller: _scrollController.horizontalController,
           axis: Axis.horizontal,
           child: SizedBox(
-            width: GridLayout.headerWidth(gridInfo.fieldOrders),
+            width: GridLayout.headerWidth(gridInfo.fields),
             child: CustomScrollView(
               physics: StyledScrollPhysics(),
               controller: _scrollController.verticalController,
               slivers: <Widget>[
-                _buildHeader(gridInfo.fieldOrders, gridInfo.fieldMap),
+                _buildHeader(gridInfo.fields),
                 _buildRows(gridInfo),
                 _builderFooter(context),
               ],
@@ -123,9 +123,9 @@ class _GridBodyState extends State<GridBody> {
     );
   }
 
-  Widget _buildHeader(List<FieldOrder> fieldOrders, FieldById fieldById) {
+  Widget _buildHeader(List<Field> fields) {
     return SliverPersistentHeader(
-      delegate: GridHeaderDelegate(fieldOrders, fieldById),
+      delegate: GridHeaderDelegate(fields),
       floating: true,
       pinned: true,
     );
@@ -135,7 +135,7 @@ class _GridBodyState extends State<GridBody> {
     return SliverList(
       delegate: SliverChildBuilderDelegate((context, index) {
         final rowInfo = gridInfo.rowInfoAtIndex(index);
-        return RepaintBoundary(child: GridRow(rowInfo));
+        return RepaintBoundary(child: GridRowWidget(rowInfo));
       }, childCount: gridInfo.numberOfRows()),
     );
   }

+ 3 - 3
frontend/app_flowy/lib/workspace/presentation/plugins/grid/widgets/grid_content/cell_builder.dart

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

+ 1 - 2
frontend/app_flowy/lib/workspace/presentation/plugins/grid/widgets/grid_content/cell_container.dart

@@ -1,12 +1,11 @@
 import 'package:app_flowy/workspace/presentation/plugins/grid/grid_sizes.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';
 import 'grid_cell.dart';
 
 class CellContainer extends StatelessWidget {
-  final GridCell child;
+  final GridCellWidget child;
   final double width;
   const CellContainer({Key? key, required this.child, required this.width}) : super(key: key);
 

+ 40 - 8
frontend/app_flowy/lib/workspace/presentation/plugins/grid/widgets/grid_content/grid_cell.dart

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

+ 9 - 11
frontend/app_flowy/lib/workspace/presentation/plugins/grid/widgets/grid_content/grid_row.dart

@@ -1,22 +1,22 @@
 import 'package:app_flowy/workspace/application/grid/grid_bloc.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/grid_sizes.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row;
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter/material.dart';
 import 'cell_builder.dart';
 import 'cell_container.dart';
-import 'grid_row_leading.dart';
+import 'grid_cell.dart';
 
 class GridRowContext {
   final RepeatedFieldOrder fieldOrders;
   final Map<String, Field> fieldById;
-  final Map<String, DisplayCell> cellByFieldId;
+  final Map<String, GridCell> cellByFieldId;
   GridRowContext(this.fieldOrders, this.fieldById, this.cellByFieldId);
 }
 
-class GridRow extends StatelessWidget {
+class GridRowWidget extends StatelessWidget {
   final RowInfo rowInfo;
   final Function(bool)? onHoverChange;
-  const GridRow(this.rowInfo, {Key? key, this.onHoverChange}) : super(key: key);
+  const GridRowWidget(this.rowInfo, {Key? key, this.onHoverChange}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -48,17 +48,15 @@ class GridRow extends StatelessWidget {
     var cells = List<Widget>.empty(growable: true);
     cells.add(const RowLeading());
 
-    rowInfo.fieldOrders.where((element) => element.visibility).forEach((fieldOrder) {
-      final field = rowInfo.fieldMap[fieldOrder.fieldId];
-      final data = rowInfo.displayCellMap[fieldOrder.fieldId];
-
+    for (var field in rowInfo.fields) {
+      final data = rowInfo.cellMap[field.id];
       final cell = CellContainer(
-        width: fieldOrder.width.toDouble(),
+        width: field.width.toDouble(),
         child: GridCellBuilder.buildCell(field, data),
       );
 
       cells.add(cell);
-    });
+    }
     return cells;
   }
 }

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

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

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

@@ -1,5 +1,5 @@
 import 'package:app_flowy/workspace/presentation/plugins/grid/grid_sizes.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row;
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter/material.dart';
 import 'constants.dart';
 

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

@@ -35,3 +35,54 @@ class GridEventOpenGrid {
     }
 }
 
+class GridEventGetRows {
+     RepeatedRowOrder request;
+     GridEventGetRows(this.request);
+
+    Future<Either<RepeatedRow, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.GetRows.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(RepeatedRow.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
+class GridEventGetFields {
+     RepeatedFieldOrder request;
+     GridEventGetFields(this.request);
+
+    Future<Either<RepeatedField, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.GetFields.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(RepeatedField.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
+class GridEventCreateRow {
+     GridId request;
+     GridEventCreateRow(this.request);
+
+    Future<Either<Unit, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.CreateRow.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+

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

@@ -229,7 +229,6 @@ class FieldOrder extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'FieldOrder', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
     ..aOB(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'visibility')
-    ..a<$core.int>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'width', $pb.PbFieldType.O3)
     ..hasRequiredFields = false
   ;
 
@@ -237,7 +236,6 @@ class FieldOrder extends $pb.GeneratedMessage {
   factory FieldOrder({
     $core.String? fieldId,
     $core.bool? visibility,
-    $core.int? width,
   }) {
     final _result = create();
     if (fieldId != null) {
@@ -246,9 +244,6 @@ class FieldOrder extends $pb.GeneratedMessage {
     if (visibility != null) {
       _result.visibility = visibility;
     }
-    if (width != null) {
-      _result.width = width;
-    }
     return _result;
   }
   factory FieldOrder.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -289,15 +284,6 @@ class FieldOrder extends $pb.GeneratedMessage {
   $core.bool hasVisibility() => $_has(1);
   @$pb.TagNumber(2)
   void clearVisibility() => clearField(2);
-
-  @$pb.TagNumber(3)
-  $core.int get width => $_getIZ(2);
-  @$pb.TagNumber(3)
-  set width($core.int v) { $_setSignedInt32(2, v); }
-  @$pb.TagNumber(3)
-  $core.bool hasWidth() => $_has(2);
-  @$pb.TagNumber(3)
-  void clearWidth() => clearField(3);
 }
 
 class RepeatedFieldOrder extends $pb.GeneratedMessage {
@@ -348,7 +334,8 @@ class Field extends $pb.GeneratedMessage {
     ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
     ..e<FieldType>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldType', $pb.PbFieldType.OE, defaultOrMaker: FieldType.RichText, valueOf: FieldType.valueOf, enumValues: FieldType.values)
     ..aOB(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'frozen')
-    ..aOM<AnyData>(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptions', subBuilder: AnyData.create)
+    ..a<$core.int>(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'width', $pb.PbFieldType.O3)
+    ..aOM<AnyData>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptions', subBuilder: AnyData.create)
     ..hasRequiredFields = false
   ;
 
@@ -359,6 +346,7 @@ class Field extends $pb.GeneratedMessage {
     $core.String? desc,
     FieldType? fieldType,
     $core.bool? frozen,
+    $core.int? width,
     AnyData? typeOptions,
   }) {
     final _result = create();
@@ -377,6 +365,9 @@ class Field extends $pb.GeneratedMessage {
     if (frozen != null) {
       _result.frozen = frozen;
     }
+    if (width != null) {
+      _result.width = width;
+    }
     if (typeOptions != null) {
       _result.typeOptions = typeOptions;
     }
@@ -449,32 +440,82 @@ class Field extends $pb.GeneratedMessage {
   void clearFrozen() => clearField(5);
 
   @$pb.TagNumber(6)
-  AnyData get typeOptions => $_getN(5);
+  $core.int get width => $_getIZ(5);
   @$pb.TagNumber(6)
-  set typeOptions(AnyData v) { setField(6, v); }
+  set width($core.int v) { $_setSignedInt32(5, v); }
   @$pb.TagNumber(6)
-  $core.bool hasTypeOptions() => $_has(5);
+  $core.bool hasWidth() => $_has(5);
   @$pb.TagNumber(6)
-  void clearTypeOptions() => clearField(6);
-  @$pb.TagNumber(6)
-  AnyData ensureTypeOptions() => $_ensure(5);
+  void clearWidth() => clearField(6);
+
+  @$pb.TagNumber(7)
+  AnyData get typeOptions => $_getN(6);
+  @$pb.TagNumber(7)
+  set typeOptions(AnyData v) { setField(7, v); }
+  @$pb.TagNumber(7)
+  $core.bool hasTypeOptions() => $_has(6);
+  @$pb.TagNumber(7)
+  void clearTypeOptions() => clearField(7);
+  @$pb.TagNumber(7)
+  AnyData ensureTypeOptions() => $_ensure(6);
+}
+
+class RepeatedField extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RepeatedField', createEmptyInstance: create)
+    ..pc<Field>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: Field.create)
+    ..hasRequiredFields = false
+  ;
+
+  RepeatedField._() : super();
+  factory RepeatedField({
+    $core.Iterable<Field>? items,
+  }) {
+    final _result = create();
+    if (items != null) {
+      _result.items.addAll(items);
+    }
+    return _result;
+  }
+  factory RepeatedField.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RepeatedField.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  RepeatedField clone() => RepeatedField()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  RepeatedField copyWith(void Function(RepeatedField) updates) => super.copyWith((message) => updates(message as RepeatedField)) as RepeatedField; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static RepeatedField create() => RepeatedField._();
+  RepeatedField createEmptyInstance() => create();
+  static $pb.PbList<RepeatedField> createRepeated() => $pb.PbList<RepeatedField>();
+  @$core.pragma('dart2js:noInline')
+  static RepeatedField getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RepeatedField>(create);
+  static RepeatedField? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<Field> get items => $_getList(0);
 }
 
 class AnyData extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'AnyData', createEmptyInstance: create)
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeUrl')
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeId')
     ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'value', $pb.PbFieldType.OY)
     ..hasRequiredFields = false
   ;
 
   AnyData._() : super();
   factory AnyData({
-    $core.String? typeUrl,
+    $core.String? typeId,
     $core.List<$core.int>? value,
   }) {
     final _result = create();
-    if (typeUrl != null) {
-      _result.typeUrl = typeUrl;
+    if (typeId != null) {
+      _result.typeId = typeId;
     }
     if (value != null) {
       _result.value = value;
@@ -503,13 +544,13 @@ class AnyData extends $pb.GeneratedMessage {
   static AnyData? _defaultInstance;
 
   @$pb.TagNumber(1)
-  $core.String get typeUrl => $_getSZ(0);
+  $core.String get typeId => $_getSZ(0);
   @$pb.TagNumber(1)
-  set typeUrl($core.String v) { $_setString(0, v); }
+  set typeId($core.String v) { $_setString(0, v); }
   @$pb.TagNumber(1)
-  $core.bool hasTypeUrl() => $_has(0);
+  $core.bool hasTypeId() => $_has(0);
   @$pb.TagNumber(1)
-  void clearTypeUrl() => clearField(1);
+  void clearTypeId() => clearField(1);
 
   @$pb.TagNumber(2)
   $core.List<$core.int> get value => $_getN(1);
@@ -637,21 +678,21 @@ class RepeatedRowOrder extends $pb.GeneratedMessage {
   $core.List<RowOrder> get items => $_getList(0);
 }
 
-class Row extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Row', createEmptyInstance: create)
+class GridRow extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridRow', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
     ..aInt64(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'modifiedTime')
-    ..m<$core.String, Cell>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'cellByFieldId', entryClassName: 'Row.CellByFieldIdEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.OM, valueCreator: Cell.create)
+    ..m<$core.String, GridCell>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'cellByFieldId', entryClassName: 'GridRow.CellByFieldIdEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.OM, valueCreator: GridCell.create)
     ..hasRequiredFields = false
   ;
 
-  Row._() : super();
-  factory Row({
+  GridRow._() : super();
+  factory GridRow({
     $core.String? id,
     $core.String? gridId,
     $fixnum.Int64? modifiedTime,
-    $core.Map<$core.String, Cell>? cellByFieldId,
+    $core.Map<$core.String, GridCell>? cellByFieldId,
   }) {
     final _result = create();
     if (id != null) {
@@ -668,26 +709,26 @@ class Row extends $pb.GeneratedMessage {
     }
     return _result;
   }
-  factory Row.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory Row.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory GridRow.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory GridRow.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
   'Will be removed in next major version')
-  Row clone() => Row()..mergeFromMessage(this);
+  GridRow clone() => GridRow()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  Row copyWith(void Function(Row) updates) => super.copyWith((message) => updates(message as Row)) as Row; // ignore: deprecated_member_use
+  GridRow copyWith(void Function(GridRow) updates) => super.copyWith((message) => updates(message as GridRow)) as GridRow; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static Row create() => Row._();
-  Row createEmptyInstance() => create();
-  static $pb.PbList<Row> createRepeated() => $pb.PbList<Row>();
+  static GridRow create() => GridRow._();
+  GridRow createEmptyInstance() => create();
+  static $pb.PbList<GridRow> createRepeated() => $pb.PbList<GridRow>();
   @$core.pragma('dart2js:noInline')
-  static Row getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Row>(create);
-  static Row? _defaultInstance;
+  static GridRow getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<GridRow>(create);
+  static GridRow? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.String get id => $_getSZ(0);
@@ -717,125 +758,101 @@ class Row extends $pb.GeneratedMessage {
   void clearModifiedTime() => clearField(3);
 
   @$pb.TagNumber(4)
-  $core.Map<$core.String, Cell> get cellByFieldId => $_getMap(3);
+  $core.Map<$core.String, GridCell> get cellByFieldId => $_getMap(3);
 }
 
-class Cell extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Cell', createEmptyInstance: create)
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowId')
-    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
+class RepeatedRow extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RepeatedRow', createEmptyInstance: create)
+    ..pc<GridRow>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: GridRow.create)
     ..hasRequiredFields = false
   ;
 
-  Cell._() : super();
-  factory Cell({
-    $core.String? id,
-    $core.String? rowId,
-    $core.String? fieldId,
+  RepeatedRow._() : super();
+  factory RepeatedRow({
+    $core.Iterable<GridRow>? items,
   }) {
     final _result = create();
-    if (id != null) {
-      _result.id = id;
-    }
-    if (rowId != null) {
-      _result.rowId = rowId;
-    }
-    if (fieldId != null) {
-      _result.fieldId = fieldId;
+    if (items != null) {
+      _result.items.addAll(items);
     }
     return _result;
   }
-  factory Cell.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory Cell.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory RepeatedRow.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RepeatedRow.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
   'Will be removed in next major version')
-  Cell clone() => Cell()..mergeFromMessage(this);
+  RepeatedRow clone() => RepeatedRow()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  Cell copyWith(void Function(Cell) updates) => super.copyWith((message) => updates(message as Cell)) as Cell; // ignore: deprecated_member_use
+  RepeatedRow copyWith(void Function(RepeatedRow) updates) => super.copyWith((message) => updates(message as RepeatedRow)) as RepeatedRow; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static Cell create() => Cell._();
-  Cell createEmptyInstance() => create();
-  static $pb.PbList<Cell> createRepeated() => $pb.PbList<Cell>();
+  static RepeatedRow create() => RepeatedRow._();
+  RepeatedRow createEmptyInstance() => create();
+  static $pb.PbList<RepeatedRow> createRepeated() => $pb.PbList<RepeatedRow>();
   @$core.pragma('dart2js:noInline')
-  static Cell getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Cell>(create);
-  static Cell? _defaultInstance;
+  static RepeatedRow getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RepeatedRow>(create);
+  static RepeatedRow? _defaultInstance;
 
   @$pb.TagNumber(1)
-  $core.String get id => $_getSZ(0);
-  @$pb.TagNumber(1)
-  set id($core.String v) { $_setString(0, v); }
-  @$pb.TagNumber(1)
-  $core.bool hasId() => $_has(0);
-  @$pb.TagNumber(1)
-  void clearId() => clearField(1);
-
-  @$pb.TagNumber(2)
-  $core.String get rowId => $_getSZ(1);
-  @$pb.TagNumber(2)
-  set rowId($core.String v) { $_setString(1, v); }
-  @$pb.TagNumber(2)
-  $core.bool hasRowId() => $_has(1);
-  @$pb.TagNumber(2)
-  void clearRowId() => clearField(2);
-
-  @$pb.TagNumber(3)
-  $core.String get fieldId => $_getSZ(2);
-  @$pb.TagNumber(3)
-  set fieldId($core.String v) { $_setString(2, v); }
-  @$pb.TagNumber(3)
-  $core.bool hasFieldId() => $_has(2);
-  @$pb.TagNumber(3)
-  void clearFieldId() => clearField(3);
+  $core.List<GridRow> get items => $_getList(0);
 }
 
-class DisplayCell extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DisplayCell', createEmptyInstance: create)
+class GridCell extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridCell', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'content')
+    ..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') ? '' : 'content')
     ..hasRequiredFields = false
   ;
 
-  DisplayCell._() : super();
-  factory DisplayCell({
+  GridCell._() : super();
+  factory GridCell({
     $core.String? id,
+    $core.String? rowId,
+    $core.String? fieldId,
     $core.String? content,
   }) {
     final _result = create();
     if (id != null) {
       _result.id = id;
     }
+    if (rowId != null) {
+      _result.rowId = rowId;
+    }
+    if (fieldId != null) {
+      _result.fieldId = fieldId;
+    }
     if (content != null) {
       _result.content = content;
     }
     return _result;
   }
-  factory DisplayCell.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory DisplayCell.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory GridCell.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory GridCell.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')
-  DisplayCell clone() => DisplayCell()..mergeFromMessage(this);
+  GridCell clone() => GridCell()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  DisplayCell copyWith(void Function(DisplayCell) updates) => super.copyWith((message) => updates(message as DisplayCell)) as DisplayCell; // ignore: deprecated_member_use
+  GridCell copyWith(void Function(GridCell) updates) => super.copyWith((message) => updates(message as GridCell)) as GridCell; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static DisplayCell create() => DisplayCell._();
-  DisplayCell createEmptyInstance() => create();
-  static $pb.PbList<DisplayCell> createRepeated() => $pb.PbList<DisplayCell>();
+  static GridCell create() => GridCell._();
+  GridCell createEmptyInstance() => create();
+  static $pb.PbList<GridCell> createRepeated() => $pb.PbList<GridCell>();
   @$core.pragma('dart2js:noInline')
-  static DisplayCell getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DisplayCell>(create);
-  static DisplayCell? _defaultInstance;
+  static GridCell getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<GridCell>(create);
+  static GridCell? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.String get id => $_getSZ(0);
@@ -847,76 +864,31 @@ class DisplayCell extends $pb.GeneratedMessage {
   void clearId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.String get content => $_getSZ(1);
+  $core.String get rowId => $_getSZ(1);
   @$pb.TagNumber(2)
-  set content($core.String v) { $_setString(1, v); }
+  set rowId($core.String v) { $_setString(1, v); }
   @$pb.TagNumber(2)
-  $core.bool hasContent() => $_has(1);
+  $core.bool hasRowId() => $_has(1);
   @$pb.TagNumber(2)
-  void clearContent() => clearField(2);
-}
-
-class RawCell extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RawCell', createEmptyInstance: create)
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
-    ..aOM<AnyData>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', subBuilder: AnyData.create)
-    ..hasRequiredFields = false
-  ;
-
-  RawCell._() : super();
-  factory RawCell({
-    $core.String? id,
-    AnyData? data,
-  }) {
-    final _result = create();
-    if (id != null) {
-      _result.id = id;
-    }
-    if (data != null) {
-      _result.data = data;
-    }
-    return _result;
-  }
-  factory RawCell.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory RawCell.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
-  @$core.Deprecated(
-  'Using this can add significant overhead to your binary. '
-  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
-  'Will be removed in next major version')
-  RawCell clone() => RawCell()..mergeFromMessage(this);
-  @$core.Deprecated(
-  'Using this can add significant overhead to your binary. '
-  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
-  'Will be removed in next major version')
-  RawCell copyWith(void Function(RawCell) updates) => super.copyWith((message) => updates(message as RawCell)) as RawCell; // ignore: deprecated_member_use
-  $pb.BuilderInfo get info_ => _i;
-  @$core.pragma('dart2js:noInline')
-  static RawCell create() => RawCell._();
-  RawCell createEmptyInstance() => create();
-  static $pb.PbList<RawCell> createRepeated() => $pb.PbList<RawCell>();
-  @$core.pragma('dart2js:noInline')
-  static RawCell getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RawCell>(create);
-  static RawCell? _defaultInstance;
+  void clearRowId() => clearField(2);
 
-  @$pb.TagNumber(1)
-  $core.String get id => $_getSZ(0);
-  @$pb.TagNumber(1)
-  set id($core.String v) { $_setString(0, v); }
-  @$pb.TagNumber(1)
-  $core.bool hasId() => $_has(0);
-  @$pb.TagNumber(1)
-  void clearId() => clearField(1);
+  @$pb.TagNumber(3)
+  $core.String get fieldId => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set fieldId($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasFieldId() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearFieldId() => clearField(3);
 
-  @$pb.TagNumber(2)
-  AnyData get data => $_getN(1);
-  @$pb.TagNumber(2)
-  set data(AnyData v) { setField(2, v); }
-  @$pb.TagNumber(2)
-  $core.bool hasData() => $_has(1);
-  @$pb.TagNumber(2)
-  void clearData() => clearField(2);
-  @$pb.TagNumber(2)
-  AnyData ensureData() => $_ensure(1);
+  @$pb.TagNumber(4)
+  $core.String get content => $_getSZ(3);
+  @$pb.TagNumber(4)
+  set content($core.String v) { $_setString(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasContent() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearContent() => clearField(4);
 }
 
 class CreateGridPayload extends $pb.GeneratedMessage {

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

@@ -64,12 +64,11 @@ const FieldOrder$json = const {
   '2': const [
     const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'},
     const {'1': 'visibility', '3': 2, '4': 1, '5': 8, '10': 'visibility'},
-    const {'1': 'width', '3': 3, '4': 1, '5': 5, '10': 'width'},
   ],
 };
 
 /// Descriptor for `FieldOrder`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List fieldOrderDescriptor = $convert.base64Decode('CgpGaWVsZE9yZGVyEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEh4KCnZpc2liaWxpdHkYAiABKAhSCnZpc2liaWxpdHkSFAoFd2lkdGgYAyABKAVSBXdpZHRo');
+final $typed_data.Uint8List fieldOrderDescriptor = $convert.base64Decode('CgpGaWVsZE9yZGVyEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEh4KCnZpc2liaWxpdHkYAiABKAhSCnZpc2liaWxpdHk=');
 @$core.Deprecated('Use repeatedFieldOrderDescriptor instead')
 const RepeatedFieldOrder$json = const {
   '1': 'RepeatedFieldOrder',
@@ -89,23 +88,34 @@ const Field$json = const {
     const {'1': 'desc', '3': 3, '4': 1, '5': 9, '10': 'desc'},
     const {'1': 'field_type', '3': 4, '4': 1, '5': 14, '6': '.FieldType', '10': 'fieldType'},
     const {'1': 'frozen', '3': 5, '4': 1, '5': 8, '10': 'frozen'},
-    const {'1': 'type_options', '3': 6, '4': 1, '5': 11, '6': '.AnyData', '10': 'typeOptions'},
+    const {'1': 'width', '3': 6, '4': 1, '5': 5, '10': 'width'},
+    const {'1': 'type_options', '3': 7, '4': 1, '5': 11, '6': '.AnyData', '10': 'typeOptions'},
   ],
 };
 
 /// Descriptor for `Field`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List fieldDescriptor = $convert.base64Decode('CgVGaWVsZBIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRISCgRkZXNjGAMgASgJUgRkZXNjEikKCmZpZWxkX3R5cGUYBCABKA4yCi5GaWVsZFR5cGVSCWZpZWxkVHlwZRIWCgZmcm96ZW4YBSABKAhSBmZyb3plbhIrCgx0eXBlX29wdGlvbnMYBiABKAsyCC5BbnlEYXRhUgt0eXBlT3B0aW9ucw==');
+final $typed_data.Uint8List fieldDescriptor = $convert.base64Decode('CgVGaWVsZBIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRISCgRkZXNjGAMgASgJUgRkZXNjEikKCmZpZWxkX3R5cGUYBCABKA4yCi5GaWVsZFR5cGVSCWZpZWxkVHlwZRIWCgZmcm96ZW4YBSABKAhSBmZyb3plbhIUCgV3aWR0aBgGIAEoBVIFd2lkdGgSKwoMdHlwZV9vcHRpb25zGAcgASgLMgguQW55RGF0YVILdHlwZU9wdGlvbnM=');
+@$core.Deprecated('Use repeatedFieldDescriptor instead')
+const RepeatedField$json = const {
+  '1': 'RepeatedField',
+  '2': const [
+    const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.Field', '10': 'items'},
+  ],
+};
+
+/// Descriptor for `RepeatedField`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List repeatedFieldDescriptor = $convert.base64Decode('Cg1SZXBlYXRlZEZpZWxkEhwKBWl0ZW1zGAEgAygLMgYuRmllbGRSBWl0ZW1z');
 @$core.Deprecated('Use anyDataDescriptor instead')
 const AnyData$json = const {
   '1': 'AnyData',
   '2': const [
-    const {'1': 'type_url', '3': 1, '4': 1, '5': 9, '10': 'typeUrl'},
+    const {'1': 'type_id', '3': 1, '4': 1, '5': 9, '10': 'typeId'},
     const {'1': 'value', '3': 2, '4': 1, '5': 12, '10': 'value'},
   ],
 };
 
 /// Descriptor for `AnyData`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List anyDataDescriptor = $convert.base64Decode('CgdBbnlEYXRhEhkKCHR5cGVfdXJsGAEgASgJUgd0eXBlVXJsEhQKBXZhbHVlGAIgASgMUgV2YWx1ZQ==');
+final $typed_data.Uint8List anyDataDescriptor = $convert.base64Decode('CgdBbnlEYXRhEhcKB3R5cGVfaWQYASABKAlSBnR5cGVJZBIUCgV2YWx1ZRgCIAEoDFIFdmFsdWU=');
 @$core.Deprecated('Use rowOrderDescriptor instead')
 const RowOrder$json = const {
   '1': 'RowOrder',
@@ -128,64 +138,53 @@ const RepeatedRowOrder$json = const {
 
 /// Descriptor for `RepeatedRowOrder`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List repeatedRowOrderDescriptor = $convert.base64Decode('ChBSZXBlYXRlZFJvd09yZGVyEh8KBWl0ZW1zGAEgAygLMgkuUm93T3JkZXJSBWl0ZW1z');
-@$core.Deprecated('Use rowDescriptor instead')
-const Row$json = const {
-  '1': 'Row',
+@$core.Deprecated('Use gridRowDescriptor instead')
+const GridRow$json = const {
+  '1': 'GridRow',
   '2': const [
     const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
     const {'1': 'grid_id', '3': 2, '4': 1, '5': 9, '10': 'gridId'},
     const {'1': 'modified_time', '3': 3, '4': 1, '5': 3, '10': 'modifiedTime'},
-    const {'1': 'cell_by_field_id', '3': 4, '4': 3, '5': 11, '6': '.Row.CellByFieldIdEntry', '10': 'cellByFieldId'},
+    const {'1': 'cell_by_field_id', '3': 4, '4': 3, '5': 11, '6': '.GridRow.CellByFieldIdEntry', '10': 'cellByFieldId'},
   ],
-  '3': const [Row_CellByFieldIdEntry$json],
+  '3': const [GridRow_CellByFieldIdEntry$json],
 };
 
-@$core.Deprecated('Use rowDescriptor instead')
-const Row_CellByFieldIdEntry$json = const {
+@$core.Deprecated('Use gridRowDescriptor instead')
+const GridRow_CellByFieldIdEntry$json = const {
   '1': 'CellByFieldIdEntry',
   '2': const [
     const {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'},
-    const {'1': 'value', '3': 2, '4': 1, '5': 11, '6': '.Cell', '10': 'value'},
+    const {'1': 'value', '3': 2, '4': 1, '5': 11, '6': '.GridCell', '10': 'value'},
   ],
   '7': const {'7': true},
 };
 
-/// Descriptor for `Row`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List rowDescriptor = $convert.base64Decode('CgNSb3cSDgoCaWQYASABKAlSAmlkEhcKB2dyaWRfaWQYAiABKAlSBmdyaWRJZBIjCg1tb2RpZmllZF90aW1lGAMgASgDUgxtb2RpZmllZFRpbWUSQAoQY2VsbF9ieV9maWVsZF9pZBgEIAMoCzIXLlJvdy5DZWxsQnlGaWVsZElkRW50cnlSDWNlbGxCeUZpZWxkSWQaRwoSQ2VsbEJ5RmllbGRJZEVudHJ5EhAKA2tleRgBIAEoCVIDa2V5EhsKBXZhbHVlGAIgASgLMgUuQ2VsbFIFdmFsdWU6AjgB');
-@$core.Deprecated('Use cellDescriptor instead')
-const Cell$json = const {
-  '1': 'Cell',
-  '2': const [
-    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
-    const {'1': 'row_id', '3': 2, '4': 1, '5': 9, '10': 'rowId'},
-    const {'1': 'field_id', '3': 3, '4': 1, '5': 9, '10': 'fieldId'},
-  ],
-};
-
-/// Descriptor for `Cell`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List cellDescriptor = $convert.base64Decode('CgRDZWxsEg4KAmlkGAEgASgJUgJpZBIVCgZyb3dfaWQYAiABKAlSBXJvd0lkEhkKCGZpZWxkX2lkGAMgASgJUgdmaWVsZElk');
-@$core.Deprecated('Use displayCellDescriptor instead')
-const DisplayCell$json = const {
-  '1': 'DisplayCell',
+/// Descriptor for `GridRow`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List gridRowDescriptor = $convert.base64Decode('CgdHcmlkUm93Eg4KAmlkGAEgASgJUgJpZBIXCgdncmlkX2lkGAIgASgJUgZncmlkSWQSIwoNbW9kaWZpZWRfdGltZRgDIAEoA1IMbW9kaWZpZWRUaW1lEkQKEGNlbGxfYnlfZmllbGRfaWQYBCADKAsyGy5HcmlkUm93LkNlbGxCeUZpZWxkSWRFbnRyeVINY2VsbEJ5RmllbGRJZBpLChJDZWxsQnlGaWVsZElkRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSHwoFdmFsdWUYAiABKAsyCS5HcmlkQ2VsbFIFdmFsdWU6AjgB');
+@$core.Deprecated('Use repeatedRowDescriptor instead')
+const RepeatedRow$json = const {
+  '1': 'RepeatedRow',
   '2': const [
-    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
-    const {'1': 'content', '3': 2, '4': 1, '5': 9, '10': 'content'},
+    const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.GridRow', '10': 'items'},
   ],
 };
 
-/// Descriptor for `DisplayCell`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List displayCellDescriptor = $convert.base64Decode('CgtEaXNwbGF5Q2VsbBIOCgJpZBgBIAEoCVICaWQSGAoHY29udGVudBgCIAEoCVIHY29udGVudA==');
-@$core.Deprecated('Use rawCellDescriptor instead')
-const RawCell$json = const {
-  '1': 'RawCell',
+/// Descriptor for `RepeatedRow`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List repeatedRowDescriptor = $convert.base64Decode('CgtSZXBlYXRlZFJvdxIeCgVpdGVtcxgBIAMoCzIILkdyaWRSb3dSBWl0ZW1z');
+@$core.Deprecated('Use gridCellDescriptor instead')
+const GridCell$json = const {
+  '1': 'GridCell',
   '2': const [
     const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
-    const {'1': 'data', '3': 2, '4': 1, '5': 11, '6': '.AnyData', '10': 'data'},
+    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': 'content', '3': 4, '4': 1, '5': 9, '10': 'content'},
   ],
 };
 
-/// Descriptor for `RawCell`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List rawCellDescriptor = $convert.base64Decode('CgdSYXdDZWxsEg4KAmlkGAEgASgJUgJpZBIcCgRkYXRhGAIgASgLMgguQW55RGF0YVIEZGF0YQ==');
+/// Descriptor for `GridCell`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List gridCellDescriptor = $convert.base64Decode('CghHcmlkQ2VsbBIOCgJpZBgBIAEoCVICaWQSFQoGcm93X2lkGAIgASgJUgVyb3dJZBIZCghmaWVsZF9pZBgDIAEoCVIHZmllbGRJZBIYCgdjb250ZW50GAQgASgJUgdjb250ZW50');
 @$core.Deprecated('Use createGridPayloadDescriptor instead')
 const CreateGridPayload$json = const {
   '1': 'CreateGridPayload',

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

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

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

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

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

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

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

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

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

@@ -12,10 +12,16 @@ import 'package:protobuf/protobuf.dart' as $pb;
 class GridEvent extends $pb.ProtobufEnum {
   static const GridEvent CreateGrid = GridEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateGrid');
   static const GridEvent OpenGrid = GridEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'OpenGrid');
+  static const GridEvent GetRows = GridEvent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetRows');
+  static const GridEvent GetFields = GridEvent._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetFields');
+  static const GridEvent CreateRow = GridEvent._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateRow');
 
   static const $core.List<GridEvent> values = <GridEvent> [
     CreateGrid,
     OpenGrid,
+    GetRows,
+    GetFields,
+    CreateRow,
   ];
 
   static final $core.Map<$core.int, GridEvent> _byValue = $pb.ProtobufEnum.initByValue(values);

+ 4 - 1
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart

@@ -14,8 +14,11 @@ const GridEvent$json = const {
   '2': const [
     const {'1': 'CreateGrid', '2': 0},
     const {'1': 'OpenGrid', '2': 1},
+    const {'1': 'GetRows', '2': 2},
+    const {'1': 'GetFields', '2': 3},
+    const {'1': 'CreateRow', '2': 4},
   ],
 };
 
 /// Descriptor for `GridEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDgoKQ3JlYXRlR3JpZBAAEgwKCE9wZW5HcmlkEAE=');
+final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDgoKQ3JlYXRlR3JpZBAAEgwKCE9wZW5HcmlkEAESCwoHR2V0Um93cxACEg0KCUdldEZpZWxkcxADEg0KCUNyZWF0ZVJvdxAE');

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

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

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

@@ -57,6 +57,12 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
 
+[[package]]
+name = "arrayvec"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
+
 [[package]]
 name = "async-stream"
 version = "0.3.2"
@@ -1042,15 +1048,21 @@ dependencies = [
 name = "flowy-grid"
 version = "0.1.0"
 dependencies = [
+ "bytes",
+ "chrono",
  "flowy-derive",
  "flowy-error",
  "flowy-grid-data-model",
+ "lazy_static",
  "lib-dispatch",
  "lib-infra",
  "protobuf",
+ "rust_decimal",
+ "rusty-money",
  "strum",
  "strum_macros",
  "tracing",
+ "uuid",
 ]
 
 [[package]]
@@ -1061,6 +1073,8 @@ dependencies = [
  "flowy-derive",
  "lib-infra",
  "protobuf",
+ "strum",
+ "strum_macros",
 ]
 
 [[package]]
@@ -1702,7 +1716,7 @@ version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
 dependencies = [
- "arrayvec",
+ "arrayvec 0.5.2",
  "bitflags",
  "cfg-if",
  "ryu",
@@ -2752,6 +2766,27 @@ dependencies = [
  "winreg",
 ]
 
+[[package]]
+name = "rust_decimal"
+version = "1.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d37baa70cf8662d2ba1c1868c5983dda16ef32b105cce41fb5c47e72936a90b3"
+dependencies = [
+ "arrayvec 0.7.2",
+ "num-traits",
+ "serde",
+]
+
+[[package]]
+name = "rust_decimal_macros"
+version = "1.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "184abaf7b434800e1a5a8aad3ebc8cd7498df33af72d65371d797a264713a59b"
+dependencies = [
+ "quote",
+ "rust_decimal",
+]
+
 [[package]]
 name = "rustc-demangle"
 version = "0.1.21"
@@ -2773,6 +2808,16 @@ version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
 
+[[package]]
+name = "rusty-money"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b28f881005eac7ad8d46b6f075da5f322bd7f4f83a38720fc069694ddadd683"
+dependencies = [
+ "rust_decimal",
+ "rust_decimal_macros",
+]
+
 [[package]]
 name = "ryu"
 version = "1.0.9"

+ 6 - 0
frontend/rust-lib/flowy-grid/Cargo.toml

@@ -15,6 +15,12 @@ strum_macros = "0.21"
 flowy-derive = { path = "../../../shared-lib/flowy-derive" }
 tracing = { version = "0.1", features = ["log"] }
 protobuf = {version = "2.18.0"}
+rust_decimal = "1.8.1"
+rusty-money = {version = "0.4.0", features = ["iso"]}
+lazy_static = "1.4.0"
+chrono = "0.4.19"
+uuid = { version = "0.8", features = ["serde", "v4"] }
+bytes = { version = "1.0" }
 
 [build-dependencies]
 lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file_gen", "proto_gen"] }

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

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

+ 469 - 0
frontend/rust-lib/flowy-grid/src/cell_service/cell_data.rs

@@ -0,0 +1,469 @@
+use crate::cell_service::util::*;
+use crate::impl_any_data;
+use bytes::Bytes;
+use chrono::format::strftime::StrftimeItems;
+use chrono::NaiveDateTime;
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::FlowyError;
+use flowy_grid_data_model::entities::{AnyData, Field, FieldType};
+use rust_decimal::Decimal;
+use rusty_money::{
+    iso::{Currency, CNY, EUR, USD},
+    Money,
+};
+use std::str::FromStr;
+
+use strum::IntoEnumIterator;
+use strum_macros::EnumIter;
+
+pub trait StringifyAnyData {
+    fn stringify_any_data(&self, data: &AnyData) -> String;
+    fn str_to_any_data(&self, s: &str) -> Result<AnyData, FlowyError>;
+}
+
+pub trait DisplayCell {
+    fn display_content(&self, s: &str) -> String;
+}
+
+#[derive(Debug, Clone, ProtoBuf, Default)]
+pub struct RichTextDescription {
+    #[pb(index = 1)]
+    pub format: String,
+}
+impl_any_data!(RichTextDescription, FieldType::RichText);
+
+impl StringifyAnyData for RichTextDescription {
+    fn stringify_any_data(&self, data: &AnyData) -> String {
+        data.to_string()
+    }
+
+    fn str_to_any_data(&self, s: &str) -> Result<AnyData, FlowyError> {
+        Ok(AnyData::from_str(&RichTextDescription::field_type(), s))
+    }
+}
+
+impl DisplayCell for RichTextDescription {
+    fn display_content(&self, s: &str) -> String {
+        s.to_string()
+    }
+}
+
+// Checkbox
+#[derive(Debug, ProtoBuf, Default)]
+pub struct CheckboxDescription {
+    #[pb(index = 1)]
+    pub is_selected: bool,
+}
+impl_any_data!(CheckboxDescription, FieldType::Checkbox);
+
+impl StringifyAnyData for CheckboxDescription {
+    fn stringify_any_data(&self, data: &AnyData) -> String {
+        data.to_string()
+    }
+
+    fn str_to_any_data(&self, s: &str) -> Result<AnyData, FlowyError> {
+        let s = match string_to_bool(s) {
+            true => "1",
+            false => "0",
+        };
+        Ok(AnyData::from_str(&CheckboxDescription::field_type(), s))
+    }
+}
+
+impl DisplayCell for CheckboxDescription {
+    fn display_content(&self, s: &str) -> String {
+        s.to_string()
+    }
+}
+
+// Date
+#[derive(Clone, Debug, ProtoBuf)]
+pub struct DateDescription {
+    #[pb(index = 1)]
+    pub date_format: DateFormat,
+
+    #[pb(index = 2)]
+    pub time_format: TimeFormat,
+}
+impl_any_data!(DateDescription, FieldType::DateTime);
+
+impl std::default::Default for DateDescription {
+    fn default() -> Self {
+        DateDescription {
+            date_format: DateFormat::default(),
+            time_format: TimeFormat::default(),
+        }
+    }
+}
+
+impl DateDescription {
+    fn date_time_format_str(&self) -> String {
+        format!("{} {}", self.date_format.format_str(), self.time_format.format_str())
+    }
+
+    #[allow(dead_code)]
+    fn today_from_timestamp(&self, timestamp: i64) -> String {
+        let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0);
+        self.today_from_native(native)
+    }
+
+    fn today_from_native(&self, naive: chrono::NaiveDateTime) -> String {
+        let utc: chrono::DateTime<chrono::Utc> = chrono::DateTime::from_utc(naive, chrono::Utc);
+        let local: chrono::DateTime<chrono::Local> = chrono::DateTime::from(utc);
+
+        let fmt_str = self.date_time_format_str();
+        let output = format!("{}", local.format_with_items(StrftimeItems::new(&fmt_str)));
+        output
+    }
+}
+
+impl DisplayCell for DateDescription {
+    fn display_content(&self, s: &str) -> String {
+        match s.parse::<i64>() {
+            Ok(timestamp) => {
+                let native = NaiveDateTime::from_timestamp(timestamp, 0);
+                self.today_from_native(native)
+            }
+            Err(e) => {
+                tracing::debug!("DateDescription format {} fail. error: {:?}", s, e);
+                String::new()
+            }
+        }
+    }
+}
+
+impl StringifyAnyData for DateDescription {
+    fn stringify_any_data(&self, data: &AnyData) -> String {
+        match String::from_utf8(data.value.clone()) {
+            Ok(s) => match s.parse::<i64>() {
+                Ok(timestamp) => {
+                    let native = NaiveDateTime::from_timestamp(timestamp, 0);
+                    self.today_from_native(native)
+                }
+                Err(e) => {
+                    tracing::debug!("DateDescription format {} fail. error: {:?}", s, e);
+                    String::new()
+                }
+            },
+            Err(e) => {
+                tracing::error!("DateDescription stringify any_data failed. {:?}", e);
+                String::new()
+            }
+        }
+    }
+
+    fn str_to_any_data(&self, s: &str) -> Result<AnyData, FlowyError> {
+        let timestamp = s
+            .parse::<i64>()
+            .map_err(|e| FlowyError::internal().context(format!("Parse {} to i64 failed: {}", s, e)))?;
+        Ok(AnyData::from_str(
+            &DateDescription::field_type(),
+            &format!("{}", timestamp),
+        ))
+    }
+}
+
+#[derive(Clone, Debug, Copy, ProtoBuf_Enum)]
+pub enum DateFormat {
+    Local = 0,
+    US = 1,
+    ISO = 2,
+    Friendly = 3,
+}
+impl std::default::Default for DateFormat {
+    fn default() -> Self {
+        DateFormat::Friendly
+    }
+}
+
+impl std::convert::From<i32> for DateFormat {
+    fn from(value: i32) -> Self {
+        match value {
+            0 => DateFormat::Local,
+            1 => DateFormat::US,
+            2 => DateFormat::ISO,
+            3 => DateFormat::Friendly,
+            _ => {
+                tracing::error!("Unsupported date format, fallback to friendly");
+                DateFormat::Friendly
+            }
+        }
+    }
+}
+
+impl DateFormat {
+    pub fn value(&self) -> i32 {
+        *self as i32
+    }
+    // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
+    pub fn format_str(&self) -> &'static str {
+        match self {
+            DateFormat::Local => "%Y/%m/%d",
+            DateFormat::US => "%Y/%m/%d",
+            DateFormat::ISO => "%Y-%m-%d",
+            DateFormat::Friendly => "%b %d,%Y",
+        }
+    }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, ProtoBuf_Enum)]
+pub enum TimeFormat {
+    TwelveHour = 0,
+    TwentyFourHour = 1,
+}
+
+impl std::convert::From<i32> for TimeFormat {
+    fn from(value: i32) -> Self {
+        match value {
+            0 => TimeFormat::TwelveHour,
+            1 => TimeFormat::TwentyFourHour,
+            _ => {
+                tracing::error!("Unsupported time format, fallback to TwentyFourHour");
+                TimeFormat::TwentyFourHour
+            }
+        }
+    }
+}
+
+impl TimeFormat {
+    pub fn value(&self) -> i32 {
+        *self as i32
+    }
+
+    // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
+    pub fn format_str(&self) -> &'static str {
+        match self {
+            TimeFormat::TwelveHour => "%r",
+            TimeFormat::TwentyFourHour => "%R",
+        }
+    }
+}
+
+impl std::default::Default for TimeFormat {
+    fn default() -> Self {
+        TimeFormat::TwentyFourHour
+    }
+}
+
+// Single select
+#[derive(Clone, Debug, ProtoBuf, Default)]
+pub struct SingleSelect {
+    #[pb(index = 1)]
+    pub options: Vec<SelectOption>,
+
+    #[pb(index = 2)]
+    pub disable_color: bool,
+}
+impl_any_data!(SingleSelect, FieldType::SingleSelect);
+
+impl StringifyAnyData for SingleSelect {
+    fn stringify_any_data(&self, data: &AnyData) -> String {
+        data.to_string()
+    }
+
+    fn str_to_any_data(&self, s: &str) -> Result<AnyData, FlowyError> {
+        Ok(AnyData::from_str(&SingleSelect::field_type(), s))
+    }
+}
+
+impl DisplayCell for SingleSelect {
+    fn display_content(&self, s: &str) -> String {
+        s.to_string()
+    }
+}
+
+// Multiple select
+#[derive(Clone, Debug, ProtoBuf, Default)]
+pub struct MultiSelect {
+    #[pb(index = 1)]
+    pub options: Vec<SelectOption>,
+
+    #[pb(index = 2)]
+    pub disable_color: bool,
+}
+impl_any_data!(MultiSelect, FieldType::MultiSelect);
+impl StringifyAnyData for MultiSelect {
+    fn stringify_any_data(&self, data: &AnyData) -> String {
+        data.to_string()
+    }
+
+    fn str_to_any_data(&self, s: &str) -> Result<AnyData, FlowyError> {
+        Ok(AnyData::from_str(&MultiSelect::field_type(), s))
+    }
+}
+
+impl DisplayCell for MultiSelect {
+    fn display_content(&self, s: &str) -> String {
+        s.to_string()
+    }
+}
+
+#[derive(Clone, Debug, ProtoBuf, Default)]
+pub struct SelectOption {
+    #[pb(index = 1)]
+    pub id: String,
+
+    #[pb(index = 2)]
+    pub name: String,
+
+    #[pb(index = 3)]
+    pub color: String,
+}
+
+impl SelectOption {
+    pub fn new(name: &str) -> Self {
+        SelectOption {
+            id: uuid(),
+            name: name.to_owned(),
+            color: "".to_string(),
+        }
+    }
+}
+
+// Number
+#[derive(Clone, Debug, ProtoBuf)]
+pub struct NumberDescription {
+    #[pb(index = 1)]
+    pub money: FlowyMoney,
+
+    #[pb(index = 2)]
+    pub scale: u32,
+
+    #[pb(index = 3)]
+    pub symbol: String,
+
+    #[pb(index = 4)]
+    pub sign_positive: bool,
+
+    #[pb(index = 5)]
+    pub name: String,
+}
+impl_any_data!(NumberDescription, FieldType::Number);
+
+impl std::default::Default for NumberDescription {
+    fn default() -> Self {
+        NumberDescription {
+            money: FlowyMoney::default(),
+            scale: 0,
+            symbol: String::new(),
+            sign_positive: true,
+            name: String::new(),
+        }
+    }
+}
+
+impl NumberDescription {
+    pub fn set_money(&mut self, money: FlowyMoney) {
+        self.money = money;
+        self.symbol = money.symbol();
+    }
+
+    fn money_from_str(&self, s: &str) -> Option<String> {
+        match Decimal::from_str(s) {
+            Ok(mut decimal) => {
+                match decimal.set_scale(self.scale) {
+                    Ok(_) => {}
+                    Err(e) => {
+                        tracing::error!("Set decimal scale failed: {:?}", e);
+                    }
+                }
+                decimal.set_sign_positive(self.sign_positive);
+                Some(self.money.with_decimal(decimal).to_string())
+            }
+            Err(e) => {
+                tracing::error!("Parser money from {} failed: {:?}", s, e);
+                None
+            }
+        }
+    }
+}
+
+impl DisplayCell for NumberDescription {
+    fn display_content(&self, s: &str) -> String {
+        match self.money_from_str(&s) {
+            Some(money_str) => money_str,
+            None => String::default(),
+        }
+    }
+}
+
+impl StringifyAnyData for NumberDescription {
+    fn stringify_any_data(&self, data: &AnyData) -> String {
+        match String::from_utf8(data.value.clone()) {
+            Ok(s) => match self.money_from_str(&s) {
+                Some(money_str) => money_str,
+                None => String::default(),
+            },
+            Err(e) => {
+                tracing::error!("NumberDescription stringify any_data failed. {:?}", e);
+                String::new()
+            }
+        }
+    }
+
+    fn str_to_any_data(&self, s: &str) -> Result<AnyData, FlowyError> {
+        let strip_symbol_money = strip_money_symbol(s);
+        let decimal = Decimal::from_str(&strip_symbol_money).map_err(|err| FlowyError::internal().context(err))?;
+        let money_str = decimal.to_string();
+        Ok(AnyData::from_str(&NumberDescription::field_type(), &money_str))
+    }
+}
+
+#[derive(Clone, Copy, Debug, EnumIter, ProtoBuf_Enum)]
+pub enum FlowyMoney {
+    CNY = 0,
+    EUR = 1,
+    USD = 2,
+}
+
+impl std::default::Default for FlowyMoney {
+    fn default() -> Self {
+        FlowyMoney::USD
+    }
+}
+
+impl FlowyMoney {
+    // Currency list https://docs.rs/rusty-money/0.4.0/rusty_money/iso/index.html
+    pub fn from_str(s: &str) -> FlowyMoney {
+        match s {
+            "CNY" => FlowyMoney::CNY,
+            "EUR" => FlowyMoney::EUR,
+            "USD" => FlowyMoney::USD,
+            _ => FlowyMoney::CNY,
+        }
+    }
+
+    pub fn from_money(money: &rusty_money::Money<Currency>) -> FlowyMoney {
+        FlowyMoney::from_str(&money.currency().symbol.to_string())
+    }
+
+    pub fn currency(&self) -> &'static Currency {
+        match self {
+            FlowyMoney::CNY => CNY,
+            FlowyMoney::EUR => EUR,
+            FlowyMoney::USD => USD,
+        }
+    }
+
+    // string_to_money("¥18,443").unwrap();
+    // string_to_money("$18,443").unwrap();
+    // string_to_money("€18,443").unwrap();
+    pub fn code(&self) -> String {
+        self.currency().iso_alpha_code.to_string()
+    }
+
+    pub fn symbol(&self) -> String {
+        self.currency().symbol.to_string()
+    }
+
+    pub fn zero(&self) -> Money<Currency> {
+        let mut decimal = Decimal::new(0, 0);
+        decimal.set_sign_positive(true);
+        self.with_decimal(decimal)
+    }
+
+    pub fn with_decimal(&self, decimal: Decimal) -> Money<Currency> {
+        let money = rusty_money::Money::from_decimal(decimal, self.currency());
+        money
+    }
+}

+ 5 - 0
frontend/rust-lib/flowy-grid/src/cell_service/mod.rs

@@ -0,0 +1,5 @@
+mod stringify;
+mod util;
+
+pub mod cell_data;
+pub use stringify::*;

+ 30 - 0
frontend/rust-lib/flowy-grid/src/cell_service/stringify.rs

@@ -0,0 +1,30 @@
+use crate::cell_service::cell_data::*;
+use crate::cell_service::util::*;
+use flowy_error::FlowyError;
+use flowy_grid_data_model::entities::{AnyData, Field, FieldType};
+
+pub trait AnyDataSerde {
+    fn serialize(field: &Field, s: &str) -> Result<AnyData, FlowyError> {
+        match field.field_type {
+            FieldType::RichText => RichTextDescription::from(field).str_to_any_data(s),
+            FieldType::Number => NumberDescription::from(field).str_to_any_data(s),
+            FieldType::DateTime => DateDescription::from(field).str_to_any_data(s),
+            FieldType::SingleSelect => SingleSelect::from(field).str_to_any_data(s),
+            FieldType::MultiSelect => MultiSelect::from(field).str_to_any_data(s),
+            FieldType::Checkbox => CheckboxDescription::from(field).str_to_any_data(s),
+        }
+    }
+
+    fn deserialize(data: &AnyData, field: &Field) -> Result<String, FlowyError> {
+        let _ = check_type_id(data, field)?;
+        let s = match field.field_type {
+            FieldType::RichText => RichTextDescription::from(field).stringify_any_data(data),
+            FieldType::Number => NumberDescription::from(field).stringify_any_data(data),
+            FieldType::DateTime => DateDescription::from(field).stringify_any_data(data),
+            FieldType::SingleSelect => SingleSelect::from(field).stringify_any_data(data),
+            FieldType::MultiSelect => MultiSelect::from(field).stringify_any_data(data),
+            FieldType::Checkbox => CheckboxDescription::from(field).stringify_any_data(data),
+        };
+        Ok(s)
+    }
+}

+ 129 - 0
frontend/rust-lib/flowy-grid/src/cell_service/util.rs

@@ -0,0 +1,129 @@
+use crate::cell_service::cell_data::FlowyMoney;
+use flowy_error::FlowyError;
+use flowy_grid_data_model::entities::{AnyData, Field, FieldType};
+use lazy_static::lazy_static;
+use rust_decimal::Decimal;
+use rusty_money::{iso::Currency, Money};
+use std::collections::HashMap;
+use std::str::FromStr;
+use strum::IntoEnumIterator;
+
+lazy_static! {
+    static ref CURRENCIES_BY_SYMBOL: HashMap<String, &'static Currency> = generate_currency_by_symbol();
+}
+
+#[allow(dead_code)]
+fn generate_currency_by_symbol() -> HashMap<String, &'static Currency> {
+    let mut map: HashMap<String, &'static Currency> = HashMap::new();
+
+    for money in FlowyMoney::iter() {
+        map.insert(money.symbol(), money.currency());
+    }
+    map
+}
+
+#[allow(dead_code)]
+pub fn string_to_money(money_str: &str) -> Option<Money<Currency>> {
+    let mut process_money_str = String::from(money_str);
+    let default_currency = FlowyMoney::from_str("CNY").currency();
+
+    if process_money_str.is_empty() {
+        return None;
+    }
+
+    return if process_money_str.chars().all(char::is_numeric) {
+        match Money::from_str(&process_money_str, default_currency) {
+            Ok(money) => Some(money),
+            Err(_) => None,
+        }
+    } else {
+        let symbol = process_money_str.chars().next().unwrap().to_string();
+        let mut currency = default_currency;
+
+        for key in CURRENCIES_BY_SYMBOL.keys() {
+            if symbol.eq(key) {
+                currency = CURRENCIES_BY_SYMBOL.get(key).unwrap();
+                crop_letters(&mut process_money_str, 1);
+            }
+        }
+
+        match Money::from_str(&process_money_str, currency) {
+            Ok(money) => Some(money),
+            Err(_) => None,
+        }
+    };
+}
+
+#[allow(dead_code)]
+pub fn money_from_str(s: &str) -> Option<String> {
+    match Decimal::from_str(s) {
+        Ok(mut decimal) => {
+            match decimal.set_scale(0) {
+                Ok(_) => {}
+                Err(e) => {
+                    tracing::error!("Set scale failed. {:?}", e);
+                }
+            }
+            decimal.set_sign_positive(true);
+            Some(FlowyMoney::USD.with_decimal(decimal).to_string())
+        }
+        Err(e) => {
+            tracing::debug!("Format {} to money failed, {:?}", s, e);
+            None
+        }
+    }
+}
+
+pub fn strip_money_symbol(money_str: &str) -> String {
+    let mut process_money_str = String::from(money_str);
+
+    if !process_money_str.chars().all(char::is_numeric) {
+        let symbol = process_money_str.chars().next().unwrap().to_string();
+        for key in CURRENCIES_BY_SYMBOL.keys() {
+            if symbol.eq(key) {
+                crop_letters(&mut process_money_str, 1);
+            }
+        }
+    }
+    process_money_str
+}
+
+fn crop_letters(s: &mut String, pos: usize) {
+    match s.char_indices().nth(pos) {
+        Some((pos, _)) => {
+            s.drain(..pos);
+        }
+        None => {
+            s.clear();
+        }
+    }
+}
+
+pub fn string_to_bool(bool_str: &str) -> bool {
+    let lower_case_str: &str = &bool_str.to_lowercase();
+    match lower_case_str {
+        "1" => true,
+        "true" => true,
+        "yes" => true,
+        "0" => false,
+        "false" => false,
+        "no" => false,
+        _ => false,
+    }
+}
+
+pub fn uuid() -> String {
+    uuid::Uuid::new_v4().to_string()
+}
+
+pub fn check_type_id(data: &AnyData, field: &Field) -> Result<(), FlowyError> {
+    let field_type = FieldType::from_type_id(&data.type_id).map_err(|e| FlowyError::internal().context(e))?;
+    if field_type != field.field_type {
+        tracing::error!(
+            "expected field type: {:?} but receive {:?} ",
+            field_type,
+            field.field_type
+        );
+    }
+    Ok(())
+}

+ 35 - 3
frontend/rust-lib/flowy-grid/src/event_handler.rs

@@ -1,7 +1,9 @@
 use crate::controller::GridManager;
 use flowy_error::FlowyError;
-use flowy_grid_data_model::entities::{CreateGridPayload, Grid, GridId};
-use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
+use flowy_grid_data_model::entities::{
+    CreateGridPayload, Grid, GridId, RepeatedFieldOrder, RepeatedRow, RepeatedRowOrder,
+};
+use lib_dispatch::prelude::{AppData, Data, DataResult};
 use std::sync::Arc;
 
 #[tracing::instrument(skip(data, controller), err)]
@@ -17,7 +19,37 @@ pub(crate) async fn open_grid_handler(
     data: Data<GridId>,
     controller: AppData<Arc<GridManager>>,
 ) -> DataResult<Grid, FlowyError> {
-    let params: GridId = data.into_inner();
+    let _params: GridId = data.into_inner();
+
+    todo!()
+}
+
+#[tracing::instrument(skip(data, controller), err)]
+pub(crate) async fn get_rows_handler(
+    data: Data<RepeatedRowOrder>,
+    controller: AppData<Arc<GridManager>>,
+) -> DataResult<Grid, FlowyError> {
+    let row_orders: RepeatedRowOrder = data.into_inner();
+
+    todo!()
+}
+
+#[tracing::instrument(skip(data, controller), err)]
+pub(crate) async fn get_fields_handler(
+    data: Data<RepeatedFieldOrder>,
+    controller: AppData<Arc<GridManager>>,
+) -> DataResult<Grid, FlowyError> {
+    let field_orders: RepeatedFieldOrder = data.into_inner();
+
+    todo!()
+}
+
+#[tracing::instrument(skip(data, controller), err)]
+pub(crate) async fn create_row_handler(
+    data: Data<GridId>,
+    controller: AppData<Arc<GridManager>>,
+) -> DataResult<Grid, FlowyError> {
+    let id: GridId = data.into_inner();
 
     todo!()
 }

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

@@ -7,10 +7,12 @@ use strum_macros::Display;
 
 pub fn create(grid_manager: Arc<GridManager>) -> Module {
     let mut module = Module::new().name(env!("CARGO_PKG_NAME")).data(grid_manager);
-
     module = module
         .event(GridEvent::CreateGrid, create_grid_handler)
-        .event(GridEvent::OpenGrid, open_grid_handler);
+        .event(GridEvent::OpenGrid, open_grid_handler)
+        .event(GridEvent::GetRows, get_rows_handler)
+        .event(GridEvent::GetFields, get_fields_handler)
+        .event(GridEvent::CreateRow, create_row_handler);
 
     module
 }
@@ -23,4 +25,13 @@ pub enum GridEvent {
 
     #[event(input = "GridId", output = "Grid")]
     OpenGrid = 1,
+
+    #[event(input = "RepeatedRowOrder", output = "RepeatedRow")]
+    GetRows = 2,
+
+    #[event(input = "RepeatedFieldOrder", output = "RepeatedField")]
+    GetFields = 3,
+
+    #[event(input = "GridId")]
+    CreateRow = 4,
 }

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

@@ -1,5 +1,9 @@
+#[macro_use]
+mod macros;
+
 mod controller;
 mod event_handler;
 mod event_map;
 
+mod cell_service;
 mod protobuf;

+ 64 - 0
frontend/rust-lib/flowy-grid/src/macros.rs

@@ -0,0 +1,64 @@
+#[macro_export]
+macro_rules! impl_any_data {
+    ($target: ident, $field_type:expr) => {
+        impl_field_type_data_from_field!($target);
+        impl_field_type_data_from_field_type_option!($target);
+        impl_type_option_from_field_data!($target, $field_type);
+    };
+}
+
+#[macro_export]
+macro_rules! impl_field_type_data_from_field {
+    ($target: ident) => {
+        impl std::convert::From<&Field> for $target {
+            fn from(field: &Field) -> $target {
+                $target::from(&field.type_options)
+            }
+        }
+    };
+}
+
+#[macro_export]
+macro_rules! impl_field_type_data_from_field_type_option {
+    ($target: ident) => {
+        impl std::convert::From<&AnyData> for $target {
+            fn from(any_data: &AnyData) -> $target {
+                match $target::try_from(Bytes::from(any_data.value.clone())) {
+                    Ok(obj) => obj,
+                    Err(err) => {
+                        tracing::error!("{} convert from any data failed, {:?}", stringify!($target), err);
+                        $target::default()
+                    }
+                }
+            }
+        }
+    };
+}
+
+#[macro_export]
+macro_rules! impl_type_option_from_field_data {
+    ($target: ident, $field_type:expr) => {
+        impl $target {
+            pub fn field_type() -> FieldType {
+                $field_type
+            }
+        }
+
+        impl std::convert::From<$target> for AnyData {
+            fn from(field_data: $target) -> Self {
+                match field_data.try_into() {
+                    Ok(bytes) => {
+                        let bytes: Bytes = bytes;
+                        AnyData::from_bytes(&$target::field_type(), bytes)
+                    }
+                    Err(e) => {
+                        tracing::error!("Field type data convert to AnyData fail, error: {:?}", e);
+                        // it's impossible to fail when unwrapping the default field type data
+                        let default_bytes: Bytes = $target::default().try_into().unwrap();
+                        AnyData::from_bytes(&$target::field_type(), default_bytes)
+                    }
+                }
+            }
+        }
+    };
+}

+ 1655 - 0
frontend/rust-lib/flowy-grid/src/protobuf/model/cell_data.rs

@@ -0,0 +1,1655 @@
+// This file is generated by rust-protobuf 2.25.2. Do not edit
+// @generated
+
+// https://github.com/rust-lang/rust-clippy/issues/702
+#![allow(unknown_lints)]
+#![allow(clippy::all)]
+
+#![allow(unused_attributes)]
+#![cfg_attr(rustfmt, rustfmt::skip)]
+
+#![allow(box_pointers)]
+#![allow(dead_code)]
+#![allow(missing_docs)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(non_upper_case_globals)]
+#![allow(trivial_casts)]
+#![allow(unused_imports)]
+#![allow(unused_results)]
+//! Generated file from `cell_data.proto`
+
+/// Generated files are compatible only with the same version
+/// of protobuf runtime.
+// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
+
+#[derive(PartialEq,Clone,Default)]
+pub struct RichTextDescription {
+    // message fields
+    pub format: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a RichTextDescription {
+    fn default() -> &'a RichTextDescription {
+        <RichTextDescription as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl RichTextDescription {
+    pub fn new() -> RichTextDescription {
+        ::std::default::Default::default()
+    }
+
+    // string format = 1;
+
+
+    pub fn get_format(&self) -> &str {
+        &self.format
+    }
+    pub fn clear_format(&mut self) {
+        self.format.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_format(&mut self, v: ::std::string::String) {
+        self.format = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_format(&mut self) -> &mut ::std::string::String {
+        &mut self.format
+    }
+
+    // Take field
+    pub fn take_format(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.format, ::std::string::String::new())
+    }
+}
+
+impl ::protobuf::Message for RichTextDescription {
+    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.format)?;
+                },
+                _ => {
+                    ::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.format.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.format);
+        }
+        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.format.is_empty() {
+            os.write_string(1, &self.format)?;
+        }
+        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() -> RichTextDescription {
+        RichTextDescription::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>(
+                "format",
+                |m: &RichTextDescription| { &m.format },
+                |m: &mut RichTextDescription| { &mut m.format },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<RichTextDescription>(
+                "RichTextDescription",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static RichTextDescription {
+        static instance: ::protobuf::rt::LazyV2<RichTextDescription> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(RichTextDescription::new)
+    }
+}
+
+impl ::protobuf::Clear for RichTextDescription {
+    fn clear(&mut self) {
+        self.format.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for RichTextDescription {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for RichTextDescription {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct CheckboxDescription {
+    // message fields
+    pub is_selected: bool,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a CheckboxDescription {
+    fn default() -> &'a CheckboxDescription {
+        <CheckboxDescription as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl CheckboxDescription {
+    pub fn new() -> CheckboxDescription {
+        ::std::default::Default::default()
+    }
+
+    // bool is_selected = 1;
+
+
+    pub fn get_is_selected(&self) -> bool {
+        self.is_selected
+    }
+    pub fn clear_is_selected(&mut self) {
+        self.is_selected = false;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_is_selected(&mut self, v: bool) {
+        self.is_selected = v;
+    }
+}
+
+impl ::protobuf::Message for CheckboxDescription {
+    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 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_bool()?;
+                    self.is_selected = tmp;
+                },
+                _ => {
+                    ::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.is_selected != false {
+            my_size += 2;
+        }
+        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.is_selected != false {
+            os.write_bool(1, self.is_selected)?;
+        }
+        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() -> CheckboxDescription {
+        CheckboxDescription::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::ProtobufTypeBool>(
+                "is_selected",
+                |m: &CheckboxDescription| { &m.is_selected },
+                |m: &mut CheckboxDescription| { &mut m.is_selected },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CheckboxDescription>(
+                "CheckboxDescription",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static CheckboxDescription {
+        static instance: ::protobuf::rt::LazyV2<CheckboxDescription> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(CheckboxDescription::new)
+    }
+}
+
+impl ::protobuf::Clear for CheckboxDescription {
+    fn clear(&mut self) {
+        self.is_selected = false;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for CheckboxDescription {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for CheckboxDescription {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct DateDescription {
+    // message fields
+    pub date_format: DateFormat,
+    pub time_format: TimeFormat,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a DateDescription {
+    fn default() -> &'a DateDescription {
+        <DateDescription as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl DateDescription {
+    pub fn new() -> DateDescription {
+        ::std::default::Default::default()
+    }
+
+    // .DateFormat date_format = 1;
+
+
+    pub fn get_date_format(&self) -> DateFormat {
+        self.date_format
+    }
+    pub fn clear_date_format(&mut self) {
+        self.date_format = DateFormat::Local;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_date_format(&mut self, v: DateFormat) {
+        self.date_format = v;
+    }
+
+    // .TimeFormat time_format = 2;
+
+
+    pub fn get_time_format(&self) -> TimeFormat {
+        self.time_format
+    }
+    pub fn clear_time_format(&mut self) {
+        self.time_format = TimeFormat::TwelveHour;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_time_format(&mut self, v: TimeFormat) {
+        self.time_format = v;
+    }
+}
+
+impl ::protobuf::Message for DateDescription {
+    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_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.date_format, 1, &mut self.unknown_fields)?
+                },
+                2 => {
+                    ::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.time_format, 2, &mut self.unknown_fields)?
+                },
+                _ => {
+                    ::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.date_format != DateFormat::Local {
+            my_size += ::protobuf::rt::enum_size(1, self.date_format);
+        }
+        if self.time_format != TimeFormat::TwelveHour {
+            my_size += ::protobuf::rt::enum_size(2, self.time_format);
+        }
+        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.date_format != DateFormat::Local {
+            os.write_enum(1, ::protobuf::ProtobufEnum::value(&self.date_format))?;
+        }
+        if self.time_format != TimeFormat::TwelveHour {
+            os.write_enum(2, ::protobuf::ProtobufEnum::value(&self.time_format))?;
+        }
+        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() -> DateDescription {
+        DateDescription::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::ProtobufTypeEnum<DateFormat>>(
+                "date_format",
+                |m: &DateDescription| { &m.date_format },
+                |m: &mut DateDescription| { &mut m.date_format },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum<TimeFormat>>(
+                "time_format",
+                |m: &DateDescription| { &m.time_format },
+                |m: &mut DateDescription| { &mut m.time_format },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<DateDescription>(
+                "DateDescription",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static DateDescription {
+        static instance: ::protobuf::rt::LazyV2<DateDescription> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(DateDescription::new)
+    }
+}
+
+impl ::protobuf::Clear for DateDescription {
+    fn clear(&mut self) {
+        self.date_format = DateFormat::Local;
+        self.time_format = TimeFormat::TwelveHour;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for DateDescription {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for DateDescription {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct SingleSelect {
+    // message fields
+    pub options: ::protobuf::RepeatedField<SelectOption>,
+    pub disable_color: bool,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a SingleSelect {
+    fn default() -> &'a SingleSelect {
+        <SingleSelect as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl SingleSelect {
+    pub fn new() -> SingleSelect {
+        ::std::default::Default::default()
+    }
+
+    // repeated .SelectOption options = 1;
+
+
+    pub fn get_options(&self) -> &[SelectOption] {
+        &self.options
+    }
+    pub fn clear_options(&mut self) {
+        self.options.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_options(&mut self, v: ::protobuf::RepeatedField<SelectOption>) {
+        self.options = v;
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_options(&mut self) -> &mut ::protobuf::RepeatedField<SelectOption> {
+        &mut self.options
+    }
+
+    // Take field
+    pub fn take_options(&mut self) -> ::protobuf::RepeatedField<SelectOption> {
+        ::std::mem::replace(&mut self.options, ::protobuf::RepeatedField::new())
+    }
+
+    // bool disable_color = 2;
+
+
+    pub fn get_disable_color(&self) -> bool {
+        self.disable_color
+    }
+    pub fn clear_disable_color(&mut self) {
+        self.disable_color = false;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_disable_color(&mut self, v: bool) {
+        self.disable_color = v;
+    }
+}
+
+impl ::protobuf::Message for SingleSelect {
+    fn is_initialized(&self) -> bool {
+        for v in &self.options {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.options)?;
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_bool()?;
+                    self.disable_color = tmp;
+                },
+                _ => {
+                    ::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;
+        for value in &self.options {
+            let len = value.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        };
+        if self.disable_color != false {
+            my_size += 2;
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        for v in &self.options {
+            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        };
+        if self.disable_color != false {
+            os.write_bool(2, self.disable_color)?;
+        }
+        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() -> SingleSelect {
+        SingleSelect::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<SelectOption>>(
+                "options",
+                |m: &SingleSelect| { &m.options },
+                |m: &mut SingleSelect| { &mut m.options },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
+                "disable_color",
+                |m: &SingleSelect| { &m.disable_color },
+                |m: &mut SingleSelect| { &mut m.disable_color },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SingleSelect>(
+                "SingleSelect",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static SingleSelect {
+        static instance: ::protobuf::rt::LazyV2<SingleSelect> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(SingleSelect::new)
+    }
+}
+
+impl ::protobuf::Clear for SingleSelect {
+    fn clear(&mut self) {
+        self.options.clear();
+        self.disable_color = false;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for SingleSelect {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for SingleSelect {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct MultiSelect {
+    // message fields
+    pub options: ::protobuf::RepeatedField<SelectOption>,
+    pub disable_color: bool,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a MultiSelect {
+    fn default() -> &'a MultiSelect {
+        <MultiSelect as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl MultiSelect {
+    pub fn new() -> MultiSelect {
+        ::std::default::Default::default()
+    }
+
+    // repeated .SelectOption options = 1;
+
+
+    pub fn get_options(&self) -> &[SelectOption] {
+        &self.options
+    }
+    pub fn clear_options(&mut self) {
+        self.options.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_options(&mut self, v: ::protobuf::RepeatedField<SelectOption>) {
+        self.options = v;
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_options(&mut self) -> &mut ::protobuf::RepeatedField<SelectOption> {
+        &mut self.options
+    }
+
+    // Take field
+    pub fn take_options(&mut self) -> ::protobuf::RepeatedField<SelectOption> {
+        ::std::mem::replace(&mut self.options, ::protobuf::RepeatedField::new())
+    }
+
+    // bool disable_color = 2;
+
+
+    pub fn get_disable_color(&self) -> bool {
+        self.disable_color
+    }
+    pub fn clear_disable_color(&mut self) {
+        self.disable_color = false;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_disable_color(&mut self, v: bool) {
+        self.disable_color = v;
+    }
+}
+
+impl ::protobuf::Message for MultiSelect {
+    fn is_initialized(&self) -> bool {
+        for v in &self.options {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.options)?;
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_bool()?;
+                    self.disable_color = tmp;
+                },
+                _ => {
+                    ::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;
+        for value in &self.options {
+            let len = value.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        };
+        if self.disable_color != false {
+            my_size += 2;
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        for v in &self.options {
+            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        };
+        if self.disable_color != false {
+            os.write_bool(2, self.disable_color)?;
+        }
+        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() -> MultiSelect {
+        MultiSelect::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<SelectOption>>(
+                "options",
+                |m: &MultiSelect| { &m.options },
+                |m: &mut MultiSelect| { &mut m.options },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
+                "disable_color",
+                |m: &MultiSelect| { &m.disable_color },
+                |m: &mut MultiSelect| { &mut m.disable_color },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<MultiSelect>(
+                "MultiSelect",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static MultiSelect {
+        static instance: ::protobuf::rt::LazyV2<MultiSelect> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(MultiSelect::new)
+    }
+}
+
+impl ::protobuf::Clear for MultiSelect {
+    fn clear(&mut self) {
+        self.options.clear();
+        self.disable_color = false;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for MultiSelect {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for MultiSelect {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct SelectOption {
+    // message fields
+    pub id: ::std::string::String,
+    pub name: ::std::string::String,
+    pub color: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a SelectOption {
+    fn default() -> &'a SelectOption {
+        <SelectOption as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl SelectOption {
+    pub fn new() -> SelectOption {
+        ::std::default::Default::default()
+    }
+
+    // string id = 1;
+
+
+    pub fn get_id(&self) -> &str {
+        &self.id
+    }
+    pub fn clear_id(&mut self) {
+        self.id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_id(&mut self, v: ::std::string::String) {
+        self.id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_id(&mut self) -> &mut ::std::string::String {
+        &mut self.id
+    }
+
+    // Take field
+    pub fn take_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.id, ::std::string::String::new())
+    }
+
+    // string name = 2;
+
+
+    pub fn get_name(&self) -> &str {
+        &self.name
+    }
+    pub fn clear_name(&mut self) {
+        self.name.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_name(&mut self, v: ::std::string::String) {
+        self.name = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_name(&mut self) -> &mut ::std::string::String {
+        &mut self.name
+    }
+
+    // Take field
+    pub fn take_name(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.name, ::std::string::String::new())
+    }
+
+    // string color = 3;
+
+
+    pub fn get_color(&self) -> &str {
+        &self.color
+    }
+    pub fn clear_color(&mut self) {
+        self.color.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_color(&mut self, v: ::std::string::String) {
+        self.color = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_color(&mut self) -> &mut ::std::string::String {
+        &mut self.color
+    }
+
+    // Take field
+    pub fn take_color(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.color, ::std::string::String::new())
+    }
+}
+
+impl ::protobuf::Message for SelectOption {
+    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.id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name)?;
+                },
+                3 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.color)?;
+                },
+                _ => {
+                    ::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.id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.id);
+        }
+        if !self.name.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.name);
+        }
+        if !self.color.is_empty() {
+            my_size += ::protobuf::rt::string_size(3, &self.color);
+        }
+        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.id.is_empty() {
+            os.write_string(1, &self.id)?;
+        }
+        if !self.name.is_empty() {
+            os.write_string(2, &self.name)?;
+        }
+        if !self.color.is_empty() {
+            os.write_string(3, &self.color)?;
+        }
+        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() -> SelectOption {
+        SelectOption::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "id",
+                |m: &SelectOption| { &m.id },
+                |m: &mut SelectOption| { &mut m.id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "name",
+                |m: &SelectOption| { &m.name },
+                |m: &mut SelectOption| { &mut m.name },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "color",
+                |m: &SelectOption| { &m.color },
+                |m: &mut SelectOption| { &mut m.color },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SelectOption>(
+                "SelectOption",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static SelectOption {
+        static instance: ::protobuf::rt::LazyV2<SelectOption> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(SelectOption::new)
+    }
+}
+
+impl ::protobuf::Clear for SelectOption {
+    fn clear(&mut self) {
+        self.id.clear();
+        self.name.clear();
+        self.color.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for SelectOption {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for SelectOption {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct NumberDescription {
+    // message fields
+    pub money: FlowyMoney,
+    pub scale: u32,
+    pub symbol: ::std::string::String,
+    pub sign_positive: bool,
+    pub name: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a NumberDescription {
+    fn default() -> &'a NumberDescription {
+        <NumberDescription as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl NumberDescription {
+    pub fn new() -> NumberDescription {
+        ::std::default::Default::default()
+    }
+
+    // .FlowyMoney money = 1;
+
+
+    pub fn get_money(&self) -> FlowyMoney {
+        self.money
+    }
+    pub fn clear_money(&mut self) {
+        self.money = FlowyMoney::CNY;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_money(&mut self, v: FlowyMoney) {
+        self.money = v;
+    }
+
+    // uint32 scale = 2;
+
+
+    pub fn get_scale(&self) -> u32 {
+        self.scale
+    }
+    pub fn clear_scale(&mut self) {
+        self.scale = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_scale(&mut self, v: u32) {
+        self.scale = v;
+    }
+
+    // string symbol = 3;
+
+
+    pub fn get_symbol(&self) -> &str {
+        &self.symbol
+    }
+    pub fn clear_symbol(&mut self) {
+        self.symbol.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_symbol(&mut self, v: ::std::string::String) {
+        self.symbol = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_symbol(&mut self) -> &mut ::std::string::String {
+        &mut self.symbol
+    }
+
+    // Take field
+    pub fn take_symbol(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.symbol, ::std::string::String::new())
+    }
+
+    // bool sign_positive = 4;
+
+
+    pub fn get_sign_positive(&self) -> bool {
+        self.sign_positive
+    }
+    pub fn clear_sign_positive(&mut self) {
+        self.sign_positive = false;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_sign_positive(&mut self, v: bool) {
+        self.sign_positive = v;
+    }
+
+    // string name = 5;
+
+
+    pub fn get_name(&self) -> &str {
+        &self.name
+    }
+    pub fn clear_name(&mut self) {
+        self.name.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_name(&mut self, v: ::std::string::String) {
+        self.name = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_name(&mut self) -> &mut ::std::string::String {
+        &mut self.name
+    }
+
+    // Take field
+    pub fn take_name(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.name, ::std::string::String::new())
+    }
+}
+
+impl ::protobuf::Message for NumberDescription {
+    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_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.money, 1, &mut self.unknown_fields)?
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_uint32()?;
+                    self.scale = tmp;
+                },
+                3 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.symbol)?;
+                },
+                4 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_bool()?;
+                    self.sign_positive = tmp;
+                },
+                5 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if self.money != FlowyMoney::CNY {
+            my_size += ::protobuf::rt::enum_size(1, self.money);
+        }
+        if self.scale != 0 {
+            my_size += ::protobuf::rt::value_size(2, self.scale, ::protobuf::wire_format::WireTypeVarint);
+        }
+        if !self.symbol.is_empty() {
+            my_size += ::protobuf::rt::string_size(3, &self.symbol);
+        }
+        if self.sign_positive != false {
+            my_size += 2;
+        }
+        if !self.name.is_empty() {
+            my_size += ::protobuf::rt::string_size(5, &self.name);
+        }
+        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.money != FlowyMoney::CNY {
+            os.write_enum(1, ::protobuf::ProtobufEnum::value(&self.money))?;
+        }
+        if self.scale != 0 {
+            os.write_uint32(2, self.scale)?;
+        }
+        if !self.symbol.is_empty() {
+            os.write_string(3, &self.symbol)?;
+        }
+        if self.sign_positive != false {
+            os.write_bool(4, self.sign_positive)?;
+        }
+        if !self.name.is_empty() {
+            os.write_string(5, &self.name)?;
+        }
+        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() -> NumberDescription {
+        NumberDescription::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::ProtobufTypeEnum<FlowyMoney>>(
+                "money",
+                |m: &NumberDescription| { &m.money },
+                |m: &mut NumberDescription| { &mut m.money },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeUint32>(
+                "scale",
+                |m: &NumberDescription| { &m.scale },
+                |m: &mut NumberDescription| { &mut m.scale },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "symbol",
+                |m: &NumberDescription| { &m.symbol },
+                |m: &mut NumberDescription| { &mut m.symbol },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
+                "sign_positive",
+                |m: &NumberDescription| { &m.sign_positive },
+                |m: &mut NumberDescription| { &mut m.sign_positive },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "name",
+                |m: &NumberDescription| { &m.name },
+                |m: &mut NumberDescription| { &mut m.name },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<NumberDescription>(
+                "NumberDescription",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static NumberDescription {
+        static instance: ::protobuf::rt::LazyV2<NumberDescription> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(NumberDescription::new)
+    }
+}
+
+impl ::protobuf::Clear for NumberDescription {
+    fn clear(&mut self) {
+        self.money = FlowyMoney::CNY;
+        self.scale = 0;
+        self.symbol.clear();
+        self.sign_positive = false;
+        self.name.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for NumberDescription {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for NumberDescription {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(Clone,PartialEq,Eq,Debug,Hash)]
+pub enum DateFormat {
+    Local = 0,
+    US = 1,
+    ISO = 2,
+    Friendly = 3,
+}
+
+impl ::protobuf::ProtobufEnum for DateFormat {
+    fn value(&self) -> i32 {
+        *self as i32
+    }
+
+    fn from_i32(value: i32) -> ::std::option::Option<DateFormat> {
+        match value {
+            0 => ::std::option::Option::Some(DateFormat::Local),
+            1 => ::std::option::Option::Some(DateFormat::US),
+            2 => ::std::option::Option::Some(DateFormat::ISO),
+            3 => ::std::option::Option::Some(DateFormat::Friendly),
+            _ => ::std::option::Option::None
+        }
+    }
+
+    fn values() -> &'static [Self] {
+        static values: &'static [DateFormat] = &[
+            DateFormat::Local,
+            DateFormat::US,
+            DateFormat::ISO,
+            DateFormat::Friendly,
+        ];
+        values
+    }
+
+    fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            ::protobuf::reflect::EnumDescriptor::new_pb_name::<DateFormat>("DateFormat", file_descriptor_proto())
+        })
+    }
+}
+
+impl ::std::marker::Copy for DateFormat {
+}
+
+impl ::std::default::Default for DateFormat {
+    fn default() -> Self {
+        DateFormat::Local
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for DateFormat {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
+    }
+}
+
+#[derive(Clone,PartialEq,Eq,Debug,Hash)]
+pub enum TimeFormat {
+    TwelveHour = 0,
+    TwentyFourHour = 1,
+}
+
+impl ::protobuf::ProtobufEnum for TimeFormat {
+    fn value(&self) -> i32 {
+        *self as i32
+    }
+
+    fn from_i32(value: i32) -> ::std::option::Option<TimeFormat> {
+        match value {
+            0 => ::std::option::Option::Some(TimeFormat::TwelveHour),
+            1 => ::std::option::Option::Some(TimeFormat::TwentyFourHour),
+            _ => ::std::option::Option::None
+        }
+    }
+
+    fn values() -> &'static [Self] {
+        static values: &'static [TimeFormat] = &[
+            TimeFormat::TwelveHour,
+            TimeFormat::TwentyFourHour,
+        ];
+        values
+    }
+
+    fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            ::protobuf::reflect::EnumDescriptor::new_pb_name::<TimeFormat>("TimeFormat", file_descriptor_proto())
+        })
+    }
+}
+
+impl ::std::marker::Copy for TimeFormat {
+}
+
+impl ::std::default::Default for TimeFormat {
+    fn default() -> Self {
+        TimeFormat::TwelveHour
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for TimeFormat {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
+    }
+}
+
+#[derive(Clone,PartialEq,Eq,Debug,Hash)]
+pub enum FlowyMoney {
+    CNY = 0,
+    EUR = 1,
+    USD = 2,
+}
+
+impl ::protobuf::ProtobufEnum for FlowyMoney {
+    fn value(&self) -> i32 {
+        *self as i32
+    }
+
+    fn from_i32(value: i32) -> ::std::option::Option<FlowyMoney> {
+        match value {
+            0 => ::std::option::Option::Some(FlowyMoney::CNY),
+            1 => ::std::option::Option::Some(FlowyMoney::EUR),
+            2 => ::std::option::Option::Some(FlowyMoney::USD),
+            _ => ::std::option::Option::None
+        }
+    }
+
+    fn values() -> &'static [Self] {
+        static values: &'static [FlowyMoney] = &[
+            FlowyMoney::CNY,
+            FlowyMoney::EUR,
+            FlowyMoney::USD,
+        ];
+        values
+    }
+
+    fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            ::protobuf::reflect::EnumDescriptor::new_pb_name::<FlowyMoney>("FlowyMoney", file_descriptor_proto())
+        })
+    }
+}
+
+impl ::std::marker::Copy for FlowyMoney {
+}
+
+impl ::std::default::Default for FlowyMoney {
+    fn default() -> Self {
+        FlowyMoney::CNY
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for FlowyMoney {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
+    }
+}
+
+static file_descriptor_proto_data: &'static [u8] = b"\
+    \n\x0fcell_data.proto\"-\n\x13RichTextDescription\x12\x16\n\x06format\
+    \x18\x01\x20\x01(\tR\x06format\"6\n\x13CheckboxDescription\x12\x1f\n\x0b\
+    is_selected\x18\x01\x20\x01(\x08R\nisSelected\"m\n\x0fDateDescription\
+    \x12,\n\x0bdate_format\x18\x01\x20\x01(\x0e2\x0b.DateFormatR\ndateFormat\
+    \x12,\n\x0btime_format\x18\x02\x20\x01(\x0e2\x0b.TimeFormatR\ntimeFormat\
+    \"\\\n\x0cSingleSelect\x12'\n\x07options\x18\x01\x20\x03(\x0b2\r.SelectO\
+    ptionR\x07options\x12#\n\rdisable_color\x18\x02\x20\x01(\x08R\x0cdisable\
+    Color\"[\n\x0bMultiSelect\x12'\n\x07options\x18\x01\x20\x03(\x0b2\r.Sele\
+    ctOptionR\x07options\x12#\n\rdisable_color\x18\x02\x20\x01(\x08R\x0cdisa\
+    bleColor\"H\n\x0cSelectOption\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\
+    \x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x14\n\x05color\x18\
+    \x03\x20\x01(\tR\x05color\"\x9d\x01\n\x11NumberDescription\x12!\n\x05mon\
+    ey\x18\x01\x20\x01(\x0e2\x0b.FlowyMoneyR\x05money\x12\x14\n\x05scale\x18\
+    \x02\x20\x01(\rR\x05scale\x12\x16\n\x06symbol\x18\x03\x20\x01(\tR\x06sym\
+    bol\x12#\n\rsign_positive\x18\x04\x20\x01(\x08R\x0csignPositive\x12\x12\
+    \n\x04name\x18\x05\x20\x01(\tR\x04name*6\n\nDateFormat\x12\t\n\x05Local\
+    \x10\0\x12\x06\n\x02US\x10\x01\x12\x07\n\x03ISO\x10\x02\x12\x0c\n\x08Fri\
+    endly\x10\x03*0\n\nTimeFormat\x12\x0e\n\nTwelveHour\x10\0\x12\x12\n\x0eT\
+    wentyFourHour\x10\x01*'\n\nFlowyMoney\x12\x07\n\x03CNY\x10\0\x12\x07\n\
+    \x03EUR\x10\x01\x12\x07\n\x03USD\x10\x02b\x06proto3\
+";
+
+static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
+
+fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
+    ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
+}
+
+pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
+    file_descriptor_proto_lazy.get(|| {
+        parse_descriptor_proto()
+    })
+}

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

@@ -27,6 +27,9 @@
 pub enum GridEvent {
     CreateGrid = 0,
     OpenGrid = 1,
+    GetRows = 2,
+    GetFields = 3,
+    CreateRow = 4,
 }
 
 impl ::protobuf::ProtobufEnum for GridEvent {
@@ -38,6 +41,9 @@ impl ::protobuf::ProtobufEnum for GridEvent {
         match value {
             0 => ::std::option::Option::Some(GridEvent::CreateGrid),
             1 => ::std::option::Option::Some(GridEvent::OpenGrid),
+            2 => ::std::option::Option::Some(GridEvent::GetRows),
+            3 => ::std::option::Option::Some(GridEvent::GetFields),
+            4 => ::std::option::Option::Some(GridEvent::CreateRow),
             _ => ::std::option::Option::None
         }
     }
@@ -46,6 +52,9 @@ impl ::protobuf::ProtobufEnum for GridEvent {
         static values: &'static [GridEvent] = &[
             GridEvent::CreateGrid,
             GridEvent::OpenGrid,
+            GridEvent::GetRows,
+            GridEvent::GetFields,
+            GridEvent::CreateRow,
         ];
         values
     }
@@ -74,8 +83,9 @@ impl ::protobuf::reflect::ProtobufValue for GridEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0fevent_map.proto*)\n\tGridEvent\x12\x0e\n\nCreateGrid\x10\0\x12\x0c\
-    \n\x08OpenGrid\x10\x01b\x06proto3\
+    \n\x0fevent_map.proto*T\n\tGridEvent\x12\x0e\n\nCreateGrid\x10\0\x12\x0c\
+    \n\x08OpenGrid\x10\x01\x12\x0b\n\x07GetRows\x10\x02\x12\r\n\tGetFields\
+    \x10\x03\x12\r\n\tCreateRow\x10\x04b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 3 - 0
frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs

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

+ 47 - 0
frontend/rust-lib/flowy-grid/src/protobuf/proto/cell_data.proto

@@ -0,0 +1,47 @@
+syntax = "proto3";
+
+message RichTextDescription {
+    string format = 1;
+}
+message CheckboxDescription {
+    bool is_selected = 1;
+}
+message DateDescription {
+    DateFormat date_format = 1;
+    TimeFormat time_format = 2;
+}
+message SingleSelect {
+    repeated SelectOption options = 1;
+    bool disable_color = 2;
+}
+message MultiSelect {
+    repeated SelectOption options = 1;
+    bool disable_color = 2;
+}
+message SelectOption {
+    string id = 1;
+    string name = 2;
+    string color = 3;
+}
+message NumberDescription {
+    FlowyMoney money = 1;
+    uint32 scale = 2;
+    string symbol = 3;
+    bool sign_positive = 4;
+    string name = 5;
+}
+enum DateFormat {
+    Local = 0;
+    US = 1;
+    ISO = 2;
+    Friendly = 3;
+}
+enum TimeFormat {
+    TwelveHour = 0;
+    TwentyFourHour = 1;
+}
+enum FlowyMoney {
+    CNY = 0;
+    EUR = 1;
+    USD = 2;
+}

+ 3 - 0
frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto

@@ -3,4 +3,7 @@ syntax = "proto3";
 enum GridEvent {
     CreateGrid = 0;
     OpenGrid = 1;
+    GetRows = 2;
+    GetFields = 3;
+    CreateRow = 4;
 }

+ 2 - 0
shared-lib/Cargo.lock

@@ -486,6 +486,8 @@ dependencies = [
  "flowy-derive",
  "lib-infra",
  "protobuf",
+ "strum",
+ "strum_macros",
 ]
 
 [[package]]

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

@@ -9,6 +9,8 @@ edition = "2021"
 flowy-derive = { path = "../flowy-derive" }
 protobuf = {version = "2.18.0"}
 bytes = "1.0"
+strum = "0.21"
+strum_macros = "0.21"
 
 [build-dependencies]
 lib-infra = { path = "../lib-infra", features = ["protobuf_file_gen"] }

+ 65 - 25
shared-lib/flowy-grid-data-model/src/entities/grid.rs

@@ -1,6 +1,8 @@
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use std::collections::HashMap;
 
+use strum_macros::{Display, EnumIter, EnumString};
+
 #[derive(Debug, Default, ProtoBuf)]
 pub struct Grid {
     #[pb(index = 1)]
@@ -41,9 +43,6 @@ pub struct FieldOrder {
 
     #[pb(index = 2)]
     pub visibility: bool,
-
-    #[pb(index = 3)]
-    pub width: i32,
 }
 
 #[derive(Debug, Default, ProtoBuf)]
@@ -53,7 +52,6 @@ pub struct RepeatedFieldOrder {
 }
 
 #[derive(Debug, Default, ProtoBuf)]
-
 pub struct Field {
     #[pb(index = 1)]
     pub id: String,
@@ -71,10 +69,19 @@ pub struct Field {
     pub frozen: bool,
 
     #[pb(index = 6)]
+    pub width: i32,
+
+    #[pb(index = 7)]
     pub type_options: AnyData,
 }
 
-#[derive(Debug, ProtoBuf_Enum)]
+#[derive(Debug, Default, ProtoBuf)]
+pub struct RepeatedField {
+    #[pb(index = 1)]
+    pub items: Vec<Field>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum, EnumString, EnumIter, Display)]
 pub enum FieldType {
     RichText = 0,
     Number = 1,
@@ -90,15 +97,57 @@ impl std::default::Default for FieldType {
     }
 }
 
+impl FieldType {
+    #[allow(dead_code)]
+    pub fn type_id(&self) -> String {
+        let ty = self.clone();
+        format!("{}", ty as u8)
+    }
+
+    pub fn from_type_id(type_id: &str) -> Result<FieldType, String> {
+        match type_id {
+            "0" => Ok(FieldType::RichText),
+            "1" => Ok(FieldType::Number),
+            "2" => Ok(FieldType::DateTime),
+            "3" => Ok(FieldType::SingleSelect),
+            "4" => Ok(FieldType::MultiSelect),
+            "5" => Ok(FieldType::Checkbox),
+            _ => Err(format!("Invalid type_id: {}", type_id)),
+        }
+    }
+}
+
 #[derive(Debug, Default, ProtoBuf)]
 pub struct AnyData {
     #[pb(index = 1)]
-    pub type_url: String,
+    pub type_id: String,
 
     #[pb(index = 2)]
     pub value: Vec<u8>,
 }
 
+impl AnyData {
+    pub fn from_str(field_type: &FieldType, s: &str) -> AnyData {
+        Self::from_bytes(field_type, s.as_bytes().to_vec())
+    }
+
+    pub fn from_bytes<T: AsRef<[u8]>>(field_type: &FieldType, bytes: T) -> AnyData {
+        AnyData {
+            type_id: field_type.type_id(),
+            value: bytes.as_ref().to_vec(),
+        }
+    }
+}
+
+impl ToString for AnyData {
+    fn to_string(&self) -> String {
+        match String::from_utf8(self.value.clone()) {
+            Ok(s) => s,
+            Err(_) => "".to_owned(),
+        }
+    }
+}
+
 #[derive(Debug, Default, ProtoBuf)]
 pub struct RowOrder {
     #[pb(index = 1)]
@@ -118,7 +167,7 @@ pub struct RepeatedRowOrder {
 }
 
 #[derive(Debug, Default, ProtoBuf)]
-pub struct Row {
+pub struct GridRow {
     #[pb(index = 1)]
     pub id: String,
 
@@ -129,37 +178,28 @@ pub struct Row {
     pub modified_time: i64,
 
     #[pb(index = 4)]
-    pub cell_by_field_id: HashMap<String, Cell>,
+    pub cell_by_field_id: HashMap<String, GridCell>,
 }
 
 #[derive(Debug, Default, ProtoBuf)]
-pub struct Cell {
+pub struct RepeatedRow {
     #[pb(index = 1)]
-    pub id: String,
-
-    #[pb(index = 2)]
-    pub row_id: String,
-
-    #[pb(index = 3)]
-    pub field_id: String,
+    pub items: Vec<GridRow>,
 }
 
 #[derive(Debug, Default, ProtoBuf)]
-pub struct DisplayCell {
+pub struct GridCell {
     #[pb(index = 1)]
     pub id: String,
 
     #[pb(index = 2)]
-    pub content: String,
-}
+    pub row_id: String,
 
-#[derive(Debug, Default, ProtoBuf)]
-pub struct RawCell {
-    #[pb(index = 1)]
-    pub id: String,
+    #[pb(index = 3)]
+    pub field_id: String,
 
-    #[pb(index = 2)]
-    pub data: AnyData,
+    #[pb(index = 4)]
+    pub content: String,
 }
 
 #[derive(ProtoBuf, Default)]

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 365 - 435
shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs


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

@@ -17,7 +17,6 @@ message RepeatedGridFilter {
 message FieldOrder {
     string field_id = 1;
     bool visibility = 2;
-    int32 width = 3;
 }
 message RepeatedFieldOrder {
     repeated FieldOrder items = 1;
@@ -28,10 +27,14 @@ message Field {
     string desc = 3;
     FieldType field_type = 4;
     bool frozen = 5;
-    AnyData type_options = 6;
+    int32 width = 6;
+    AnyData type_options = 7;
+}
+message RepeatedField {
+    repeated Field items = 1;
 }
 message AnyData {
-    string type_url = 1;
+    string type_id = 1;
     bytes value = 2;
 }
 message RowOrder {
@@ -42,24 +45,20 @@ message RowOrder {
 message RepeatedRowOrder {
     repeated RowOrder items = 1;
 }
-message Row {
+message GridRow {
     string id = 1;
     string grid_id = 2;
     int64 modified_time = 3;
-    map<string, Cell> cell_by_field_id = 4;
+    map<string, GridCell> cell_by_field_id = 4;
+}
+message RepeatedRow {
+    repeated GridRow items = 1;
 }
-message Cell {
+message GridCell {
     string id = 1;
     string row_id = 2;
     string field_id = 3;
-}
-message DisplayCell {
-    string id = 1;
-    string content = 2;
-}
-message RawCell {
-    string id = 1;
-    AnyData data = 2;
+    string content = 4;
 }
 message CreateGridPayload {
     string name = 1;

Vissa filer visades inte eftersom för många filer har ändrats