Parcourir la source

feat: save text cell data

appflowy il y a 3 ans
Parent
commit
1f30a77f1d
32 fichiers modifiés avec 473 ajouts et 405 suppressions
  1. 0 6
      frontend/app_flowy/lib/plugin/plugin.dart
  2. 7 7
      frontend/app_flowy/lib/workspace/application/appearance.dart
  3. 1 0
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/text_cell_bloc.dart
  4. 24 32
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  5. 37 19
      frontend/app_flowy/lib/workspace/application/grid/grid_block_service.dart
  6. 2 2
      frontend/app_flowy/lib/workspace/application/grid/grid_service.dart
  7. 17 17
      frontend/app_flowy/lib/workspace/application/grid/row_bloc.dart
  8. 4 4
      frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart
  9. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/blank/blank.dart
  10. 4 80
      frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart
  11. 3 3
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/grid.dart
  12. 37 35
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
  13. 69 70
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_row.dart
  14. 30 9
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/text_cell.dart
  15. 11 7
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header.dart
  16. 74 0
      frontend/app_flowy/lib/workspace/presentation/plugins/widgets/left_bar_item.dart
  17. 20 1
      frontend/app_flowy/packages/flowy_infra/lib/notifier.dart
  18. 8 8
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
  19. 2 2
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
  20. 4 4
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/dart_notification.pbenum.dart
  21. 4 4
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/dart_notification.pbjson.dart
  22. 5 3
      frontend/rust-lib/flowy-grid/src/dart_notification.rs
  23. 1 1
      frontend/rust-lib/flowy-grid/src/manager.rs
  24. 11 11
      frontend/rust-lib/flowy-grid/src/protobuf/model/dart_notification.rs
  25. 3 3
      frontend/rust-lib/flowy-grid/src/protobuf/proto/dart_notification.proto
  26. 22 13
      frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs
  27. 3 3
      frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs
  28. 2 0
      frontend/rust-lib/flowy-revision/src/rev_manager.rs
  29. 8 2
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  30. 39 39
      shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
  31. 1 1
      shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto
  32. 19 18
      shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs

+ 0 - 6
frontend/app_flowy/lib/plugin/plugin.dart

@@ -61,12 +61,6 @@ abstract class PluginConfig {
 }
 
 abstract class PluginDisplay<T> with NavigationItem {
-  @override
-  Widget get leftBarItem;
-
-  @override
-  Widget? get rightBarItem;
-
   List<NavigationItem> get navigationItems;
 
   PublishNotifier<T>? get notifier => null;

+ 7 - 7
frontend/app_flowy/lib/workspace/application/appearance.dart

@@ -1,3 +1,5 @@
+import 'dart:async';
+
 import 'package:app_flowy/user/application/user_settings_service.dart';
 import 'package:equatable/equatable.dart';
 import 'package:flowy_infra/theme.dart';
@@ -11,7 +13,7 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
   AppearanceSettings setting;
   AppTheme _theme;
   Locale _locale;
-  CancelableOperation? _saveOperation;
+  Timer? _saveOperation;
 
   AppearanceSettingModel(this.setting)
       : _theme = AppTheme.fromName(name: setting.theme),
@@ -21,12 +23,10 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
   Locale get locale => _locale;
 
   Future<void> save() async {
-    _saveOperation?.cancel;
-    _saveOperation = CancelableOperation.fromFuture(
-      Future.delayed(const Duration(seconds: 1), () async {
-        await UserSettingsService().setAppearanceSettings(setting);
-      }),
-    );
+    _saveOperation?.cancel();
+    _saveOperation = Timer(const Duration(seconds: 2), () async {
+      await UserSettingsService().setAppearanceSettings(setting);
+    });
   }
 
   @override

+ 1 - 0
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/text_cell_bloc.dart

@@ -17,6 +17,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
           initial: (_InitialCell value) async {},
           updateText: (_UpdateText value) {
             service.updateCell(data: value.text);
+            emit(state.copyWith(content: value.text));
           },
         );
       },

+ 24 - 32
frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart

@@ -1,5 +1,6 @@
 import 'dart:async';
 import 'package:dartz/dartz.dart';
+import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
@@ -32,7 +33,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
           delete: (_Delete value) {},
           rename: (_Rename value) {},
           updateDesc: (_Desc value) {},
-          didLoadRows: (_DidLoadRows value) {
+          rowsDidUpdate: (_RowsDidUpdate value) {
             emit(state.copyWith(rows: value.rows));
           },
         );
@@ -47,10 +48,19 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     return super.close();
   }
 
-  Future<void> _startGridListening() async {
-    _blockService.didLoadRowscallback = (rows) {
-      add(GridEvent.didLoadRows(rows));
-    };
+  Future<void> _initGridBlockService(Grid grid, List<Field> fields) async {
+    _blockService = GridBlockService(
+      gridId: grid.id,
+      fields: fields,
+      blockOrders: grid.blockOrders,
+    );
+
+    _blockService.rowsUpdateNotifier.addPublishListener((result) {
+      result.fold(
+        (rows) => add(GridEvent.rowsDidUpdate(rows)),
+        (err) => Log.error('$err'),
+      );
+    });
 
     _gridListener.start();
   }
@@ -70,36 +80,18 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     final result = await service.getFields(gridId: grid.id, fieldOrders: grid.fieldOrders);
     return Future(
       () => result.fold(
-        (fields) => _loadGridBlocks(grid, fields.items, emit),
+        (fields) {
+          _initGridBlockService(grid, fields.items);
+          emit(state.copyWith(
+            grid: Some(grid),
+            fields: Some(fields.items),
+            loadingState: GridLoadingState.finish(left(unit)),
+          ));
+        },
         (err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
       ),
     );
   }
-
-  Future<void> _loadGridBlocks(Grid grid, List<Field> fields, Emitter<GridState> emit) async {
-    final result = await service.getGridBlocks(gridId: grid.id, blockOrders: grid.blockOrders);
-    result.fold(
-      (repeatedGridBlock) {
-        final gridBlocks = repeatedGridBlock.items;
-        final gridId = view.id;
-        _blockService = GridBlockService(
-          gridId: gridId,
-          fields: fields,
-          gridBlocks: gridBlocks,
-        );
-        final rows = _blockService.rows();
-
-        _startGridListening();
-        emit(state.copyWith(
-          grid: Some(grid),
-          fields: Some(fields),
-          rows: rows,
-          loadingState: GridLoadingState.finish(left(unit)),
-        ));
-      },
-      (err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)), rows: [])),
-    );
-  }
 }
 
 @freezed
@@ -109,7 +101,7 @@ abstract class GridEvent with _$GridEvent {
   const factory GridEvent.updateDesc(String gridId, String desc) = _Desc;
   const factory GridEvent.delete(String gridId) = _Delete;
   const factory GridEvent.createRow() = _CreateRow;
-  const factory GridEvent.didLoadRows(List<GridRowData> rows) = _DidLoadRows;
+  const factory GridEvent.rowsDidUpdate(List<GridRowData> rows) = _RowsDidUpdate;
 }
 
 @freezed

+ 37 - 19
frontend/app_flowy/lib/workspace/application/grid/grid_block_service.dart

@@ -1,5 +1,7 @@
 import 'dart:collection';
 import 'package:dartz/dartz.dart';
+import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flowy_sdk/protobuf/dart-notify/subject.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
@@ -9,40 +11,38 @@ import 'package:flowy_infra/notifier.dart';
 import 'dart:async';
 import 'dart:typed_data';
 import 'package:app_flowy/core/notification_helper.dart';
-
 import 'grid_service.dart';
 
-typedef DidLoadRowsCallback = void Function(List<GridRowData>);
-typedef GridBlockUpdateNotifiedValue = Either<GridBlockId, FlowyError>;
+typedef RowsUpdateNotifierValue = Either<List<GridRowData>, FlowyError>;
 
 class GridBlockService {
   String gridId;
   List<Field> fields;
   LinkedHashMap<String, GridBlock> blockMap = LinkedHashMap();
   late GridBlockListener _blockListener;
-  DidLoadRowsCallback? didLoadRowscallback;
+  PublishNotifier<RowsUpdateNotifierValue> rowsUpdateNotifier = PublishNotifier<RowsUpdateNotifierValue>();
 
-  GridBlockService({required this.gridId, required this.fields, required List<GridBlock> gridBlocks}) {
-    for (final gridBlock in gridBlocks) {
-      blockMap[gridBlock.blockId] = gridBlock;
-    }
+  GridBlockService({required this.gridId, required this.fields, required List<GridBlockOrder> blockOrders}) {
+    _loadGridBlocks(blockOrders: blockOrders);
 
     _blockListener = GridBlockListener(gridId: gridId);
-    _blockListener.blockUpdateNotifier.addPublishListener((result) {
-      result.fold((blockId) {
-        //
-      }, (err) => null);
+    _blockListener.rowsUpdateNotifier.addPublishListener((result) {
+      result.fold(
+        (blockId) => _loadGridBlocks(blockOrders: [GridBlockOrder.create()..blockId = blockId.value]),
+        (err) => Log.error(err),
+      );
     });
+    _blockListener.start();
   }
 
-  List<GridRowData> rows() {
+  List<GridRowData> buildRows() {
     List<GridRowData> rows = [];
     blockMap.forEach((_, GridBlock gridBlock) {
       rows.addAll(gridBlock.rowOrders.map(
         (rowOrder) => GridRowData(
           gridId: gridId,
           fields: fields,
-          blockId: gridBlock.blockId,
+          blockId: gridBlock.id,
           rowId: rowOrder.rowId,
           height: rowOrder.height.toDouble(),
         ),
@@ -54,11 +54,29 @@ class GridBlockService {
   Future<void> stop() async {
     await _blockListener.stop();
   }
+
+  void _loadGridBlocks({required List<GridBlockOrder> blockOrders}) {
+    final payload = QueryGridBlocksPayload.create()
+      ..gridId = gridId
+      ..blockOrders.addAll(blockOrders);
+
+    GridEventGetGridBlocks(payload).send().then((result) {
+      result.fold(
+        (repeatedBlocks) {
+          for (final gridBlock in repeatedBlocks.items) {
+            blockMap[gridBlock.id] = gridBlock;
+          }
+          rowsUpdateNotifier.value = left(buildRows());
+        },
+        (err) => rowsUpdateNotifier.value = right(err),
+      );
+    });
+  }
 }
 
 class GridBlockListener {
   final String gridId;
-  PublishNotifier<GridBlockUpdateNotifiedValue> blockUpdateNotifier = PublishNotifier<GridBlockUpdateNotifiedValue>();
+  PublishNotifier<Either<GridBlockId, FlowyError>> rowsUpdateNotifier = PublishNotifier(comparable: null);
   StreamSubscription<SubscribeObject>? _subscription;
   late GridNotificationParser _parser;
 
@@ -77,10 +95,10 @@ class GridBlockListener {
 
   void _handleObservableType(GridNotification ty, Either<Uint8List, FlowyError> result) {
     switch (ty) {
-      case GridNotification.GridDidUpdateBlock:
+      case GridNotification.BlockDidUpdateRow:
         result.fold(
-          (payload) => blockUpdateNotifier.value = left(GridBlockId.fromBuffer(payload)),
-          (error) => blockUpdateNotifier.value = right(error),
+          (payload) => rowsUpdateNotifier.value = left(GridBlockId.fromBuffer(payload)),
+          (error) => rowsUpdateNotifier.value = right(error),
         );
         break;
 
@@ -91,6 +109,6 @@ class GridBlockListener {
 
   Future<void> stop() async {
     await _subscription?.cancel();
-    blockUpdateNotifier.dispose();
+    rowsUpdateNotifier.dispose();
   }
 }

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

@@ -13,9 +13,9 @@ class GridService {
     return GridEventGetGridData(payload).send();
   }
 
-  Future<Either<Row, FlowyError>> createRow({required String gridId, Option<String>? upperRowId}) {
+  Future<Either<Row, FlowyError>> createRow({required String gridId, Option<String>? startRowId}) {
     CreateRowPayload payload = CreateRowPayload.create()..gridId = gridId;
-    upperRowId?.fold(() => null, (id) => payload.startRowId = id);
+    startRowId?.fold(() => null, (id) => payload.startRowId = id);
     return GridEventCreateRow(payload).send();
   }
 

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

@@ -19,7 +19,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
         await event.map(
           initial: (_InitialRow value) async {
             _startRowListening();
-            await _loadCellDatas(emit);
+            await _loadRow(emit);
           },
           createRow: (_CreateRow value) {
             rowService.createRow();
@@ -58,20 +58,20 @@ class RowBloc extends Bloc<RowEvent, RowState> {
     listener.start();
   }
 
-  Future<void> _loadCellDatas(Emitter<RowState> emit) async {
-    final result = await rowService.getRow();
-    result.fold(
-      (row) {
-        emit(state.copyWith(
-          cellDatas: makeGridCellDatas(row),
-          rowHeight: row.height.toDouble(),
-        ));
-      },
-      (e) => Log.error(e),
-    );
+  Future<void> _loadRow(Emitter<RowState> emit) async {
+    final Future<List<GridCellData>> cellDatas = rowService.getRow().then((result) {
+      return result.fold(
+        (row) => _makeCellDatas(row),
+        (e) {
+          Log.error(e);
+          return [];
+        },
+      );
+    });
+    emit(state.copyWith(cellDatas: cellDatas));
   }
 
-  List<GridCellData> makeGridCellDatas(Row row) {
+  List<GridCellData> _makeCellDatas(Row row) {
     return rowService.rowData.fields.map((field) {
       final cell = row.cellByFieldId[field.id];
       final rowData = rowService.rowData;
@@ -96,18 +96,18 @@ abstract class RowEvent with _$RowEvent {
 }
 
 @freezed
-abstract class RowState with _$RowState {
+class RowState with _$RowState {
   const factory RowState({
     required String rowId,
     required double rowHeight,
-    required List<GridCellData> cellDatas,
+    required Future<List<GridCellData>> cellDatas,
     required bool active,
   }) = _RowState;
 
   factory RowState.initial(GridRowData data) => RowState(
         rowId: data.rowId,
-        active: false,
         rowHeight: data.height,
-        cellDatas: [],
+        cellDatas: Future(() => []),
+        active: false,
       );
 }

+ 4 - 4
frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart

@@ -1,3 +1,5 @@
+import 'dart:async';
+
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/view/view_ext.dart';
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
@@ -64,7 +66,7 @@ class ViewSectionNotifier with ChangeNotifier {
   bool isDisposed = false;
   List<View> _views;
   View? _selectedView;
-  CancelableOperation? _notifyListenerOperation;
+  Timer? _notifyListenerOperation;
 
   ViewSectionNotifier({
     required BuildContext context,
@@ -120,9 +122,7 @@ class ViewSectionNotifier with ChangeNotifier {
 
   void _notifyListeners() {
     _notifyListenerOperation?.cancel();
-    _notifyListenerOperation = CancelableOperation.fromFuture(
-      Future.delayed(const Duration(milliseconds: 30), () {}),
-    ).then((_) {
+    _notifyListenerOperation = Timer(const Duration(milliseconds: 30), () {
       if (!isDisposed) {
         notifyListeners();
       }

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

@@ -40,7 +40,7 @@ class BlankPagePlugin extends Plugin {
   PluginType get ty => _pluginType;
 }
 
-class BlankPagePluginDisplay extends PluginDisplay {
+class BlankPagePluginDisplay extends PluginDisplay with NavigationItem {
   @override
   Widget get leftBarItem => FlowyText.medium(LocaleKeys.blankPageTitle.tr(), fontSize: 12);
 

+ 4 - 80
frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart

@@ -10,14 +10,13 @@ import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/appearance.dart';
 import 'package:app_flowy/workspace/application/doc/share_bloc.dart';
 import 'package:app_flowy/workspace/application/view/view_listener.dart';
-import 'package:app_flowy/workspace/application/view/view_service.dart';
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
+import 'package:app_flowy/workspace/presentation/plugins/widgets/left_bar_item.dart';
 import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
 import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/notifier.dart';
 import 'package:flowy_infra/size.dart';
-import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/widget/rounded_button.dart';
 import 'package:flowy_sdk/log.dart';
@@ -89,7 +88,7 @@ class DocumentPlugin implements Plugin {
   PluginId get id => _view.id;
 }
 
-class DocumentPluginDisplay extends PluginDisplay<int> {
+class DocumentPluginDisplay extends PluginDisplay<int> with NavigationItem {
   final PublishNotifier<int> _displayNotifier = PublishNotifier<int>();
   final View _view;
 
@@ -99,91 +98,16 @@ class DocumentPluginDisplay extends PluginDisplay<int> {
   Widget buildWidget() => DocumentPage(view: _view, key: ValueKey(_view.id));
 
   @override
-  Widget get leftBarItem => DocumentLeftBarItem(view: _view);
+  Widget get leftBarItem => ViewLeftBarItem(view: _view);
 
   @override
   Widget? get rightBarItem => DocumentShareButton(view: _view);
 
   @override
-  List<NavigationItem> get navigationItems => _makeNavigationItems();
+  List<NavigationItem> get navigationItems => [this];
 
   @override
   PublishNotifier<int>? get notifier => _displayNotifier;
-
-  List<NavigationItem> _makeNavigationItems() {
-    return [
-      this,
-    ];
-  }
-}
-
-class DocumentLeftBarItem extends StatefulWidget {
-  final View view;
-
-  DocumentLeftBarItem({required this.view, Key? key}) : super(key: ValueKey(view.hashCode));
-
-  @override
-  State<DocumentLeftBarItem> createState() => _DocumentLeftBarItemState();
-}
-
-class _DocumentLeftBarItemState extends State<DocumentLeftBarItem> {
-  final _controller = TextEditingController();
-  final _focusNode = FocusNode();
-  late ViewService service;
-
-  @override
-  void initState() {
-    service = ViewService(/*view: widget.view*/);
-    _focusNode.addListener(_handleFocusChanged);
-    super.initState();
-  }
-
-  @override
-  void dispose() {
-    _controller.dispose();
-    _focusNode.removeListener(_handleFocusChanged);
-    _focusNode.dispose();
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    _controller.text = widget.view.name;
-
-    final theme = context.watch<AppTheme>();
-    return IntrinsicWidth(
-      key: ValueKey(_controller.text),
-      child: TextField(
-        controller: _controller,
-        focusNode: _focusNode,
-        scrollPadding: EdgeInsets.zero,
-        decoration: const InputDecoration(
-          contentPadding: EdgeInsets.zero,
-          border: InputBorder.none,
-          isDense: true,
-        ),
-        style: TextStyle(
-          color: theme.textColor,
-          fontSize: 14,
-          fontWeight: FontWeight.w500,
-          overflow: TextOverflow.ellipsis,
-        ),
-        // cursorColor: widget.cursorColor,
-        // obscureText: widget.enableObscure,
-      ),
-    );
-  }
-
-  void _handleFocusChanged() {
-    if (_controller.text.isEmpty) {
-      _controller.text = widget.view.name;
-      return;
-    }
-
-    if (_controller.text != widget.view.name) {
-      service.updateView(viewId: widget.view.id, name: _controller.text);
-    }
-  }
 }
 
 class DocumentShareButton extends StatelessWidget {

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

@@ -1,5 +1,5 @@
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
-import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:app_flowy/workspace/presentation/plugins/widgets/left_bar_item.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:app_flowy/plugin/plugin.dart';
 import 'package:flutter/material.dart';
@@ -28,7 +28,7 @@ class GridPluginBuilder implements PluginBuilder {
 
 class GridPluginConfig implements PluginConfig {
   @override
-  bool get creatable => false;
+  bool get creatable => true;
 }
 
 class GridPlugin extends Plugin {
@@ -56,7 +56,7 @@ class GridPluginDisplay extends PluginDisplay {
   GridPluginDisplay({required View view, Key? key}) : _view = view;
 
   @override
-  Widget get leftBarItem => const FlowyText.medium("Grid demo", fontSize: 12);
+  Widget get leftBarItem => ViewLeftBarItem(view: _view);
 
   @override
   Widget buildWidget() => GridPage(view: _view);

+ 37 - 35
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart

@@ -84,42 +84,38 @@ class _FlowyGridState extends State<FlowyGrid> {
   @override
   Widget build(BuildContext context) {
     return BlocBuilder<GridBloc, GridState>(
+      buildWhen: (previous, current) => previous.fields != current.fields,
       builder: (context, state) {
         return state.fields.fold(
           () => const Center(child: CircularProgressIndicator.adaptive()),
-          (fields) => _renderGrid(context, fields),
+          (fields) => _wrapScrollbar(fields, [
+            _buildHeader(fields),
+            _buildRows(context),
+            const GridFooter(),
+          ]),
         );
       },
     );
   }
 
-  Widget _renderGrid(BuildContext context, List<Field> fields) {
-    return Stack(
-      children: [
-        StyledSingleChildScrollView(
-          controller: _scrollController.horizontalController,
-          axis: Axis.horizontal,
-          child: SizedBox(
-            width: GridLayout.headerWidth(fields),
-            child: CustomScrollView(
-              physics: StyledScrollPhysics(),
-              controller: _scrollController.verticalController,
-              slivers: <Widget>[
-                _buildHeader(fields),
-                _buildRows(context),
-                const GridFooter(),
-              ],
-            ),
+  Widget _wrapScrollbar(List<Field> fields, List<Widget> children) {
+    return ScrollbarListStack(
+      axis: Axis.vertical,
+      controller: _scrollController.verticalController,
+      barSize: GridSize.scrollBarSize,
+      child: StyledSingleChildScrollView(
+        controller: _scrollController.horizontalController,
+        axis: Axis.horizontal,
+        child: SizedBox(
+          width: GridLayout.headerWidth(fields),
+          child: CustomScrollView(
+            physics: StyledScrollPhysics(),
+            controller: _scrollController.verticalController,
+            slivers: <Widget>[...children],
           ),
         ),
-        ScrollbarListStack(
-          axis: Axis.vertical,
-          controller: _scrollController.verticalController,
-          barSize: GridSize.scrollBarSize,
-          child: Container(),
-        ).padding(right: 0, top: GridSize.headerHeight, bottom: GridSize.scrollBarSize),
-      ],
-    );
+      ),
+    ).padding(right: 0, top: GridSize.headerHeight, bottom: GridSize.scrollBarSize);
   }
 
   Widget _buildHeader(List<Field> fields) {
@@ -131,15 +127,21 @@ class _FlowyGridState extends State<FlowyGrid> {
   }
 
   Widget _buildRows(BuildContext context) {
-    return SliverList(
-      delegate: SliverChildBuilderDelegate(
-        (context, index) {
-          final rowData = context.read<GridBloc>().state.rows[index];
-          return GridRowWidget(data: rowData);
-        },
-        childCount: context.read<GridBloc>().state.rows.length,
-        addRepaintBoundaries: true,
-      ),
+    return BlocBuilder<GridBloc, GridState>(
+      buildWhen: (previous, current) => previous.rows.length != current.rows.length,
+      builder: (context, state) {
+        return SliverList(
+          delegate: SliverChildBuilderDelegate(
+            (context, index) {
+              final rowData = context.read<GridBloc>().state.rows[index];
+              return GridRowWidget(data: rowData);
+            },
+            childCount: context.read<GridBloc>().state.rows.length,
+            addRepaintBoundaries: true,
+            addAutomaticKeepAlives: true,
+          ),
+        );
+      },
     );
   }
 }

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

@@ -11,7 +11,7 @@ import 'cell_container.dart';
 
 class GridRowWidget extends StatefulWidget {
   final GridRowData data;
-  GridRowWidget({required this.data, Key? key}) : super(key: ObjectKey(data.rowId));
+  GridRowWidget({required this.data, Key? key}) : super(key: ValueKey(data.rowId));
 
   @override
   State<GridRowWidget> createState() => _GridRowWidgetState();
@@ -30,28 +30,25 @@ class _GridRowWidgetState extends State<GridRowWidget> {
   Widget build(BuildContext context) {
     return BlocProvider.value(
       value: _rowBloc,
-      child: GestureDetector(
-        behavior: HitTestBehavior.translucent,
-        child: MouseRegion(
-          cursor: SystemMouseCursors.click,
-          onEnter: (p) => _rowBloc.add(const RowEvent.activeRow()),
-          onExit: (p) => _rowBloc.add(const RowEvent.disactiveRow()),
-          child: BlocBuilder<RowBloc, RowState>(
-            buildWhen: (p, c) => p.rowHeight != c.rowHeight,
-            builder: (context, state) {
-              return SizedBox(
-                height: _rowBloc.state.rowHeight,
-                child: Row(
-                  crossAxisAlignment: CrossAxisAlignment.stretch,
-                  children: [
-                    const LeadingRow(),
-                    _buildCells(),
-                    const TrailingRow(),
-                  ],
-                ),
-              );
-            },
-          ),
+      child: MouseRegion(
+        cursor: SystemMouseCursors.click,
+        onEnter: (p) => _rowBloc.add(const RowEvent.activeRow()),
+        onExit: (p) => _rowBloc.add(const RowEvent.disactiveRow()),
+        child: BlocBuilder<RowBloc, RowState>(
+          buildWhen: (p, c) => p.rowHeight != c.rowHeight,
+          builder: (context, state) {
+            return SizedBox(
+              height: _rowBloc.state.rowHeight,
+              child: Row(
+                crossAxisAlignment: CrossAxisAlignment.stretch,
+                children: const [
+                  _RowLeading(),
+                  _RowCells(),
+                  _RowTrailing(),
+                ],
+              ),
+            );
+          },
         ),
       ),
     );
@@ -62,69 +59,37 @@ class _GridRowWidgetState extends State<GridRowWidget> {
     _rowBloc.close();
     super.dispose();
   }
-
-  Widget _buildCells() {
-    return BlocBuilder<RowBloc, RowState>(
-      buildWhen: (p, c) => p.cellDatas != c.cellDatas,
-      builder: (context, state) {
-        return Row(
-          children: state.cellDatas
-              .map(
-                (cellData) => CellContainer(
-                  width: cellData.field.width.toDouble(),
-                  child: buildGridCell(cellData),
-                ),
-              )
-              .toList(),
-        );
-      },
-    );
-  }
 }
 
-class LeadingRow extends StatelessWidget {
-  const LeadingRow({Key? key}) : super(key: key);
+class _RowLeading extends StatelessWidget {
+  const _RowLeading({Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     return BlocSelector<RowBloc, RowState, bool>(
       selector: (state) => state.active,
       builder: (context, isActive) {
-        return SizedBox(
-          width: GridSize.leadingHeaderPadding,
-          child: isActive
-              ? Row(
-                  mainAxisAlignment: MainAxisAlignment.center,
-                  children: const [
-                    AppendRowButton(),
-                  ],
-                )
-              : null,
-        );
+        return SizedBox(width: GridSize.leadingHeaderPadding, child: isActive ? _activeWidget() : null);
       },
     );
   }
+
+  Widget _activeWidget() {
+    return Row(
+      mainAxisAlignment: MainAxisAlignment.center,
+      children: const [
+        AppendRowButton(),
+      ],
+    );
+  }
 }
 
-class TrailingRow extends StatelessWidget {
-  const TrailingRow({Key? key}) : super(key: key);
+class _RowTrailing extends StatelessWidget {
+  const _RowTrailing({Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    final borderSide = BorderSide(color: theme.shader4, width: 0.4);
-
-    return BlocBuilder<RowBloc, RowState>(
-      builder: (context, state) {
-        return Container(
-          width: GridSize.trailHeaderPadding,
-          decoration: BoxDecoration(
-            border: Border(bottom: borderSide),
-          ),
-          padding: GridSize.cellContentInsets,
-        );
-      },
-    );
+    return const SizedBox();
   }
 }
 
@@ -143,3 +108,37 @@ class AppendRowButton extends StatelessWidget {
     );
   }
 }
+
+class _RowCells extends StatelessWidget {
+  const _RowCells({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<RowBloc, RowState>(
+      buildWhen: (previous, current) => previous.cellDatas != current.cellDatas,
+      builder: (context, state) {
+        return FutureBuilder(
+          future: state.cellDatas,
+          builder: builder,
+        );
+      },
+    );
+  }
+
+  Widget builder(context, AsyncSnapshot<dynamic> snapshot) {
+    switch (snapshot.connectionState) {
+      case ConnectionState.done:
+        List<GridCellData> cellDatas = snapshot.data;
+        return Row(children: cellDatas.map(_toCell).toList());
+      default:
+        return const SizedBox();
+    }
+  }
+
+  Widget _toCell(GridCellData data) {
+    return CellContainer(
+      width: data.field.width.toDouble(),
+      child: buildGridCell(data),
+    );
+  }
+}

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

@@ -1,6 +1,9 @@
+import 'dart:async';
+
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/cell_bloc/text_cell_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/row_service.dart';
+import 'package:flowy_sdk/log.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
@@ -19,27 +22,40 @@ class GridTextCell extends StatefulWidget {
 
 class _GridTextCellState extends State<GridTextCell> {
   late TextEditingController _controller;
+  Timer? _delayOperation;
   final _focusNode = FocusNode();
-  late TextCellBloc _cellBloc;
+  TextCellBloc? _cellBloc;
 
   @override
   void initState() {
     _cellBloc = getIt<TextCellBloc>(param1: widget.cellData);
-    _controller = TextEditingController(text: _cellBloc.state.content);
-    _focusNode.addListener(_focusChanged);
+    _controller = TextEditingController(text: _cellBloc!.state.content);
+    _focusNode.addListener(save);
     super.initState();
   }
 
   @override
   Widget build(BuildContext context) {
     return BlocProvider.value(
-      value: _cellBloc,
+      value: _cellBloc!,
       child: BlocBuilder<TextCellBloc, TextCellState>(
+        buildWhen: (previous, current) {
+          return _controller.text != current.content;
+        },
         builder: (context, state) {
           return TextField(
             controller: _controller,
             focusNode: _focusNode,
-            onChanged: (value) {},
+            onChanged: (value) {
+              Log.info("On change");
+              save();
+            },
+            onEditingComplete: () {
+              Log.info("On complete");
+            },
+            onSubmitted: (value) {
+              Log.info("On submit");
+            },
             maxLines: 1,
             style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
             decoration: const InputDecoration(
@@ -55,13 +71,18 @@ class _GridTextCellState extends State<GridTextCell> {
 
   @override
   Future<void> dispose() async {
-    _cellBloc.close();
-    _focusNode.removeListener(_focusChanged);
+    _cellBloc?.close();
+    _cellBloc = null;
+    _focusNode.removeListener(save);
     _focusNode.dispose();
     super.dispose();
   }
 
-  void _focusChanged() {
-    _cellBloc.add(TextCellEvent.updateText(_controller.text));
+  Future<void> save() async {
+    _delayOperation?.cancel();
+    _delayOperation = Timer(const Duration(seconds: 2), () {
+      _cellBloc?.add(TextCellEvent.updateText(_controller.text));
+    });
+    // and later, before the timer goes off...
   }
 }

+ 11 - 7
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header.dart

@@ -42,6 +42,7 @@ class GridHeader extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
     return BlocProvider(
       create: (context) => getIt<ColumnBloc>(param1: fields)..add(const ColumnEvent.initial()),
       child: BlocBuilder<ColumnBloc, ColumnState>(
@@ -55,13 +56,16 @@ class GridHeader extends StatelessWidget {
               )
               .toList();
 
-          return Row(
-            crossAxisAlignment: CrossAxisAlignment.stretch,
-            children: [
-              const LeadingHeaderCell(),
-              ...headers,
-              const TrailingHeaderCell(),
-            ],
+          return Container(
+            color: theme.surface,
+            child: Row(
+              crossAxisAlignment: CrossAxisAlignment.stretch,
+              children: [
+                const LeadingHeaderCell(),
+                ...headers,
+                const TrailingHeaderCell(),
+              ],
+            ),
           );
         },
       ),

+ 74 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/widgets/left_bar_item.dart

@@ -0,0 +1,74 @@
+import 'package:app_flowy/workspace/application/view/view_service.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class ViewLeftBarItem extends StatefulWidget {
+  final View view;
+
+  ViewLeftBarItem({required this.view, Key? key}) : super(key: ValueKey(view.hashCode));
+
+  @override
+  State<ViewLeftBarItem> createState() => _ViewLeftBarItemState();
+}
+
+class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
+  final _controller = TextEditingController();
+  final _focusNode = FocusNode();
+  late ViewService serviceService;
+
+  @override
+  void initState() {
+    serviceService = ViewService(/*view: widget.view*/);
+    _focusNode.addListener(_handleFocusChanged);
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    _focusNode.removeListener(_handleFocusChanged);
+    _focusNode.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    _controller.text = widget.view.name;
+
+    final theme = context.watch<AppTheme>();
+    return IntrinsicWidth(
+      key: ValueKey(_controller.text),
+      child: TextField(
+        controller: _controller,
+        focusNode: _focusNode,
+        scrollPadding: EdgeInsets.zero,
+        decoration: const InputDecoration(
+          contentPadding: EdgeInsets.zero,
+          border: InputBorder.none,
+          isDense: true,
+        ),
+        style: TextStyle(
+          color: theme.textColor,
+          fontSize: 14,
+          fontWeight: FontWeight.w500,
+          overflow: TextOverflow.ellipsis,
+        ),
+        // cursorColor: widget.cursorColor,
+        // obscureText: widget.enableObscure,
+      ),
+    );
+  }
+
+  void _handleFocusChanged() {
+    if (_controller.text.isEmpty) {
+      _controller.text = widget.view.name;
+      return;
+    }
+
+    if (_controller.text != widget.view.name) {
+      serviceService.updateView(viewId: widget.view.id, name: _controller.text);
+    }
+  }
+}

+ 20 - 1
frontend/app_flowy/packages/flowy_infra/lib/notifier.dart

@@ -1,10 +1,29 @@
 import 'package:flutter/material.dart';
 
+abstract class Comparable<T> {
+  bool compare(T? previous, T? current);
+}
+
+class ObjectComparable<T> extends Comparable<T> {
+  @override
+  bool compare(T? previous, T? current) {
+    return previous == current;
+  }
+}
+
 class PublishNotifier<T> extends ChangeNotifier {
   T? _value;
+  Comparable<T>? comparable = ObjectComparable();
+
+  PublishNotifier({this.comparable});
 
   set value(T newValue) {
-    if (_value != newValue) {
+    if (comparable != null) {
+      if (comparable!.compare(_value, newValue)) {
+        _value = newValue;
+        notifyListeners();
+      }
+    } else {
       _value = newValue;
       notifyListeners();
     }

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

@@ -609,19 +609,19 @@ class GridBlockOrder extends $pb.GeneratedMessage {
 
 class GridBlock extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridBlock', createEmptyInstance: create)
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blockId')
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
     ..pc<RowOrder>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowOrders', $pb.PbFieldType.PM, subBuilder: RowOrder.create)
     ..hasRequiredFields = false
   ;
 
   GridBlock._() : super();
   factory GridBlock({
-    $core.String? blockId,
+    $core.String? id,
     $core.Iterable<RowOrder>? rowOrders,
   }) {
     final _result = create();
-    if (blockId != null) {
-      _result.blockId = blockId;
+    if (id != null) {
+      _result.id = id;
     }
     if (rowOrders != null) {
       _result.rowOrders.addAll(rowOrders);
@@ -650,13 +650,13 @@ class GridBlock extends $pb.GeneratedMessage {
   static GridBlock? _defaultInstance;
 
   @$pb.TagNumber(1)
-  $core.String get blockId => $_getSZ(0);
+  $core.String get id => $_getSZ(0);
   @$pb.TagNumber(1)
-  set blockId($core.String v) { $_setString(0, v); }
+  set id($core.String v) { $_setString(0, v); }
   @$pb.TagNumber(1)
-  $core.bool hasBlockId() => $_has(0);
+  $core.bool hasId() => $_has(0);
   @$pb.TagNumber(1)
-  void clearBlockId() => clearField(1);
+  void clearId() => clearField(1);
 
   @$pb.TagNumber(2)
   $core.List<RowOrder> get rowOrders => $_getList(1);

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

@@ -135,13 +135,13 @@ final $typed_data.Uint8List gridBlockOrderDescriptor = $convert.base64Decode('Cg
 const GridBlock$json = const {
   '1': 'GridBlock',
   '2': const [
-    const {'1': 'block_id', '3': 1, '4': 1, '5': 9, '10': 'blockId'},
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
     const {'1': 'row_orders', '3': 2, '4': 3, '5': 11, '6': '.RowOrder', '10': 'rowOrders'},
   ],
 };
 
 /// Descriptor for `GridBlock`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List gridBlockDescriptor = $convert.base64Decode('CglHcmlkQmxvY2sSGQoIYmxvY2tfaWQYASABKAlSB2Jsb2NrSWQSKAoKcm93X29yZGVycxgCIAMoCzIJLlJvd09yZGVyUglyb3dPcmRlcnM=');
+final $typed_data.Uint8List gridBlockDescriptor = $convert.base64Decode('CglHcmlkQmxvY2sSDgoCaWQYASABKAlSAmlkEigKCnJvd19vcmRlcnMYAiADKAsyCS5Sb3dPcmRlclIJcm93T3JkZXJz');
 @$core.Deprecated('Use cellDescriptor instead')
 const Cell$json = const {
   '1': 'Cell',

+ 4 - 4
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/dart_notification.pbenum.dart

@@ -11,15 +11,15 @@ import 'package:protobuf/protobuf.dart' as $pb;
 
 class GridNotification extends $pb.ProtobufEnum {
   static const GridNotification Unknown = GridNotification._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Unknown');
-  static const GridNotification GridDidUpdateBlock = GridNotification._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidUpdateBlock');
   static const GridNotification GridDidCreateBlock = GridNotification._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidCreateBlock');
-  static const GridNotification GridDidUpdateCells = GridNotification._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidUpdateCells');
-  static const GridNotification GridDidUpdateFields = GridNotification._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidUpdateFields');
+  static const GridNotification BlockDidUpdateRow = GridNotification._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'BlockDidUpdateRow');
+  static const GridNotification GridDidUpdateCells = GridNotification._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidUpdateCells');
+  static const GridNotification GridDidUpdateFields = GridNotification._(40, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidUpdateFields');
 
   static const $core.List<GridNotification> values = <GridNotification> [
     Unknown,
-    GridDidUpdateBlock,
     GridDidCreateBlock,
+    BlockDidUpdateRow,
     GridDidUpdateCells,
     GridDidUpdateFields,
   ];

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

@@ -13,12 +13,12 @@ const GridNotification$json = const {
   '1': 'GridNotification',
   '2': const [
     const {'1': 'Unknown', '2': 0},
-    const {'1': 'GridDidUpdateBlock', '2': 10},
     const {'1': 'GridDidCreateBlock', '2': 11},
-    const {'1': 'GridDidUpdateCells', '2': 20},
-    const {'1': 'GridDidUpdateFields', '2': 30},
+    const {'1': 'BlockDidUpdateRow', '2': 20},
+    const {'1': 'GridDidUpdateCells', '2': 30},
+    const {'1': 'GridDidUpdateFields', '2': 40},
   ],
 };
 
 /// Descriptor for `GridNotification`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List gridNotificationDescriptor = $convert.base64Decode('ChBHcmlkTm90aWZpY2F0aW9uEgsKB1Vua25vd24QABIWChJHcmlkRGlkVXBkYXRlQmxvY2sQChIWChJHcmlkRGlkQ3JlYXRlQmxvY2sQCxIWChJHcmlkRGlkVXBkYXRlQ2VsbHMQFBIXChNHcmlkRGlkVXBkYXRlRmllbGRzEB4=');
+final $typed_data.Uint8List gridNotificationDescriptor = $convert.base64Decode('ChBHcmlkTm90aWZpY2F0aW9uEgsKB1Vua25vd24QABIWChJHcmlkRGlkQ3JlYXRlQmxvY2sQCxIVChFCbG9ja0RpZFVwZGF0ZVJvdxAUEhYKEkdyaWREaWRVcGRhdGVDZWxscxAeEhcKE0dyaWREaWRVcGRhdGVGaWVsZHMQKA==');

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

@@ -5,11 +5,13 @@ const OBSERVABLE_CATEGORY: &str = "Grid";
 #[derive(ProtoBuf_Enum, Debug)]
 pub enum GridNotification {
     Unknown = 0,
-    GridDidUpdateBlock = 10,
+
     GridDidCreateBlock = 11,
 
-    GridDidUpdateCells = 20,
-    GridDidUpdateFields = 30,
+    BlockDidUpdateRow = 20,
+
+    GridDidUpdateCells = 30,
+    GridDidUpdateFields = 40,
 }
 
 impl std::default::Default for GridNotification {

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

@@ -83,7 +83,7 @@ impl GridManager {
         Ok(())
     }
 
-    #[tracing::instrument(level = "debug", skip(self), err)]
+    // #[tracing::instrument(level = "debug", skip(self), err)]
     pub fn get_grid_editor(&self, grid_id: &str) -> FlowyResult<Arc<ClientGridEditor>> {
         match self.editor_map.get(grid_id) {
             None => Err(FlowyError::internal().context("Should call open_grid function first")),

+ 11 - 11
frontend/rust-lib/flowy-grid/src/protobuf/model/dart_notification.rs

@@ -26,10 +26,10 @@
 #[derive(Clone,PartialEq,Eq,Debug,Hash)]
 pub enum GridNotification {
     Unknown = 0,
-    GridDidUpdateBlock = 10,
     GridDidCreateBlock = 11,
-    GridDidUpdateCells = 20,
-    GridDidUpdateFields = 30,
+    BlockDidUpdateRow = 20,
+    GridDidUpdateCells = 30,
+    GridDidUpdateFields = 40,
 }
 
 impl ::protobuf::ProtobufEnum for GridNotification {
@@ -40,10 +40,10 @@ impl ::protobuf::ProtobufEnum for GridNotification {
     fn from_i32(value: i32) -> ::std::option::Option<GridNotification> {
         match value {
             0 => ::std::option::Option::Some(GridNotification::Unknown),
-            10 => ::std::option::Option::Some(GridNotification::GridDidUpdateBlock),
             11 => ::std::option::Option::Some(GridNotification::GridDidCreateBlock),
-            20 => ::std::option::Option::Some(GridNotification::GridDidUpdateCells),
-            30 => ::std::option::Option::Some(GridNotification::GridDidUpdateFields),
+            20 => ::std::option::Option::Some(GridNotification::BlockDidUpdateRow),
+            30 => ::std::option::Option::Some(GridNotification::GridDidUpdateCells),
+            40 => ::std::option::Option::Some(GridNotification::GridDidUpdateFields),
             _ => ::std::option::Option::None
         }
     }
@@ -51,8 +51,8 @@ impl ::protobuf::ProtobufEnum for GridNotification {
     fn values() -> &'static [Self] {
         static values: &'static [GridNotification] = &[
             GridNotification::Unknown,
-            GridNotification::GridDidUpdateBlock,
             GridNotification::GridDidCreateBlock,
+            GridNotification::BlockDidUpdateRow,
             GridNotification::GridDidUpdateCells,
             GridNotification::GridDidUpdateFields,
         ];
@@ -83,10 +83,10 @@ impl ::protobuf::reflect::ProtobufValue for GridNotification {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x17dart_notification.proto*\x80\x01\n\x10GridNotification\x12\x0b\n\
-    \x07Unknown\x10\0\x12\x16\n\x12GridDidUpdateBlock\x10\n\x12\x16\n\x12Gri\
-    dDidCreateBlock\x10\x0b\x12\x16\n\x12GridDidUpdateCells\x10\x14\x12\x17\
-    \n\x13GridDidUpdateFields\x10\x1eb\x06proto3\
+    \n\x17dart_notification.proto*\x7f\n\x10GridNotification\x12\x0b\n\x07Un\
+    known\x10\0\x12\x16\n\x12GridDidCreateBlock\x10\x0b\x12\x15\n\x11BlockDi\
+    dUpdateRow\x10\x14\x12\x16\n\x12GridDidUpdateCells\x10\x1e\x12\x17\n\x13\
+    GridDidUpdateFields\x10(b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -2,8 +2,8 @@ syntax = "proto3";
 
 enum GridNotification {
     Unknown = 0;
-    GridDidUpdateBlock = 10;
     GridDidCreateBlock = 11;
-    GridDidUpdateCells = 20;
-    GridDidUpdateFields = 30;
+    BlockDidUpdateRow = 20;
+    GridDidUpdateCells = 30;
+    GridDidUpdateFields = 40;
 }

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

@@ -46,7 +46,9 @@ impl GridBlockMetaEditorManager {
         Ok(manager)
     }
 
+    // #[tracing::instrument(level = "trace", skip(self))]
     pub(crate) async fn get_editor(&self, block_id: &str) -> FlowyResult<Arc<ClientGridBlockMetaEditor>> {
+        debug_assert!(!block_id.is_empty());
         match self.editor_map.get(block_id) {
             None => {
                 tracing::error!("The is a fatal error, block is not exist");
@@ -68,7 +70,7 @@ impl GridBlockMetaEditorManager {
             .insert(row_meta.id.clone(), row_meta.block_id.clone());
         let editor = self.get_editor(&row_meta.block_id).await?;
         let row_count = editor.create_row(row_meta, start_row_id).await?;
-        self.notify_did_update_block(block_id).await?;
+        self.notify_block_did_update_row(&block_id).await?;
         Ok(row_count)
     }
 
@@ -85,7 +87,7 @@ impl GridBlockMetaEditorManager {
                 row_count = editor.create_row(row.clone(), None).await?;
             }
             changesets.push(GridBlockMetaChangeset::from_row_count(&block_id, row_count));
-            let _ = self.notify_did_update_block(&block_id).await?;
+            let _ = self.notify_block_did_update_row(&block_id).await?;
         }
 
         Ok(changesets)
@@ -108,7 +110,7 @@ impl GridBlockMetaEditorManager {
     pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
         let editor = self.get_editor_from_row_id(&changeset.row_id).await?;
         let _ = editor.update_row(changeset.clone()).await?;
-        let _ = self.notify_did_update_block(&editor.block_id).await?;
+        let _ = self.notify_block_did_update_row(&editor.block_id).await?;
         Ok(())
     }
 
@@ -183,11 +185,9 @@ impl GridBlockMetaEditorManager {
         }
     }
 
-    async fn notify_did_update_block(&self, block_id: &str) -> FlowyResult<()> {
-        let block_id = GridBlockId {
-            value: block_id.to_owned(),
-        };
-        send_dart_notification(&self.grid_id, GridNotification::GridDidUpdateBlock)
+    async fn notify_block_did_update_row(&self, block_id: &str) -> FlowyResult<()> {
+        let block_id: GridBlockId = block_id.into();
+        send_dart_notification(&self.grid_id, GridNotification::BlockDidUpdateRow)
             .payload(block_id)
             .send();
         Ok(())
@@ -277,7 +277,7 @@ impl ClientGridBlockMetaEditor {
         let mut row_count = 0;
         let _ = self
             .modify(|pad| {
-                let change = pad.add_row(row, start_row_id)?;
+                let change = pad.add_row_meta(row, start_row_id)?;
                 row_count = pad.number_of_rows();
                 Ok(change)
             })
@@ -298,13 +298,22 @@ impl ClientGridBlockMetaEditor {
         Ok(row_count)
     }
 
-    pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
+    pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<RowMeta> {
+        let row_id = changeset.row_id.clone();
         let _ = self.modify(|pad| Ok(pad.update_row(changeset)?)).await?;
-        Ok(())
+        let mut row_metas = self.get_row_metas(Some(vec![row_id.clone()])).await?;
+        debug_assert_eq!(row_metas.len(), 1);
+
+        if row_metas.is_empty() {
+            return Err(FlowyError::record_not_found().context(format!("Can't find the row with id: {}", &row_id)));
+        } else {
+            let a = (**row_metas.pop().as_ref().unwrap()).clone();
+            Ok(a)
+        }
     }
 
     pub async fn get_row_metas(&self, row_ids: Option<Vec<String>>) -> FlowyResult<Vec<Arc<RowMeta>>> {
-        let row_metas = self.pad.read().await.get_rows(row_ids)?;
+        let row_metas = self.pad.read().await.get_row_metas(row_ids)?;
         Ok(row_metas)
     }
 
@@ -313,7 +322,7 @@ impl ClientGridBlockMetaEditor {
             .pad
             .read()
             .await
-            .get_rows(row_ids)?
+            .get_row_metas(row_ids)?
             .iter()
             .map(RowOrder::from)
             .collect::<Vec<RowOrder>>();

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

@@ -43,10 +43,10 @@ pub(crate) fn make_row_ids_per_block(row_orders: &[RowOrder]) -> Vec<RowIdsPerBl
 pub(crate) fn make_grid_blocks(block_meta_snapshots: Vec<GridBlockMetaData>) -> FlowyResult<RepeatedGridBlock> {
     Ok(block_meta_snapshots
         .into_iter()
-        .map(|row_metas_per_block| {
-            let row_orders = make_row_orders_from_row_metas(&row_metas_per_block.row_metas);
+        .map(|block_meta_data| {
+            let row_orders = make_row_orders_from_row_metas(&block_meta_data.row_metas);
             GridBlock {
-                block_id: row_metas_per_block.block_id,
+                id: block_meta_data.block_id,
                 row_orders,
             }
         })

+ 2 - 0
frontend/rust-lib/flowy-revision/src/rev_manager.rs

@@ -67,6 +67,7 @@ impl RevisionManager {
         }
     }
 
+    #[tracing::instrument(level = "debug", skip_all, fields(object_id) err)]
     pub async fn load<B>(&mut self, cloud: Option<Arc<dyn RevisionCloudService>>) -> FlowyResult<B::Output>
     where
         B: RevisionObjectBuilder,
@@ -80,6 +81,7 @@ impl RevisionManager {
         .load()
         .await?;
         self.rev_id_counter.set(rev_id);
+        tracing::Span::current().record("object_id", &self.object_id.as_str());
         B::build_object(&self.object_id, revisions)
     }
 

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

@@ -181,7 +181,7 @@ pub struct GridBlockOrder {
 #[derive(Debug, Default, ProtoBuf)]
 pub struct GridBlock {
     #[pb(index = 1)]
-    pub block_id: String,
+    pub id: String,
 
     #[pb(index = 2)]
     pub row_orders: Vec<RowOrder>,
@@ -190,7 +190,7 @@ pub struct GridBlock {
 impl GridBlock {
     pub fn new(block_id: &str, row_orders: Vec<RowOrder>) -> Self {
         Self {
-            block_id: block_id.to_owned(),
+            id: block_id.to_owned(),
             row_orders,
         }
     }
@@ -269,6 +269,12 @@ impl AsRef<str> for GridBlockId {
     }
 }
 
+impl std::convert::From<&str> for GridBlockId {
+    fn from(s: &str) -> Self {
+        GridBlockId { value: s.to_owned() }
+    }
+}
+
 #[derive(ProtoBuf, Default)]
 pub struct CreateRowPayload {
     #[pb(index = 1)]

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

@@ -2111,7 +2111,7 @@ impl ::protobuf::reflect::ProtobufValue for GridBlockOrder {
 #[derive(PartialEq,Clone,Default)]
 pub struct GridBlock {
     // message fields
-    pub block_id: ::std::string::String,
+    pub id: ::std::string::String,
     pub row_orders: ::protobuf::RepeatedField<RowOrder>,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
@@ -2129,30 +2129,30 @@ impl GridBlock {
         ::std::default::Default::default()
     }
 
-    // string block_id = 1;
+    // string id = 1;
 
 
-    pub fn get_block_id(&self) -> &str {
-        &self.block_id
+    pub fn get_id(&self) -> &str {
+        &self.id
     }
-    pub fn clear_block_id(&mut self) {
-        self.block_id.clear();
+    pub fn clear_id(&mut self) {
+        self.id.clear();
     }
 
     // Param is passed by value, moved
-    pub fn set_block_id(&mut self, v: ::std::string::String) {
-        self.block_id = v;
+    pub fn set_id(&mut self, v: ::std::string::String) {
+        self.id = v;
     }
 
     // Mutable pointer to the field.
     // If field is not initialized, it is initialized with default value first.
-    pub fn mut_block_id(&mut self) -> &mut ::std::string::String {
-        &mut self.block_id
+    pub fn mut_id(&mut self) -> &mut ::std::string::String {
+        &mut self.id
     }
 
     // Take field
-    pub fn take_block_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.block_id, ::std::string::String::new())
+    pub fn take_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.id, ::std::string::String::new())
     }
 
     // repeated .RowOrder row_orders = 2;
@@ -2196,7 +2196,7 @@ impl ::protobuf::Message for GridBlock {
             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.block_id)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
                 },
                 2 => {
                     ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.row_orders)?;
@@ -2213,8 +2213,8 @@ impl ::protobuf::Message for GridBlock {
     #[allow(unused_variables)]
     fn compute_size(&self) -> u32 {
         let mut my_size = 0;
-        if !self.block_id.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.block_id);
+        if !self.id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.id);
         }
         for value in &self.row_orders {
             let len = value.compute_size();
@@ -2226,8 +2226,8 @@ impl ::protobuf::Message for GridBlock {
     }
 
     fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        if !self.block_id.is_empty() {
-            os.write_string(1, &self.block_id)?;
+        if !self.id.is_empty() {
+            os.write_string(1, &self.id)?;
         }
         for v in &self.row_orders {
             os.write_tag(2, ::protobuf::wire_format::WireTypeLengthDelimited)?;
@@ -2273,9 +2273,9 @@ impl ::protobuf::Message for GridBlock {
         descriptor.get(|| {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "block_id",
-                |m: &GridBlock| { &m.block_id },
-                |m: &mut GridBlock| { &mut m.block_id },
+                "id",
+                |m: &GridBlock| { &m.id },
+                |m: &mut GridBlock| { &mut m.id },
             ));
             fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<RowOrder>>(
                 "row_orders",
@@ -2298,7 +2298,7 @@ impl ::protobuf::Message for GridBlock {
 
 impl ::protobuf::Clear for GridBlock {
     fn clear(&mut self) {
-        self.block_id.clear();
+        self.id.clear();
         self.row_orders.clear();
         self.unknown_fields.clear();
     }
@@ -4091,24 +4091,24 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x05.CellR\x05value:\x028\x01\")\n\x0bRepeatedRow\x12\x1a\n\x05items\x18\
     \x01\x20\x03(\x0b2\x04.RowR\x05items\"5\n\x11RepeatedGridBlock\x12\x20\n\
     \x05items\x18\x01\x20\x03(\x0b2\n.GridBlockR\x05items\"+\n\x0eGridBlockO\
-    rder\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\"P\n\tGridBloc\
-    k\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\x12(\n\nrow_order\
-    s\x18\x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\";\n\x04Cell\x12\x19\n\
-    \x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x18\n\x07content\x18\x02\
-    \x20\x01(\tR\x07content\"+\n\x0cRepeatedCell\x12\x1b\n\x05items\x18\x01\
-    \x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11CreateGridPayload\x12\x12\n\
-    \x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\x05valu\
-    e\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlockId\x12\x14\n\x05value\
-    \x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\n\x07grid\
-    _id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_row_id\x18\x02\x20\
-    \x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\"d\n\x11QueryField\
-    Payload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x126\n\x0cfie\
-    ld_orders\x18\x02\x20\x01(\x0b2\x13.RepeatedFieldOrderR\x0bfieldOrders\"\
-    e\n\x16QueryGridBlocksPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
-    \x06gridId\x122\n\x0cblock_orders\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrd\
-    erR\x0bblockOrders\"\\\n\x0fQueryRowPayload\x12\x17\n\x07grid_id\x18\x01\
-    \x20\x01(\tR\x06gridId\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07bloc\
-    kId\x12\x15\n\x06row_id\x18\x03\x20\x01(\tR\x05rowIdb\x06proto3\
+    rder\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\"E\n\tGridBloc\
+    k\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12(\n\nrow_orders\x18\x02\
+    \x20\x03(\x0b2\t.RowOrderR\trowOrders\";\n\x04Cell\x12\x19\n\x08field_id\
+    \x18\x01\x20\x01(\tR\x07fieldId\x12\x18\n\x07content\x18\x02\x20\x01(\tR\
+    \x07content\"+\n\x0cRepeatedCell\x12\x1b\n\x05items\x18\x01\x20\x03(\x0b\
+    2\x05.CellR\x05items\"'\n\x11CreateGridPayload\x12\x12\n\x04name\x18\x01\
+    \x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\x05value\x18\x01\x20\
+    \x01(\tR\x05value\"#\n\x0bGridBlockId\x12\x14\n\x05value\x18\x01\x20\x01\
+    (\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\n\x07grid_id\x18\x01\x20\
+    \x01(\tR\x06gridId\x12\"\n\x0cstart_row_id\x18\x02\x20\x01(\tH\0R\nstart\
+    RowIdB\x15\n\x13one_of_start_row_id\"d\n\x11QueryFieldPayload\x12\x17\n\
+    \x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x126\n\x0cfield_orders\x18\x02\
+    \x20\x01(\x0b2\x13.RepeatedFieldOrderR\x0bfieldOrders\"e\n\x16QueryGridB\
+    locksPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x122\n\
+    \x0cblock_orders\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrder\
+    s\"\\\n\x0fQueryRowPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06g\
+    ridId\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07blockId\x12\x15\n\x06\
+    row_id\x18\x03\x20\x01(\tR\x05rowIdb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -44,7 +44,7 @@ message GridBlockOrder {
     string block_id = 1;
 }
 message GridBlock {
-    string block_id = 1;
+    string id = 1;
     repeated RowOrder row_orders = 2;
 }
 message Cell {

+ 19 - 18
shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs

@@ -47,20 +47,21 @@ impl GridBlockMetaPad {
         Self::from_delta(block_delta)
     }
 
-    pub fn add_row(
+    #[tracing::instrument(level = "trace", skip(self, row), err)]
+    pub fn add_row_meta(
         &mut self,
         row: RowMeta,
         start_row_id: Option<String>,
     ) -> CollaborateResult<Option<GridBlockMetaChange>> {
         self.modify(|rows| {
-            if let Some(upper_row_id) = start_row_id {
-                if upper_row_id.is_empty() {
+            if let Some(start_row_id) = start_row_id {
+                if start_row_id.is_empty() {
                     rows.insert(0, Arc::new(row));
                     return Ok(Some(()));
                 }
 
-                if let Some(index) = rows.iter().position(|row| row.id == upper_row_id) {
-                    rows.insert(index, Arc::new(row));
+                if let Some(index) = rows.iter().position(|row| row.id == start_row_id) {
+                    rows.insert(index + 1, Arc::new(row));
                     return Ok(Some(()));
                 }
             }
@@ -77,7 +78,7 @@ impl GridBlockMetaPad {
         })
     }
 
-    pub fn get_rows(&self, row_ids: Option<Vec<String>>) -> CollaborateResult<Vec<Arc<RowMeta>>> {
+    pub fn get_row_metas(&self, row_ids: Option<Vec<String>>) -> CollaborateResult<Vec<Arc<RowMeta>>> {
         match row_ids {
             None => Ok(self.row_metas.to_vec()),
             Some(row_ids) => {
@@ -229,7 +230,7 @@ mod tests {
             visibility: false,
         };
 
-        let change = pad.add_row(row, None).unwrap().unwrap();
+        let change = pad.add_row_meta(row, None).unwrap().unwrap();
         assert_eq!(
             change.delta.to_delta_str(),
             r#"[{"retain":29},{"insert":"{\"id\":\"1\",\"block_id\":\"1\",\"cell_by_field_id\":{},\"height\":0,\"visibility\":false}"},{"retain":2}]"#
@@ -243,19 +244,19 @@ mod tests {
         let row_2 = test_row_meta("2", &pad);
         let row_3 = test_row_meta("3", &pad);
 
-        let change = pad.add_row(row_1.clone(), None).unwrap().unwrap();
+        let change = pad.add_row_meta(row_1.clone(), None).unwrap().unwrap();
         assert_eq!(
             change.delta.to_delta_str(),
             r#"[{"retain":29},{"insert":"{\"id\":\"1\",\"block_id\":\"1\",\"cell_by_field_id\":{},\"height\":0,\"visibility\":false}"},{"retain":2}]"#
         );
 
-        let change = pad.add_row(row_2.clone(), None).unwrap().unwrap();
+        let change = pad.add_row_meta(row_2.clone(), None).unwrap().unwrap();
         assert_eq!(
             change.delta.to_delta_str(),
             r#"[{"retain":106},{"insert":",{\"id\":\"2\",\"block_id\":\"1\",\"cell_by_field_id\":{},\"height\":0,\"visibility\":false}"},{"retain":2}]"#
         );
 
-        let change = pad.add_row(row_3.clone(), Some("2".to_string())).unwrap().unwrap();
+        let change = pad.add_row_meta(row_3.clone(), Some("2".to_string())).unwrap().unwrap();
         assert_eq!(
             change.delta.to_delta_str(),
             r#"[{"retain":114},{"insert":"3\",\"block_id\":\"1\",\"cell_by_field_id\":{},\"height\":0,\"visibility\":false},{\"id\":\""},{"retain":72}]"#
@@ -283,9 +284,9 @@ mod tests {
         let row_2 = test_row_meta("2", &pad);
         let row_3 = test_row_meta("3", &pad);
 
-        let _ = pad.add_row(row_1.clone(), None).unwrap().unwrap();
-        let _ = pad.add_row(row_2.clone(), None).unwrap().unwrap();
-        let _ = pad.add_row(row_3.clone(), Some("1".to_string())).unwrap().unwrap();
+        let _ = pad.add_row_meta(row_1.clone(), None).unwrap().unwrap();
+        let _ = pad.add_row_meta(row_2.clone(), None).unwrap().unwrap();
+        let _ = pad.add_row_meta(row_3.clone(), Some("1".to_string())).unwrap().unwrap();
 
         assert_eq!(*pad.row_metas[0], row_3);
         assert_eq!(*pad.row_metas[1], row_1);
@@ -299,9 +300,9 @@ mod tests {
         let row_2 = test_row_meta("2", &pad);
         let row_3 = test_row_meta("3", &pad);
 
-        let _ = pad.add_row(row_1.clone(), None).unwrap().unwrap();
-        let _ = pad.add_row(row_2.clone(), None).unwrap().unwrap();
-        let _ = pad.add_row(row_3.clone(), Some("".to_string())).unwrap().unwrap();
+        let _ = pad.add_row_meta(row_1.clone(), None).unwrap().unwrap();
+        let _ = pad.add_row_meta(row_2.clone(), None).unwrap().unwrap();
+        let _ = pad.add_row_meta(row_3.clone(), Some("".to_string())).unwrap().unwrap();
 
         assert_eq!(*pad.row_metas[0], row_3);
         assert_eq!(*pad.row_metas[1], row_1);
@@ -320,7 +321,7 @@ mod tests {
             visibility: false,
         };
 
-        let _ = pad.add_row(row.clone(), None).unwrap().unwrap();
+        let _ = pad.add_row_meta(row.clone(), None).unwrap().unwrap();
         let change = pad.delete_rows(&[row.id]).unwrap().unwrap();
         assert_eq!(
             change.delta.to_delta_str(),
@@ -348,7 +349,7 @@ mod tests {
             cell_by_field_id: Default::default(),
         };
 
-        let _ = pad.add_row(row, None).unwrap().unwrap();
+        let _ = pad.add_row_meta(row, None).unwrap().unwrap();
         let change = pad.update_row(changeset).unwrap().unwrap();
 
         assert_eq!(