Browse Source

refactor: fetch block data

appflowy 2 năm trước cách đây
mục cha
commit
0f868f48f3
19 tập tin đã thay đổi với 245 bổ sung164 xóa
  1. 90 0
      frontend/app_flowy/lib/workspace/application/grid/block/block_listener.dart
  2. 0 0
      frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart
  3. 17 17
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_cache.dart
  4. 0 0
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_loader.dart
  5. 0 0
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_persistence.dart
  6. 3 3
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart
  7. 3 3
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart
  8. 22 3
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  9. 0 42
      frontend/app_flowy/lib/workspace/application/grid/grid_listener.dart
  10. 25 33
      frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart
  11. 6 11
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  12. 1 1
      frontend/rust-lib/flowy-grid/src/event_map.rs
  13. 15 11
      frontend/rust-lib/flowy-grid/src/services/block_manager.rs
  14. 3 3
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  15. 4 4
      frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs
  16. 4 4
      frontend/rust-lib/flowy-grid/tests/grid/filter_test.rs
  17. 40 5
      frontend/rust-lib/flowy-grid/tests/grid/row_util.rs
  18. 11 23
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  19. 1 1
      shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs

+ 90 - 0
frontend/app_flowy/lib/workspace/application/grid/block/block_listener.dart

@@ -0,0 +1,90 @@
+import 'dart:async';
+import 'dart:collection';
+import 'dart:typed_data';
+
+import 'package:app_flowy/core/notification_helper.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flowy_infra/notifier.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
+
+class GridBlockCache {
+  final String gridId;
+  void Function(GridBlockUpdateNotifierValue)? _onBlockChanged;
+
+  final LinkedHashMap<String, _GridBlockListener> _listeners = LinkedHashMap();
+  GridBlockCache({required this.gridId});
+
+  void start(void Function(GridBlockUpdateNotifierValue) onBlockChanged) {
+    _onBlockChanged = onBlockChanged;
+    for (final listener in _listeners.values) {
+      listener.start(onBlockChanged);
+    }
+  }
+
+  Future<void> dispose() async {
+    for (final listener in _listeners.values) {
+      await listener.stop();
+    }
+  }
+
+  void addBlockListener(String blockId) {
+    if (_onBlockChanged == null) {
+      Log.error("Should call start() first");
+      return;
+    }
+    if (_listeners.containsKey(blockId)) {
+      Log.error("Duplicate block listener");
+      return;
+    }
+
+    final listener = _GridBlockListener(blockId: blockId);
+    listener.start(_onBlockChanged!);
+    _listeners[blockId] = listener;
+  }
+}
+
+typedef GridBlockUpdateNotifierValue = Either<List<GridRowsChangeset>, FlowyError>;
+
+class _GridBlockListener {
+  final String blockId;
+  PublishNotifier<GridBlockUpdateNotifierValue>? _rowsUpdateNotifier = PublishNotifier();
+  GridNotificationListener? _listener;
+
+  _GridBlockListener({required this.blockId});
+
+  void start(void Function(GridBlockUpdateNotifierValue) onBlockChanged) {
+    if (_listener != null) {
+      _listener?.stop();
+    }
+
+    _listener = GridNotificationListener(
+      objectId: blockId,
+      handler: _handler,
+    );
+
+    _rowsUpdateNotifier?.addPublishListener(onBlockChanged);
+  }
+
+  void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
+    switch (ty) {
+      case GridNotification.DidUpdateGridRow:
+        result.fold(
+          (payload) => _rowsUpdateNotifier?.value = left([GridRowsChangeset.fromBuffer(payload)]),
+          (error) => _rowsUpdateNotifier?.value = right(error),
+        );
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  Future<void> stop() async {
+    await _listener?.stop();
+    _rowsUpdateNotifier?.dispose();
+    _rowsUpdateNotifier = null;
+  }
+}

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


+ 17 - 17
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_cache.dart → frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_cache.dart

@@ -2,21 +2,21 @@ part of 'cell_service.dart';
 
 typedef GridCellMap = LinkedHashMap<String, GridCell>;
 
-class GridCellCacheData {
-  GridCellCacheKey key;
+class _GridCellCacheObject {
+  _GridCellCacheKey key;
   dynamic object;
-  GridCellCacheData({
+  _GridCellCacheObject({
     required this.key,
     required this.object,
   });
 }
 
-class GridCellCacheKey {
+class _GridCellCacheKey {
   final String fieldId;
-  final String objectId;
-  GridCellCacheKey({
+  final String rowId;
+  _GridCellCacheKey({
     required this.fieldId,
-    required this.objectId,
+    required this.rowId,
   });
 }
 
@@ -51,46 +51,46 @@ class GridCellCache {
     });
   }
 
-  void addFieldListener(GridCellCacheKey cacheKey, VoidCallback onFieldChanged) {
+  void addFieldListener(_GridCellCacheKey cacheKey, VoidCallback onFieldChanged) {
     var map = _fieldListenerByFieldId[cacheKey.fieldId];
     if (map == null) {
       _fieldListenerByFieldId[cacheKey.fieldId] = {};
       map = _fieldListenerByFieldId[cacheKey.fieldId];
-      map![cacheKey.objectId] = [onFieldChanged];
+      map![cacheKey.rowId] = [onFieldChanged];
     } else {
-      var objects = map[cacheKey.objectId];
+      var objects = map[cacheKey.rowId];
       if (objects == null) {
-        map[cacheKey.objectId] = [onFieldChanged];
+        map[cacheKey.rowId] = [onFieldChanged];
       } else {
         objects.add(onFieldChanged);
       }
     }
   }
 
-  void removeFieldListener(GridCellCacheKey cacheKey, VoidCallback fn) {
-    var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.objectId];
+  void removeFieldListener(_GridCellCacheKey cacheKey, VoidCallback fn) {
+    var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.rowId];
     final index = callbacks?.indexWhere((callback) => callback == fn);
     if (index != null && index != -1) {
       callbacks?.removeAt(index);
     }
   }
 
-  void insert<T extends GridCellCacheData>(T item) {
+  void insert<T extends _GridCellCacheObject>(T item) {
     var map = _cellDataByFieldId[item.key.fieldId];
     if (map == null) {
       _cellDataByFieldId[item.key.fieldId] = {};
       map = _cellDataByFieldId[item.key.fieldId];
     }
 
-    map![item.key.objectId] = item.object;
+    map![item.key.rowId] = item.object;
   }
 
-  T? get<T>(GridCellCacheKey key) {
+  T? get<T>(_GridCellCacheKey key) {
     final map = _cellDataByFieldId[key.fieldId];
     if (map == null) {
       return null;
     } else {
-      final object = map[key.objectId];
+      final object = map[key.rowId];
       if (object is T) {
         return object;
       } else {

+ 0 - 0
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart → frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_loader.dart


+ 0 - 0
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart → frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_persistence.dart


+ 3 - 3
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart

@@ -19,10 +19,10 @@ import 'package:app_flowy/workspace/application/grid/cell/select_option_service.
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
 import 'dart:convert' show utf8;
 part 'cell_service.freezed.dart';
-part 'data_loader.dart';
+part 'cell_data_loader.dart';
 part 'context_builder.dart';
-part 'data_cache.dart';
-part 'data_persistence.dart';
+part 'cell_data_cache.dart';
+part 'cell_data_persistence.dart';
 
 // key: rowId
 

+ 3 - 3
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart

@@ -100,7 +100,7 @@ class GridCellContextBuilder {
 class _GridCellContext<T, D> extends Equatable {
   final GridCell gridCell;
   final GridCellCache cellCache;
-  final GridCellCacheKey _cacheKey;
+  final _GridCellCacheKey _cacheKey;
   final IGridCellDataLoader<T> cellDataLoader;
   final _GridCellDataPersistence<D> cellDataPersistence;
   final FieldService _fieldService;
@@ -118,7 +118,7 @@ class _GridCellContext<T, D> extends Equatable {
     required this.cellDataLoader,
     required this.cellDataPersistence,
   })  : _fieldService = FieldService(gridId: gridCell.gridId, fieldId: gridCell.field.id),
-        _cacheKey = GridCellCacheKey(objectId: gridCell.rowId, fieldId: gridCell.field.id);
+        _cacheKey = _GridCellCacheKey(rowId: gridCell.rowId, fieldId: gridCell.field.id);
 
   _GridCellContext<T, D> clone() {
     return _GridCellContext(
@@ -213,7 +213,7 @@ class _GridCellContext<T, D> extends Equatable {
     _loadDataOperation = Timer(const Duration(milliseconds: 10), () {
       cellDataLoader.loadData().then((data) {
         _cellDataNotifier?.value = data;
-        cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
+        cellCache.insert(_GridCellCacheObject(key: _cacheKey, object: data));
       });
     });
   }

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

@@ -1,11 +1,13 @@
 import 'dart:async';
 import 'package:dartz/dartz.dart';
 import 'package:equatable/equatable.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';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
+import 'block/block_listener.dart';
 import 'cell/cell_service/cell_service.dart';
 import 'grid_service.dart';
 import 'row/row_service.dart';
@@ -19,9 +21,12 @@ class GridBloc extends Bloc<GridEvent, GridState> {
   late final GridRowCache rowCache;
   late final GridCellCache cellCache;
 
+  final GridBlockCache blockCache;
+
   GridBloc({required View view})
       : _gridService = GridService(gridId: view.id),
         fieldCache = GridFieldCache(gridId: view.id),
+        blockCache = GridBlockCache(gridId: view.id),
         super(GridState.initial(view.id)) {
     rowCache = GridRowCache(
       gridId: view.id,
@@ -33,6 +38,13 @@ class GridBloc extends Bloc<GridEvent, GridState> {
       fieldDelegate: GridCellCacheDelegateImpl(fieldCache),
     );
 
+    blockCache.start((result) {
+      result.fold(
+        (changesets) => rowCache.applyChangesets(changesets),
+        (err) => Log.error(err),
+      );
+    });
+
     on<GridEvent>(
       (event, emit) async {
         await event.when(
@@ -60,6 +72,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     await cellCache.dispose();
     await rowCache.dispose();
     await fieldCache.dispose();
+    await blockCache.dispose();
     return super.close();
   }
 
@@ -79,7 +92,15 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     final result = await _gridService.loadGrid();
     return Future(
       () => result.fold(
-        (grid) async => await _loadFields(grid, emit),
+        (grid) async {
+          for (final block in grid.blocks) {
+            blockCache.addBlockListener(block.id);
+          }
+          final rowOrders = grid.blocks.expand((block) => block.rowOrders).toList();
+          rowCache.initialRows(rowOrders);
+
+          await _loadFields(grid, emit);
+        },
         (err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
       ),
     );
@@ -91,7 +112,6 @@ class GridBloc extends Bloc<GridEvent, GridState> {
       () => result.fold(
         (fields) {
           fieldCache.fields = fields.items;
-          rowCache.resetRows(grid.blockOrders);
 
           emit(state.copyWith(
             grid: Some(grid),
@@ -143,7 +163,6 @@ class GridLoadingState with _$GridLoadingState {
 
 class GridFieldEquatable extends Equatable {
   final List<Field> _fields;
-
   const GridFieldEquatable(List<Field> fields) : _fields = fields;
 
   @override

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

@@ -1,42 +0,0 @@
-import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
-import 'package:flowy_infra/notifier.dart';
-import 'dart:async';
-import 'dart:typed_data';
-import 'package:app_flowy/core/notification_helper.dart';
-
-class GridRowListener {
-  final String gridId;
-  PublishNotifier<Either<List<GridRowsChangeset>, FlowyError>> rowsUpdateNotifier = PublishNotifier(comparable: null);
-  GridNotificationListener? _listener;
-
-  GridRowListener({required this.gridId});
-
-  void start() {
-    _listener = GridNotificationListener(
-      objectId: gridId,
-      handler: _handler,
-    );
-  }
-
-  void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
-    switch (ty) {
-      case GridNotification.DidUpdateGridRow:
-        result.fold(
-          (payload) => rowsUpdateNotifier.value = left([GridRowsChangeset.fromBuffer(payload)]),
-          (error) => rowsUpdateNotifier.value = right(error),
-        );
-        break;
-
-      default:
-        break;
-    }
-  }
-
-  Future<void> stop() async {
-    await _listener?.stop();
-    rowsUpdateNotifier.dispose();
-  }
-}

+ 25 - 33
frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart

@@ -10,7 +10,6 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
 import 'package:flutter/foundation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-import 'package:app_flowy/workspace/application/grid/grid_listener.dart';
 part 'row_service.freezed.dart';
 
 typedef RowUpdateCallback = void Function();
@@ -24,7 +23,6 @@ abstract class GridRowFieldDelegate {
 class GridRowCache {
   final String gridId;
   final RowsNotifier _rowsNotifier;
-  final GridRowListener _rowsListener;
   final GridRowFieldDelegate _fieldDelegate;
   List<GridRow> get clonedRows => _rowsNotifier.clonedRows;
 
@@ -39,32 +37,23 @@ class GridRowCache {
             );
           },
         ),
-        _rowsListener = GridRowListener(gridId: gridId),
         _fieldDelegate = fieldDelegate {
     //
     fieldDelegate.onFieldChanged(() => _rowsNotifier.fieldDidChange());
-
-    // listen on the row update
-    _rowsListener.rowsUpdateNotifier.addPublishListener((result) {
-      result.fold(
-        (changesets) {
-          for (final changeset in changesets) {
-            _rowsNotifier.deleteRows(changeset.deletedRows);
-            _rowsNotifier.insertRows(changeset.insertedRows);
-            _rowsNotifier.updateRows(changeset.updatedRows);
-          }
-        },
-        (err) => Log.error(err),
-      );
-    });
-    _rowsListener.start();
   }
 
   Future<void> dispose() async {
-    await _rowsListener.stop();
     _rowsNotifier.dispose();
   }
 
+  void applyChangesets(List<GridRowsChangeset> changesets) {
+    for (final changeset in changesets) {
+      _rowsNotifier.deleteRows(changeset.deletedRows);
+      _rowsNotifier.insertRows(changeset.insertedRows);
+      _rowsNotifier.updateRows(changeset.updatedRows);
+    }
+  }
+
   void addListener({
     void Function(List<GridRow>, GridRowChangeReason)? onChanged,
     bool Function()? listenWhen,
@@ -130,9 +119,8 @@ class GridRowCache {
     return _makeGridCells(rowId, data);
   }
 
-  void resetRows(List<GridBlockOrder> blocks) {
-    final rowOrders = blocks.expand((block) => block.rowOrders).toList();
-    _rowsNotifier.reset(rowOrders);
+  void initialRows(List<RowOrder> rowOrders) {
+    _rowsNotifier.initialRows(rowOrders);
   }
 
   Future<void> _loadRow(String rowId) async {
@@ -142,7 +130,11 @@ class GridRowCache {
 
     final result = await GridEventGetRow(payload).send();
     result.fold(
-      (rowData) => _rowsNotifier.rowData = rowData,
+      (rowData) {
+        if (rowData.hasRow()) {
+          _rowsNotifier.rowData = rowData.row;
+        }
+      },
       (err) => Log.error(err),
     );
   }
@@ -173,7 +165,9 @@ class RowsNotifier extends ChangeNotifier {
     required this.rowBuilder,
   });
 
-  void reset(List<RowOrder> rowOrders) {
+  List<GridRow> get clonedRows => [..._rows];
+
+  void initialRows(List<RowOrder> rowOrders) {
     _rowDataMap = HashMap();
     final rows = rowOrders.map((rowOrder) => rowBuilder(rowOrder)).toList();
     _update(rows, const GridRowChangeReason.initial());
@@ -199,20 +193,20 @@ class RowsNotifier extends ChangeNotifier {
     _update(newRows, GridRowChangeReason.delete(deletedIndex));
   }
 
-  void insertRows(List<IndexRowOrder> createdRows) {
-    if (createdRows.isEmpty) {
+  void insertRows(List<IndexRowOrder> insertRows) {
+    if (insertRows.isEmpty) {
       return;
     }
 
     InsertedIndexs insertIndexs = [];
     final List<GridRow> newRows = clonedRows;
-    for (final createdRow in createdRows) {
+    for (final insertRow in insertRows) {
       final insertIndex = InsertedIndex(
-        index: createdRow.index,
-        rowId: createdRow.rowOrder.rowId,
+        index: insertRow.index,
+        rowId: insertRow.rowOrder.rowId,
       );
       insertIndexs.add(insertIndex);
-      newRows.insert(createdRow.index, (rowBuilder(createdRow.rowOrder)));
+      newRows.insert(insertRow.index, (rowBuilder(insertRow.rowOrder)));
     }
     _update(newRows, GridRowChangeReason.insert(insertIndexs));
   }
@@ -281,8 +275,6 @@ class RowsNotifier extends ChangeNotifier {
   Row? rowDataWithId(String rowId) {
     return _rowDataMap[rowId];
   }
-
-  List<GridRow> get clonedRows => [..._rows];
 }
 
 class RowService {
@@ -310,7 +302,7 @@ class RowService {
     return GridEventMoveItem(payload).send();
   }
 
-  Future<Either<Row, FlowyError>> getRow() {
+  Future<Either<OptionalRow, FlowyError>> getRow() {
     final payload = RowIdentifierPayload.create()
       ..gridId = gridId
       ..rowId = rowId;

+ 6 - 11
frontend/rust-lib/flowy-grid/src/event_handler.rs

@@ -48,12 +48,7 @@ pub(crate) async fn get_grid_blocks_handler(
 ) -> DataResult<RepeatedGridBlock, FlowyError> {
     let params: QueryGridBlocksParams = data.into_inner().try_into()?;
     let editor = manager.get_grid_editor(&params.grid_id)?;
-    let block_ids = params
-        .block_orders
-        .into_iter()
-        .map(|block| block.block_id)
-        .collect::<Vec<String>>();
-    let repeated_grid_block = editor.get_blocks(Some(block_ids)).await?;
+    let repeated_grid_block = editor.get_blocks(Some(params.block_ids)).await?;
     data_result(repeated_grid_block)
 }
 
@@ -220,13 +215,13 @@ async fn get_type_option_data(field_rev: &FieldRevision, field_type: &FieldType)
 pub(crate) async fn get_row_handler(
     data: Data<RowIdentifierPayload>,
     manager: AppData<Arc<GridManager>>,
-) -> DataResult<Row, FlowyError> {
+) -> DataResult<OptionalRow, FlowyError> {
     let params: RowIdentifier = data.into_inner().try_into()?;
     let editor = manager.get_grid_editor(&params.grid_id)?;
-    match editor.get_row(&params.row_id).await? {
-        None => Err(FlowyError::record_not_found().context("Can not find the row")),
-        Some(row) => data_result(row),
-    }
+    let row = OptionalRow {
+        row: editor.get_row(&params.row_id).await?,
+    };
+    data_result(row)
 }
 
 #[tracing::instrument(level = "debug", skip(data, manager), err)]

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

@@ -99,7 +99,7 @@ pub enum GridEvent {
     #[event(input = "CreateRowPayload", output = "Row")]
     CreateRow = 50,
 
-    #[event(input = "RowIdentifierPayload", output = "Row")]
+    #[event(input = "RowIdentifierPayload", output = "OptionalRow")]
     GetRow = 51,
 
     #[event(input = "RowIdentifierPayload")]

+ 15 - 11
frontend/rust-lib/flowy-grid/src/services/block_manager.rs

@@ -2,7 +2,7 @@ use crate::dart_notification::{send_dart_notification, GridNotification};
 use crate::manager::GridUser;
 use crate::services::block_revision_editor::GridBlockRevisionEditor;
 use crate::services::persistence::block_index::BlockIndexCache;
-use crate::services::row::{group_row_orders, GridBlockSnapshot};
+use crate::services::row::{block_from_row_orders, GridBlockSnapshot};
 use dashmap::DashMap;
 use flowy_error::FlowyResult;
 use flowy_grid_data_model::entities::{
@@ -77,7 +77,7 @@ impl GridBlockManager {
         index_row_order.index = row_index;
 
         let _ = self
-            .notify_did_update_block(GridRowsChangeset::insert(block_id, vec![index_row_order]))
+            .notify_did_update_block(block_id, GridRowsChangeset::insert(block_id, vec![index_row_order]))
             .await?;
         Ok(row_count)
     }
@@ -102,7 +102,7 @@ impl GridBlockManager {
             changesets.push(GridBlockRevisionChangeset::from_row_count(&block_id, row_count));
 
             let _ = self
-                .notify_did_update_block(GridRowsChangeset::insert(&block_id, inserted_row_orders))
+                .notify_did_update_block(&block_id, GridRowsChangeset::insert(&block_id, inserted_row_orders))
                 .await?;
         }
 
@@ -121,7 +121,9 @@ impl GridBlockManager {
                 if let Some(row) = row_builder(row_rev.clone()) {
                     let row_order = UpdatedRowOrder::new(&row_rev, row);
                     let block_order_changeset = GridRowsChangeset::update(&editor.block_id, vec![row_order]);
-                    let _ = self.notify_did_update_block(block_order_changeset).await?;
+                    let _ = self
+                        .notify_did_update_block(&editor.block_id, block_order_changeset)
+                        .await?;
                 }
             }
         }
@@ -137,7 +139,7 @@ impl GridBlockManager {
             Some(row_order) => {
                 let _ = editor.delete_rows(vec![Cow::Borrowed(&row_id)]).await?;
                 let _ = self
-                    .notify_did_update_block(GridRowsChangeset::delete(&block_id, vec![row_order]))
+                    .notify_did_update_block(&block_id, GridRowsChangeset::delete(&block_id, vec![row_order]))
                     .await?;
             }
         }
@@ -147,15 +149,15 @@ impl GridBlockManager {
 
     pub(crate) async fn delete_rows(&self, row_orders: Vec<RowOrder>) -> FlowyResult<Vec<GridBlockRevisionChangeset>> {
         let mut changesets = vec![];
-        for block_order in group_row_orders(row_orders) {
-            let editor = self.get_editor(&block_order.block_id).await?;
+        for block_order in block_from_row_orders(row_orders) {
+            let editor = self.get_editor(&block_order.id).await?;
             let row_ids = block_order
                 .row_orders
                 .into_iter()
                 .map(|row_order| Cow::Owned(row_order.row_id))
                 .collect::<Vec<Cow<String>>>();
             let row_count = editor.delete_rows(row_ids).await?;
-            let changeset = GridBlockRevisionChangeset::from_row_count(&block_order.block_id, row_count);
+            let changeset = GridBlockRevisionChangeset::from_row_count(&block_order.id, row_count);
             changesets.push(changeset);
         }
 
@@ -181,7 +183,9 @@ impl GridBlockManager {
                     updated_rows: vec![],
                 };
 
-                let _ = self.notify_did_update_block(notified_changeset).await?;
+                let _ = self
+                    .notify_did_update_block(&editor.block_id, notified_changeset)
+                    .await?;
             }
         }
 
@@ -241,8 +245,8 @@ impl GridBlockManager {
         Ok(block_cell_revs)
     }
 
-    async fn notify_did_update_block(&self, changeset: GridRowsChangeset) -> FlowyResult<()> {
-        send_dart_notification(&self.grid_id, GridNotification::DidUpdateGridRow)
+    async fn notify_did_update_block(&self, block_id: &str, changeset: GridRowsChangeset) -> FlowyResult<()> {
+        send_dart_notification(block_id, GridNotification::DidUpdateGridRow)
             .payload(changeset)
             .send();
         Ok(())

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

@@ -424,8 +424,8 @@ impl GridRevisionEditor {
         let mut block_orders = vec![];
         for block_order in pad_read_guard.get_block_revs() {
             let row_orders = self.block_manager.get_row_orders(&block_order.block_id).await?;
-            let block_order = GridBlockOrder {
-                block_id: block_order.block_id,
+            let block_order = GridBlock {
+                id: block_order.block_id,
                 row_orders,
             };
             block_orders.push(block_order);
@@ -434,7 +434,7 @@ impl GridRevisionEditor {
         Ok(Grid {
             id: self.grid_id.clone(),
             field_orders,
-            block_orders,
+            blocks: block_orders,
         })
     }
 

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

@@ -1,6 +1,6 @@
 use crate::services::row::decode_cell_data_from_type_option_cell_data;
 use flowy_error::FlowyResult;
-use flowy_grid_data_model::entities::{Cell, GridBlock, GridBlockOrder, RepeatedGridBlock, Row, RowOrder};
+use flowy_grid_data_model::entities::{Cell, GridBlock, RepeatedGridBlock, Row, RowOrder};
 use flowy_grid_data_model::revision::{CellRevision, FieldRevision, RowRevision};
 use std::collections::HashMap;
 use std::sync::Arc;
@@ -10,13 +10,13 @@ pub struct GridBlockSnapshot {
     pub row_revs: Vec<Arc<RowRevision>>,
 }
 
-pub(crate) fn group_row_orders(row_orders: Vec<RowOrder>) -> Vec<GridBlockOrder> {
-    let mut map: HashMap<String, GridBlockOrder> = HashMap::new();
+pub(crate) fn block_from_row_orders(row_orders: Vec<RowOrder>) -> Vec<GridBlock> {
+    let mut map: HashMap<String, GridBlock> = HashMap::new();
     row_orders.into_iter().for_each(|row_order| {
         // Memory Optimization: escape clone block_id
         let block_id = row_order.block_id.clone();
         map.entry(block_id)
-            .or_insert_with(|| GridBlockOrder::new(&row_order.block_id))
+            .or_insert_with(|| GridBlock::new(&row_order.block_id, vec![]))
             .row_orders
             .push(row_order);
     });

+ 4 - 4
frontend/rust-lib/flowy-grid/tests/grid/filter_test.rs

@@ -6,7 +6,7 @@ use flowy_grid_data_model::entities::{
 };
 
 #[tokio::test]
-async fn grid_setting_create_text_filter_test() {
+async fn grid_filter_create_test() {
     let test = GridEditorTest::new().await;
     let field_rev = test.text_field();
     let payload = CreateGridFilterPayload::new(field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned()));
@@ -16,7 +16,7 @@ async fn grid_setting_create_text_filter_test() {
 
 #[tokio::test]
 #[should_panic]
-async fn grid_setting_create_text_filter_panic_test() {
+async fn grid_filter_invalid_condition_panic_test() {
     let test = GridEditorTest::new().await;
     let field_rev = test.text_field();
 
@@ -27,7 +27,7 @@ async fn grid_setting_create_text_filter_panic_test() {
 }
 
 #[tokio::test]
-async fn grid_setting_delete_text_filter_test() {
+async fn grid_filter_delete_test() {
     let mut test = GridEditorTest::new().await;
     let field_rev = test.text_field();
     let payload = CreateGridFilterPayload::new(field_rev, 100, Some("abc".to_owned()));
@@ -43,4 +43,4 @@ async fn grid_setting_delete_text_filter_test() {
 }
 
 #[tokio::test]
-async fn grid_setting_sort_test() {}
+async fn grid_filter_get_rows_test() {}

+ 40 - 5
frontend/rust-lib/flowy-grid/tests/grid/row_util.rs

@@ -1,4 +1,5 @@
 use crate::grid::script::GridEditorTest;
+use flowy_grid::services::field::DateCellContentChangeset;
 use flowy_grid::services::row::{CreateRowRevisionBuilder, CreateRowRevisionPayload};
 use flowy_grid_data_model::entities::FieldType;
 use flowy_grid_data_model::revision::FieldRevision;
@@ -17,13 +18,47 @@ impl<'a> GridRowTestBuilder<'a> {
         Self { test, inner_builder }
     }
 
-    pub fn update_text_cell(&mut self) -> Self {
-        let text_field = self
-            .test
+    pub fn update_text_cell(mut self, data: String) -> Self {
+        let text_field = self.field_rev_with_type(&FieldType::DateTime);
+        self.inner_builder.add_cell(&text_field.id, data).unwrap();
+        self
+    }
+
+    pub fn update_number_cell(mut self, data: String) -> Self {
+        let number_field = self.field_rev_with_type(&FieldType::DateTime);
+        self.inner_builder.add_cell(&number_field.id, data).unwrap();
+        self
+    }
+
+    pub fn update_date_cell(mut self, value: i64) -> Self {
+        let value = serde_json::to_string(&DateCellContentChangeset {
+            date: Some(value.to_string()),
+            time: None,
+        })
+        .unwrap();
+        let date_field = self.field_rev_with_type(&FieldType::DateTime);
+        self.inner_builder.add_cell(&date_field.id, value).unwrap();
+        self
+    }
+
+    pub fn update_checkbox_cell(mut self, data: bool) -> Self {
+        let number_field = self.field_rev_with_type(&FieldType::Checkbox);
+        self.inner_builder.add_cell(&number_field.id, data.to_string()).unwrap();
+        self
+    }
+
+    pub fn update_url_cell(mut self, data: String) -> Self {
+        let number_field = self.field_rev_with_type(&FieldType::Checkbox);
+        self.inner_builder.add_cell(&number_field.id, data).unwrap();
+        self
+    }
+
+    pub fn field_rev_with_type(&self, field_type: &FieldType) -> &FieldRevision {
+        self.test
             .field_revs
             .iter()
-            .find(|field_rev| field_rev.field_type == FieldType::RichText);
-        // self.inner_builder
+            .find(|field_rev| field_rev.field_type == &field_type)
+            .unwrap()
     }
 
     pub fn build(self) -> CreateRowRevisionPayload {

+ 11 - 23
shared-lib/flowy-grid-data-model/src/entities/grid.rs

@@ -15,7 +15,7 @@ pub struct Grid {
     pub field_orders: Vec<FieldOrder>,
 
     #[pb(index = 3)]
-    pub block_orders: Vec<GridBlockOrder>,
+    pub blocks: Vec<GridBlock>,
 }
 
 #[derive(Debug, Default, Clone, ProtoBuf)]
@@ -42,6 +42,12 @@ pub struct Row {
     pub height: i32,
 }
 
+#[derive(Debug, Default, ProtoBuf)]
+pub struct OptionalRow {
+    #[pb(index = 1, one_of)]
+    pub row: Option<Row>,
+}
+
 #[derive(Debug, Default, ProtoBuf)]
 pub struct RepeatedRow {
     #[pb(index = 1)]
@@ -66,24 +72,6 @@ impl std::convert::From<Vec<GridBlock>> for RepeatedGridBlock {
     }
 }
 
-#[derive(Debug, Clone, Default, ProtoBuf)]
-pub struct GridBlockOrder {
-    #[pb(index = 1)]
-    pub block_id: String,
-
-    #[pb(index = 2)]
-    pub row_orders: Vec<RowOrder>,
-}
-
-impl GridBlockOrder {
-    pub fn new(block_id: &str) -> Self {
-        GridBlockOrder {
-            block_id: block_id.to_owned(),
-            row_orders: vec![],
-        }
-    }
-}
-
 #[derive(Debug, Clone, Default, ProtoBuf)]
 pub struct IndexRowOrder {
     #[pb(index = 1)]
@@ -168,7 +156,7 @@ impl GridRowsChangeset {
     }
 }
 
-#[derive(Debug, Default, ProtoBuf)]
+#[derive(Debug, Clone, Default, ProtoBuf)]
 pub struct GridBlock {
     #[pb(index = 1)]
     pub id: String,
@@ -305,12 +293,12 @@ pub struct QueryGridBlocksPayload {
     pub grid_id: String,
 
     #[pb(index = 2)]
-    pub block_orders: Vec<GridBlockOrder>,
+    pub block_ids: Vec<String>,
 }
 
 pub struct QueryGridBlocksParams {
     pub grid_id: String,
-    pub block_orders: Vec<GridBlockOrder>,
+    pub block_ids: Vec<String>,
 }
 
 impl TryInto<QueryGridBlocksParams> for QueryGridBlocksPayload {
@@ -320,7 +308,7 @@ impl TryInto<QueryGridBlocksParams> for QueryGridBlocksPayload {
         let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
         Ok(QueryGridBlocksParams {
             grid_id: grid_id.0,
-            block_orders: self.block_orders,
+            block_ids: self.block_ids,
         })
     }
 }

+ 1 - 1
shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs

@@ -32,7 +32,7 @@ pub struct GridRevision {
     pub fields: Vec<FieldRevision>,
     pub blocks: Vec<GridBlockRevision>,
 
-    #[serde(default)]
+    #[serde(default, skip)]
     pub setting: GridSettingRevision,
 }