Explorar o código

Merge pull request #463 from AppFlowy-IO/fix_0.0.4_bugs

Fix 0.0.4 bugs
Nathan.fooo %!s(int64=3) %!d(string=hai) anos
pai
achega
b3764e601f
Modificáronse 39 ficheiros con 1297 adicións e 1252 borrados
  1. 14 14
      frontend/app_flowy/lib/startup/deps_resolver.dart
  2. 8 8
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_listener.dart
  3. 289 37
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart
  4. 18 41
      frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart
  5. 18 56
      frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart
  6. 19 59
      frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart
  7. 36 32
      frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart
  8. 33 58
      frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart
  9. 33 94
      frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart
  10. 19 54
      frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart
  11. 5 3
      frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart
  12. 7 6
      frontend/app_flowy/lib/workspace/application/grid/field/field_listener.dart
  13. 2 1
      frontend/app_flowy/lib/workspace/application/grid/field/grid_listenr.dart
  14. 11 3
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  15. 43 27
      frontend/app_flowy/lib/workspace/application/grid/grid_service.dart
  16. 8 17
      frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart
  17. 9 10
      frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart
  18. 66 54
      frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart
  19. 3 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
  20. 42 10
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart
  21. 3 3
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart
  22. 3 3
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell.dart
  23. 3 3
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart
  24. 62 16
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart
  25. 6 16
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart
  26. 3 3
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart
  27. 26 23
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart
  28. 54 25
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart
  29. 1 1
      frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart
  30. 1 1
      frontend/app_flowy/packages/flowy_sdk/lib/log.dart
  31. 68 105
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
  32. 13 18
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
  33. 11 0
      frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs
  34. 35 41
      frontend/rust-lib/flowy-grid/src/services/block_meta_manager.rs
  35. 9 2
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  36. 4 0
      frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs
  37. 21 18
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  38. 286 382
      shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
  39. 5 7
      shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto

+ 14 - 14
frontend/app_flowy/lib/startup/deps_resolver.dart

@@ -168,34 +168,34 @@ void _resolveGridDeps(GetIt getIt) {
     ),
     ),
   );
   );
 
 
-  getIt.registerFactoryParam<TextCellBloc, GridCell, void>(
-    (cellData, _) => TextCellBloc(
-      cellData: cellData,
+  getIt.registerFactoryParam<TextCellBloc, GridDefaultCellContext, void>(
+    (context, _) => TextCellBloc(
+      cellContext: context,
     ),
     ),
   );
   );
 
 
-  getIt.registerFactoryParam<SelectionCellBloc, GridCell, void>(
-    (cellData, _) => SelectionCellBloc(
-      cellData: cellData,
+  getIt.registerFactoryParam<SelectionCellBloc, GridSelectOptionCellContext, void>(
+    (context, _) => SelectionCellBloc(
+      cellContext: context,
     ),
     ),
   );
   );
 
 
-  getIt.registerFactoryParam<NumberCellBloc, GridCell, void>(
-    (cellData, _) => NumberCellBloc(
-      cellData: cellData,
+  getIt.registerFactoryParam<NumberCellBloc, GridDefaultCellContext, void>(
+    (context, _) => NumberCellBloc(
+      cellContext: context,
     ),
     ),
   );
   );
 
 
-  getIt.registerFactoryParam<DateCellBloc, GridCell, void>(
-    (cellData, _) => DateCellBloc(
-      cellData: cellData,
+  getIt.registerFactoryParam<DateCellBloc, GridDefaultCellContext, void>(
+    (context, _) => DateCellBloc(
+      cellContext: context,
     ),
     ),
   );
   );
 
 
-  getIt.registerFactoryParam<CheckboxCellBloc, GridCell, void>(
+  getIt.registerFactoryParam<CheckboxCellBloc, GridDefaultCellContext, void>(
     (cellData, _) => CheckboxCellBloc(
     (cellData, _) => CheckboxCellBloc(
       service: CellService(),
       service: CellService(),
-      cellData: cellData,
+      cellContext: cellData,
     ),
     ),
   );
   );
 
 

+ 8 - 8
frontend/app_flowy/lib/workspace/application/grid/cell/cell_listener.dart

@@ -1,5 +1,4 @@
 import 'package:dartz/dartz.dart';
 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-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
 import 'package:flowy_infra/notifier.dart';
 import 'package:flowy_infra/notifier.dart';
@@ -7,16 +6,17 @@ import 'dart:async';
 import 'dart:typed_data';
 import 'dart:typed_data';
 import 'package:app_flowy/core/notification_helper.dart';
 import 'package:app_flowy/core/notification_helper.dart';
 
 
-typedef UpdateFieldNotifiedValue = Either<CellNotificationData, FlowyError>;
+typedef UpdateFieldNotifiedValue = Either<Unit, FlowyError>;
 
 
 class CellListener {
 class CellListener {
   final String rowId;
   final String rowId;
   final String fieldId;
   final String fieldId;
-  PublishNotifier<UpdateFieldNotifiedValue>? updateCellNotifier = PublishNotifier();
+  PublishNotifier<UpdateFieldNotifiedValue>? _updateCellNotifier = PublishNotifier();
   GridNotificationListener? _listener;
   GridNotificationListener? _listener;
   CellListener({required this.rowId, required this.fieldId});
   CellListener({required this.rowId, required this.fieldId});
 
 
-  void start() {
+  void start({required void Function(UpdateFieldNotifiedValue) onCellChanged}) {
+    _updateCellNotifier?.addPublishListener(onCellChanged);
     _listener = GridNotificationListener(objectId: "$rowId:$fieldId", handler: _handler);
     _listener = GridNotificationListener(objectId: "$rowId:$fieldId", handler: _handler);
   }
   }
 
 
@@ -24,8 +24,8 @@ class CellListener {
     switch (ty) {
     switch (ty) {
       case GridNotification.DidUpdateCell:
       case GridNotification.DidUpdateCell:
         result.fold(
         result.fold(
-          (payload) => updateCellNotifier?.value = left(CellNotificationData.fromBuffer(payload)),
-          (error) => updateCellNotifier?.value = right(error),
+          (payload) => _updateCellNotifier?.value = left(unit),
+          (error) => _updateCellNotifier?.value = right(error),
         );
         );
         break;
         break;
       default:
       default:
@@ -35,7 +35,7 @@ class CellListener {
 
 
   Future<void> stop() async {
   Future<void> stop() async {
     await _listener?.stop();
     await _listener?.stop();
-    updateCellNotifier?.dispose();
-    updateCellNotifier = null;
+    _updateCellNotifier?.dispose();
+    _updateCellNotifier = null;
   }
   }
 }
 }

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

@@ -1,12 +1,290 @@
+import 'dart:async';
 import 'dart:collection';
 import 'dart:collection';
 
 
-import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:dartz/dartz.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/dispatch/dispatch.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-data-model/grid.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flutter/foundation.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
+import 'package:equatable/equatable.dart';
+part 'cell_service.freezed.dart';
+
+typedef GridDefaultCellContext = GridCellContext<Cell>;
+typedef GridSelectOptionCellContext = GridCellContext<SelectOptionContext>;
+
+// ignore: must_be_immutable
+class GridCellContext<T> extends Equatable {
+  final GridCell gridCell;
+  final GridCellCache cellCache;
+  final GridCellCacheKey _cacheKey;
+  final GridCellDataLoader<T> cellDataLoader;
+  final CellService _cellService = CellService();
+
+  late final CellListener _cellListener;
+  late final ValueNotifier<T?> _cellDataNotifier;
+  bool isListening = false;
+  VoidCallback? _onFieldChangedFn;
+  Timer? _delayOperation;
+
+  GridCellContext({
+    required this.gridCell,
+    required this.cellCache,
+    required this.cellDataLoader,
+  }) : _cacheKey = GridCellCacheKey(objectId: gridCell.rowId, fieldId: gridCell.field.id);
+
+  GridCellContext<T> clone() {
+    return GridCellContext(
+      gridCell: gridCell,
+      cellDataLoader: cellDataLoader,
+      cellCache: cellCache,
+    );
+  }
+
+  String get gridId => gridCell.gridId;
+
+  String get rowId => gridCell.rowId;
+
+  String get cellId => gridCell.rowId + gridCell.field.id;
+
+  String get fieldId => gridCell.field.id;
+
+  Field get field => gridCell.field;
+
+  FieldType get fieldType => gridCell.field.fieldType;
+
+  GridCellCacheKey get cacheKey => _cacheKey;
+
+  VoidCallback? startListening({required void Function(T) onCellChanged}) {
+    if (isListening) {
+      Log.error("Already started. It seems like you should call clone first");
+      return null;
+    }
+
+    isListening = true;
+    _cellDataNotifier = ValueNotifier(cellCache.get(cacheKey));
+    _cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id);
+    _cellListener.start(onCellChanged: (result) {
+      result.fold(
+        (_) => _loadData(),
+        (err) => Log.error(err),
+      );
+    });
+
+    if (cellDataLoader.reloadOnFieldChanged) {
+      _onFieldChangedFn = () {
+        _loadData();
+      };
+      cellCache.addListener(cacheKey, _onFieldChangedFn!);
+    }
+
+    onCellChangedFn() {
+      final value = _cellDataNotifier.value;
+      if (value is T) {
+        onCellChanged(value);
+      }
+
+      if (cellDataLoader.reloadOnCellChanged) {
+        _loadData();
+      }
+    }
+
+    _cellDataNotifier.addListener(onCellChangedFn);
+    return onCellChangedFn;
+  }
+
+  void removeListener(VoidCallback fn) {
+    _cellDataNotifier.removeListener(fn);
+  }
+
+  T? getCellData() {
+    final data = cellCache.get(cacheKey);
+    if (data == null) {
+      _loadData();
+    }
+    return data;
+  }
+
+  void saveCellData(String data) {
+    _cellService.updateCell(gridId: gridId, fieldId: field.id, rowId: rowId, data: data).then((result) {
+      result.fold((l) => null, (err) => Log.error(err));
+    });
+  }
+
+  void _loadData() {
+    _delayOperation?.cancel();
+    _delayOperation = Timer(const Duration(milliseconds: 10), () {
+      cellDataLoader.loadData().then((data) {
+        _cellDataNotifier.value = data;
+        cellCache.insert(GridCellCacheData(key: cacheKey, object: data));
+      });
+    });
+  }
+
+  void dispose() {
+    _delayOperation?.cancel();
+
+    if (_onFieldChangedFn != null) {
+      cellCache.removeListener(cacheKey, _onFieldChangedFn!);
+      _onFieldChangedFn = null;
+    }
+  }
+
+  @override
+  List<Object> get props => [cellCache.get(cacheKey) ?? "", cellId];
+}
+
+abstract class GridCellDataLoader<T> {
+  Future<T?> loadData();
+
+  bool get reloadOnFieldChanged => true;
+  bool get reloadOnCellChanged => false;
+}
+
+abstract class GridCellDataConfig {
+  bool get reloadOnFieldChanged => true;
+  bool get reloadOnCellChanged => false;
+}
+
+class DefaultCellDataLoader extends GridCellDataLoader<Cell> {
+  final CellService service = CellService();
+  final GridCell gridCell;
+  @override
+  final bool reloadOnCellChanged;
+
+  DefaultCellDataLoader({
+    required this.gridCell,
+    this.reloadOnCellChanged = false,
+  });
+
+  @override
+  Future<Cell?> loadData() {
+    final fut = service.getCell(
+      gridId: gridCell.gridId,
+      fieldId: gridCell.field.id,
+      rowId: gridCell.rowId,
+    );
+    return fut.then((result) {
+      return result.fold((data) => data, (err) {
+        Log.error(err);
+        return null;
+      });
+    });
+  }
+}
+
+// key: rowId
+typedef GridCellMap = LinkedHashMap<String, GridCell>;
+
+class GridCellCacheData {
+  GridCellCacheKey key;
+  dynamic object;
+  GridCellCacheData({
+    required this.key,
+    required this.object,
+  });
+}
+
+class GridCellCacheKey {
+  final String fieldId;
+  final String objectId;
+  GridCellCacheKey({
+    required this.fieldId,
+    required this.objectId,
+  });
+}
+
+abstract class GridCellFieldDelegate {
+  void onFieldChanged(void Function(String) callback);
+  void dispose();
+}
+
+class GridCellCache {
+  final String gridId;
+  final GridCellFieldDelegate fieldDelegate;
+
+  /// fieldId: {objectId: callback}
+  final Map<String, Map<String, List<VoidCallback>>> _listenerByFieldId = {};
+
+  /// fieldId: {cacheKey: cacheData}
+  final Map<String, Map<String, dynamic>> _cellDataByFieldId = {};
+  GridCellCache({
+    required this.gridId,
+    required this.fieldDelegate,
+  }) {
+    fieldDelegate.onFieldChanged((fieldId) {
+      _cellDataByFieldId.remove(fieldId);
+      final map = _listenerByFieldId[fieldId];
+      if (map != null) {
+        for (final callbacks in map.values) {
+          for (final callback in callbacks) {
+            callback();
+          }
+        }
+      }
+    });
+  }
+
+  void addListener(GridCellCacheKey cacheKey, VoidCallback callback) {
+    var map = _listenerByFieldId[cacheKey.fieldId];
+    if (map == null) {
+      _listenerByFieldId[cacheKey.fieldId] = {};
+      map = _listenerByFieldId[cacheKey.fieldId];
+      map![cacheKey.objectId] = [callback];
+    } else {
+      var objects = map[cacheKey.objectId];
+      if (objects == null) {
+        map[cacheKey.objectId] = [callback];
+      } else {
+        objects.add(callback);
+      }
+    }
+  }
+
+  void removeListener(GridCellCacheKey cacheKey, VoidCallback fn) {
+    var callbacks = _listenerByFieldId[cacheKey.fieldId]?[cacheKey.objectId];
+    final index = callbacks?.indexWhere((callback) => callback == fn);
+    if (index != null && index != -1) {
+      callbacks?.removeAt(index);
+    }
+  }
+
+  void insert<T extends GridCellCacheData>(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;
+  }
+
+  T? get<T>(GridCellCacheKey key) {
+    final map = _cellDataByFieldId[key.fieldId];
+    if (map == null) {
+      return null;
+    } else {
+      final object = map[key.objectId];
+      if (object is T) {
+        return object;
+      } else {
+        if (object != null) {
+          Log.error("Cache data type does not match the cache data type");
+        }
+
+        return null;
+      }
+    }
+  }
+
+  Future<void> dispose() async {
+    fieldDelegate.dispose();
+  }
+}
 
 
 class CellService {
 class CellService {
   CellService();
   CellService();
@@ -38,38 +316,12 @@ class CellService {
   }
   }
 }
 }
 
 
-class CellCache {
-  final CellService _cellService;
-  final HashMap<String, Cell> _cellDataMap = HashMap();
-
-  CellCache() : _cellService = CellService();
-
-  Future<Option<Cell>> getCellData(GridCell identifier) async {
-    final cellId = _cellId(identifier);
-    final Cell? data = _cellDataMap[cellId];
-    if (data != null) {
-      return Future(() => Some(data));
-    }
-
-    final result = await _cellService.getCell(
-      gridId: identifier.gridId,
-      fieldId: identifier.field.id,
-      rowId: identifier.rowId,
-    );
-
-    return result.fold(
-      (cell) {
-        _cellDataMap[_cellId(identifier)] = cell;
-        return Some(cell);
-      },
-      (err) {
-        Log.error(err);
-        return none();
-      },
-    );
-  }
-
-  String _cellId(GridCell identifier) {
-    return "${identifier.rowId}/${identifier.field.id}";
-  }
+@freezed
+class GridCell with _$GridCell {
+  const factory GridCell({
+    required String gridId,
+    required String rowId,
+    required Field field,
+    Cell? cell,
+  }) = _GridCell;
 }
 }

+ 18 - 41
frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart

@@ -1,6 +1,3 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
-import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
-import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell;
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell;
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -10,15 +7,13 @@ import 'cell_service.dart';
 part 'checkbox_cell_bloc.freezed.dart';
 part 'checkbox_cell_bloc.freezed.dart';
 
 
 class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
 class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
-  final CellService _service;
-  final CellListener _cellListener;
+  final GridDefaultCellContext cellContext;
+  void Function()? _onCellChangedFn;
 
 
   CheckboxCellBloc({
   CheckboxCellBloc({
     required CellService service,
     required CellService service,
-    required GridCell cellData,
-  })  : _service = service,
-        _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
-        super(CheckboxCellState.initial(cellData)) {
+    required this.cellContext,
+  }) : super(CheckboxCellState.initial(cellContext)) {
     on<CheckboxCellEvent>(
     on<CheckboxCellEvent>(
       (event, emit) async {
       (event, emit) async {
         await event.map(
         await event.map(
@@ -38,42 +33,25 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
 
 
   @override
   @override
   Future<void> close() async {
   Future<void> close() async {
-    await _cellListener.stop();
+    if (_onCellChangedFn != null) {
+      cellContext.removeListener(_onCellChangedFn!);
+      _onCellChangedFn = null;
+    }
+
+    cellContext.dispose();
     return super.close();
     return super.close();
   }
   }
 
 
   void _startListening() {
   void _startListening() {
-    _cellListener.updateCellNotifier?.addPublishListener((result) {
-      result.fold(
-        (notificationData) async => await _loadCellData(),
-        (err) => Log.error(err),
-      );
-    });
-    _cellListener.start();
-  }
-
-  Future<void> _loadCellData() async {
-    final result = await _service.getCell(
-      gridId: state.cellData.gridId,
-      fieldId: state.cellData.field.id,
-      rowId: state.cellData.rowId,
-    );
-    if (isClosed) {
-      return;
-    }
-    result.fold(
-      (cell) => add(CheckboxCellEvent.didReceiveCellUpdate(cell)),
-      (err) => Log.error(err),
-    );
+    _onCellChangedFn = cellContext.startListening(onCellChanged: ((cell) {
+      if (!isClosed) {
+        add(CheckboxCellEvent.didReceiveCellUpdate(cell));
+      }
+    }));
   }
   }
 
 
   void _updateCellData() {
   void _updateCellData() {
-    _service.updateCell(
-      gridId: state.cellData.gridId,
-      fieldId: state.cellData.field.id,
-      rowId: state.cellData.rowId,
-      data: !state.isSelected ? "Yes" : "No",
-    );
+    cellContext.saveCellData(!state.isSelected ? "Yes" : "No");
   }
   }
 }
 }
 
 
@@ -87,12 +65,11 @@ class CheckboxCellEvent with _$CheckboxCellEvent {
 @freezed
 @freezed
 class CheckboxCellState with _$CheckboxCellState {
 class CheckboxCellState with _$CheckboxCellState {
   const factory CheckboxCellState({
   const factory CheckboxCellState({
-    required GridCell cellData,
     required bool isSelected,
     required bool isSelected,
   }) = _CheckboxCellState;
   }) = _CheckboxCellState;
 
 
-  factory CheckboxCellState.initial(GridCell cellData) {
-    return CheckboxCellState(cellData: cellData, isSelected: _isSelected(cellData.cell));
+  factory CheckboxCellState.initial(GridCellContext context) {
+    return CheckboxCellState(isSelected: _isSelected(context.getCellData()));
   }
   }
 }
 }
 
 

+ 18 - 56
frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart

@@ -1,7 +1,3 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
-import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
-import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
-import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell, Field;
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell, Field;
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -11,15 +7,10 @@ import 'cell_service.dart';
 part 'date_cell_bloc.freezed.dart';
 part 'date_cell_bloc.freezed.dart';
 
 
 class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
 class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
-  final CellService _service;
-  final CellListener _cellListener;
-  final SingleFieldListener _fieldListener;
+  final GridDefaultCellContext cellContext;
+  void Function()? _onCellChangedFn;
 
 
-  DateCellBloc({required GridCell cellData})
-      : _service = CellService(),
-        _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
-        _fieldListener = SingleFieldListener(fieldId: cellData.field.id),
-        super(DateCellState.initial(cellData)) {
+  DateCellBloc({required this.cellContext}) : super(DateCellState.initial(cellContext)) {
     on<DateCellEvent>(
     on<DateCellEvent>(
       (event, emit) async {
       (event, emit) async {
         event.map(
         event.map(
@@ -31,13 +22,11 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
           },
           },
           didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
           didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
             emit(state.copyWith(
             emit(state.copyWith(
-              cellData: state.cellData.copyWith(cell: value.cell),
               content: value.cell.content,
               content: value.cell.content,
             ));
             ));
           },
           },
           didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
           didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
             emit(state.copyWith(field: value.field));
             emit(state.copyWith(field: value.field));
-            _loadCellData();
           },
           },
         );
         );
       },
       },
@@ -46,52 +35,27 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
 
 
   @override
   @override
   Future<void> close() async {
   Future<void> close() async {
-    await _cellListener.stop();
-    await _fieldListener.stop();
+    if (_onCellChangedFn != null) {
+      cellContext.removeListener(_onCellChangedFn!);
+      _onCellChangedFn = null;
+    }
+    cellContext.dispose();
     return super.close();
     return super.close();
   }
   }
 
 
   void _startListening() {
   void _startListening() {
-    _cellListener.updateCellNotifier?.addPublishListener((result) {
-      result.fold(
-        (notificationData) => _loadCellData(),
-        (err) => Log.error(err),
-      );
-    }, listenWhen: () => !isClosed);
-    _cellListener.start();
-
-    _fieldListener.updateFieldNotifier?.addPublishListener((result) {
-      result.fold(
-        (field) => add(DateCellEvent.didReceiveFieldUpdate(field)),
-        (err) => Log.error(err),
-      );
-    }, listenWhen: () => !isClosed);
-    _fieldListener.start();
-  }
-
-  Future<void> _loadCellData() async {
-    final result = await _service.getCell(
-      gridId: state.cellData.gridId,
-      fieldId: state.cellData.field.id,
-      rowId: state.cellData.rowId,
-    );
-    if (isClosed) {
-      return;
-    }
-    result.fold(
-      (cell) => add(DateCellEvent.didReceiveCellUpdate(cell)),
-      (err) => Log.error(err),
+    _onCellChangedFn = cellContext.startListening(
+      onCellChanged: ((cell) {
+        if (!isClosed) {
+          add(DateCellEvent.didReceiveCellUpdate(cell));
+        }
+      }),
     );
     );
   }
   }
 
 
   void _updateCellData(DateTime day) {
   void _updateCellData(DateTime day) {
     final data = day.millisecondsSinceEpoch ~/ 1000;
     final data = day.millisecondsSinceEpoch ~/ 1000;
-    _service.updateCell(
-      gridId: state.cellData.gridId,
-      fieldId: state.cellData.field.id,
-      rowId: state.cellData.rowId,
-      data: data.toString(),
-    );
+    cellContext.saveCellData(data.toString());
   }
   }
 }
 }
 
 
@@ -106,15 +70,13 @@ class DateCellEvent with _$DateCellEvent {
 @freezed
 @freezed
 class DateCellState with _$DateCellState {
 class DateCellState with _$DateCellState {
   const factory DateCellState({
   const factory DateCellState({
-    required GridCell cellData,
     required String content,
     required String content,
     required Field field,
     required Field field,
     DateTime? selectedDay,
     DateTime? selectedDay,
   }) = _DateCellState;
   }) = _DateCellState;
 
 
-  factory DateCellState.initial(GridCell cellData) => DateCellState(
-        cellData: cellData,
-        field: cellData.field,
-        content: cellData.cell?.content ?? "",
+  factory DateCellState.initial(GridCellContext context) => DateCellState(
+        field: context.field,
+        content: context.getCellData()?.content ?? "",
       );
       );
 }
 }

+ 19 - 59
frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart

@@ -1,7 +1,3 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
-import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
-import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
-import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -11,16 +7,12 @@ import 'cell_service.dart';
 part 'number_cell_bloc.freezed.dart';
 part 'number_cell_bloc.freezed.dart';
 
 
 class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
 class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
-  final CellService _service;
-  final CellListener _cellListener;
-  final SingleFieldListener _fieldListener;
+  final GridDefaultCellContext cellContext;
+  void Function()? _onCellChangedFn;
 
 
   NumberCellBloc({
   NumberCellBloc({
-    required GridCell cellData,
-  })  : _service = CellService(),
-        _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
-        _fieldListener = SingleFieldListener(fieldId: cellData.field.id),
-        super(NumberCellState.initial(cellData)) {
+    required this.cellContext,
+  }) : super(NumberCellState.initial(cellContext)) {
     on<NumberCellEvent>(
     on<NumberCellEvent>(
       (event, emit) async {
       (event, emit) async {
         await event.map(
         await event.map(
@@ -39,58 +31,26 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
   }
   }
 
 
   Future<void> _updateCellValue(_UpdateCell value, Emitter<NumberCellState> emit) async {
   Future<void> _updateCellValue(_UpdateCell value, Emitter<NumberCellState> emit) async {
-    final result = await _service.updateCell(
-      gridId: state.cellData.gridId,
-      fieldId: state.cellData.field.id,
-      rowId: state.cellData.rowId,
-      data: value.text,
-    );
-    result.fold(
-      (field) => _getCellData(),
-      (err) => Log.error(err),
-    );
+    cellContext.saveCellData(value.text);
   }
   }
 
 
   @override
   @override
   Future<void> close() async {
   Future<void> close() async {
-    await _cellListener.stop();
-    await _fieldListener.stop();
+    if (_onCellChangedFn != null) {
+      cellContext.removeListener(_onCellChangedFn!);
+      _onCellChangedFn = null;
+    }
+    cellContext.dispose();
     return super.close();
     return super.close();
   }
   }
 
 
   void _startListening() {
   void _startListening() {
-    _cellListener.updateCellNotifier?.addPublishListener((result) {
-      result.fold(
-        (notificationData) async {
-          await _getCellData();
-        },
-        (err) => Log.error(err),
-      );
-    });
-    _cellListener.start();
-
-    _fieldListener.updateFieldNotifier?.addPublishListener((result) {
-      result.fold(
-        (field) => _getCellData(),
-        (err) => Log.error(err),
-      );
-    });
-    _fieldListener.start();
-  }
-
-  Future<void> _getCellData() async {
-    final result = await _service.getCell(
-      gridId: state.cellData.gridId,
-      fieldId: state.cellData.field.id,
-      rowId: state.cellData.rowId,
-    );
-
-    if (isClosed) {
-      return;
-    }
-    result.fold(
-      (cell) => add(NumberCellEvent.didReceiveCellUpdate(cell)),
-      (err) => Log.error(err),
+    _onCellChangedFn = cellContext.startListening(
+      onCellChanged: ((cell) {
+        if (!isClosed) {
+          add(NumberCellEvent.didReceiveCellUpdate(cell));
+        }
+      }),
     );
     );
   }
   }
 }
 }
@@ -105,11 +65,11 @@ class NumberCellEvent with _$NumberCellEvent {
 @freezed
 @freezed
 class NumberCellState with _$NumberCellState {
 class NumberCellState with _$NumberCellState {
   const factory NumberCellState({
   const factory NumberCellState({
-    required GridCell cellData,
     required String content,
     required String content,
   }) = _NumberCellState;
   }) = _NumberCellState;
 
 
-  factory NumberCellState.initial(GridCell cellData) {
-    return NumberCellState(cellData: cellData, content: cellData.cell?.content ?? "");
+  factory NumberCellState.initial(GridDefaultCellContext context) {
+    final cell = context.getCellData();
+    return NumberCellState(content: cell?.content ?? "");
   }
   }
 }
 }

+ 36 - 32
frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart

@@ -1,19 +1,43 @@
-import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:dartz/dartz.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 
 
+import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
+
+import 'cell_service.dart';
+
+class SelectOptionCellDataLoader extends GridCellDataLoader<SelectOptionContext> {
+  final SelectOptionService service;
+  final GridCell gridCell;
+  SelectOptionCellDataLoader({
+    required this.gridCell,
+  }) : service = SelectOptionService(gridCell: gridCell);
+  @override
+  Future<SelectOptionContext?> loadData() async {
+    return service.getOpitonContext().then((result) {
+      return result.fold(
+        (data) => data,
+        (err) {
+          Log.error(err);
+          return null;
+        },
+      );
+    });
+  }
+}
+
 class SelectOptionService {
 class SelectOptionService {
-  SelectOptionService();
+  final GridCell gridCell;
+  SelectOptionService({required this.gridCell});
 
 
-  Future<Either<Unit, FlowyError>> create({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
-    required String name,
-  }) {
+  String get gridId => gridCell.gridId;
+  String get fieldId => gridCell.field.id;
+  String get rowId => gridCell.rowId;
+
+  Future<Either<Unit, FlowyError>> create({required String name}) {
     return TypeOptionService(gridId: gridId, fieldId: fieldId).newOption(name: name).then(
     return TypeOptionService(gridId: gridId, fieldId: fieldId).newOption(name: name).then(
       (result) {
       (result) {
         return result.fold(
         return result.fold(
@@ -34,9 +58,6 @@ class SelectOptionService {
   }
   }
 
 
   Future<Either<Unit, FlowyError>> update({
   Future<Either<Unit, FlowyError>> update({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
     required SelectOption option,
     required SelectOption option,
   }) {
   }) {
     final cellIdentifier = CellIdentifierPayload.create()
     final cellIdentifier = CellIdentifierPayload.create()
@@ -50,9 +71,6 @@ class SelectOptionService {
   }
   }
 
 
   Future<Either<Unit, FlowyError>> delete({
   Future<Either<Unit, FlowyError>> delete({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
     required SelectOption option,
     required SelectOption option,
   }) {
   }) {
     final cellIdentifier = CellIdentifierPayload.create()
     final cellIdentifier = CellIdentifierPayload.create()
@@ -67,11 +85,7 @@ class SelectOptionService {
     return GridEventUpdateSelectOption(payload).send();
     return GridEventUpdateSelectOption(payload).send();
   }
   }
 
 
-  Future<Either<SelectOptionContext, FlowyError>> getOpitonContext({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
-  }) {
+  Future<Either<SelectOptionContext, FlowyError>> getOpitonContext() {
     final payload = CellIdentifierPayload.create()
     final payload = CellIdentifierPayload.create()
       ..gridId = gridId
       ..gridId = gridId
       ..fieldId = fieldId
       ..fieldId = fieldId
@@ -80,12 +94,7 @@ class SelectOptionService {
     return GridEventGetSelectOptionContext(payload).send();
     return GridEventGetSelectOptionContext(payload).send();
   }
   }
 
 
-  Future<Either<void, FlowyError>> select({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
-    required String optionId,
-  }) {
+  Future<Either<void, FlowyError>> select({required String optionId}) {
     final payload = SelectOptionCellChangesetPayload.create()
     final payload = SelectOptionCellChangesetPayload.create()
       ..gridId = gridId
       ..gridId = gridId
       ..fieldId = fieldId
       ..fieldId = fieldId
@@ -94,12 +103,7 @@ class SelectOptionService {
     return GridEventUpdateCellSelectOption(payload).send();
     return GridEventUpdateCellSelectOption(payload).send();
   }
   }
 
 
-  Future<Either<void, FlowyError>> remove({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
-    required String optionId,
-  }) {
+  Future<Either<void, FlowyError>> unSelect({required String optionId}) {
     final payload = SelectOptionCellChangesetPayload.create()
     final payload = SelectOptionCellChangesetPayload.create()
       ..gridId = gridId
       ..gridId = gridId
       ..fieldId = fieldId
       ..fieldId = fieldId

+ 33 - 58
frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart

@@ -1,35 +1,29 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
-import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
-import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
-import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
-import 'package:flowy_sdk/log.dart';
+import 'dart:async';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-import 'dart:async';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
 
 
 part 'selection_cell_bloc.freezed.dart';
 part 'selection_cell_bloc.freezed.dart';
 
 
 class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
 class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
-  final SelectOptionService _service;
-  final CellListener _cellListener;
-  final SingleFieldListener _fieldListener;
+  final GridSelectOptionCellContext cellContext;
+  void Function()? _onCellChangedFn;
 
 
   SelectionCellBloc({
   SelectionCellBloc({
-    required GridCell cellData,
-  })  : _service = SelectOptionService(),
-        _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
-        _fieldListener = SingleFieldListener(fieldId: cellData.field.id),
-        super(SelectionCellState.initial(cellData)) {
+    required this.cellContext,
+  }) : super(SelectionCellState.initial(cellContext)) {
     on<SelectionCellEvent>(
     on<SelectionCellEvent>(
       (event, emit) async {
       (event, emit) async {
         await event.map(
         await event.map(
           initial: (_InitialCell value) async {
           initial: (_InitialCell value) async {
-            _loadOptions();
             _startListening();
             _startListening();
           },
           },
           didReceiveOptions: (_DidReceiveOptions value) {
           didReceiveOptions: (_DidReceiveOptions value) {
-            emit(state.copyWith(options: value.options, selectedOptions: value.selectedOptions));
+            emit(state.copyWith(
+              options: value.options,
+              selectedOptions: value.selectedOptions,
+            ));
           },
           },
         );
         );
       },
       },
@@ -38,46 +32,25 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
 
 
   @override
   @override
   Future<void> close() async {
   Future<void> close() async {
-    await _cellListener.stop();
-    await _fieldListener.stop();
-    return super.close();
-  }
-
-  void _loadOptions() async {
-    final result = await _service.getOpitonContext(
-      gridId: state.cellData.gridId,
-      fieldId: state.cellData.field.id,
-      rowId: state.cellData.rowId,
-    );
-    if (isClosed) {
-      return;
+    if (_onCellChangedFn != null) {
+      cellContext.removeListener(_onCellChangedFn!);
+      _onCellChangedFn = null;
     }
     }
-
-    result.fold(
-      (selectOptionContext) => add(SelectionCellEvent.didReceiveOptions(
-        selectOptionContext.options,
-        selectOptionContext.selectOptions,
-      )),
-      (err) => Log.error(err),
-    );
+    cellContext.dispose();
+    return super.close();
   }
   }
 
 
   void _startListening() {
   void _startListening() {
-    _cellListener.updateCellNotifier?.addPublishListener((result) {
-      result.fold(
-        (notificationData) => _loadOptions(),
-        (err) => Log.error(err),
-      );
-    });
-    _cellListener.start();
-
-    _fieldListener.updateFieldNotifier?.addPublishListener((result) {
-      result.fold(
-        (field) => _loadOptions(),
-        (err) => Log.error(err),
-      );
-    });
-    _fieldListener.start();
+    _onCellChangedFn = cellContext.startListening(
+      onCellChanged: ((selectOptionContext) {
+        if (!isClosed) {
+          add(SelectionCellEvent.didReceiveOptions(
+            selectOptionContext.options,
+            selectOptionContext.selectOptions,
+          ));
+        }
+      }),
+    );
   }
   }
 }
 }
 
 
@@ -93,14 +66,16 @@ class SelectionCellEvent with _$SelectionCellEvent {
 @freezed
 @freezed
 class SelectionCellState with _$SelectionCellState {
 class SelectionCellState with _$SelectionCellState {
   const factory SelectionCellState({
   const factory SelectionCellState({
-    required GridCell cellData,
     required List<SelectOption> options,
     required List<SelectOption> options,
     required List<SelectOption> selectedOptions,
     required List<SelectOption> selectedOptions,
   }) = _SelectionCellState;
   }) = _SelectionCellState;
 
 
-  factory SelectionCellState.initial(GridCell cellData) => SelectionCellState(
-        cellData: cellData,
-        options: [],
-        selectedOptions: [],
-      );
+  factory SelectionCellState.initial(GridSelectOptionCellContext context) {
+    final data = context.getCellData();
+
+    return SelectionCellState(
+      options: data?.options ?? [],
+      selectedOptions: data?.selectOptions ?? [],
+    );
+  }
 }
 }

+ 33 - 94
frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart

@@ -1,8 +1,5 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
-import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
-import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -13,28 +10,19 @@ part 'selection_editor_bloc.freezed.dart';
 
 
 class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
 class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
   final SelectOptionService _selectOptionService;
   final SelectOptionService _selectOptionService;
-  final SingleFieldListener _fieldListener;
-  final CellListener _cellListener;
-  Timer? _delayOperation;
+  final GridSelectOptionCellContext cellContext;
+  void Function()? _onCellChangedFn;
 
 
   SelectOptionEditorBloc({
   SelectOptionEditorBloc({
-    required GridCell cellData,
-    required List<SelectOption> options,
-    required List<SelectOption> selectedOptions,
-  })  : _selectOptionService = SelectOptionService(),
-        _fieldListener = SingleFieldListener(fieldId: cellData.field.id),
-        _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
-        super(SelectOptionEditorState.initial(cellData, options, selectedOptions)) {
+    required this.cellContext,
+  })  : _selectOptionService = SelectOptionService(gridCell: cellContext.gridCell),
+        super(SelectOptionEditorState.initial(cellContext)) {
     on<SelectOptionEditorEvent>(
     on<SelectOptionEditorEvent>(
       (event, emit) async {
       (event, emit) async {
         await event.map(
         await event.map(
           initial: (_Initial value) async {
           initial: (_Initial value) async {
             _startListening();
             _startListening();
           },
           },
-          didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
-            emit(state.copyWith(field: value.field));
-            _loadOptions();
-          },
           didReceiveOptions: (_DidReceiveOptions value) {
           didReceiveOptions: (_DidReceiveOptions value) {
             emit(state.copyWith(
             emit(state.copyWith(
               options: value.options,
               options: value.options,
@@ -51,7 +39,7 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
             _updateOption(value.option);
             _updateOption(value.option);
           },
           },
           selectOption: (_SelectOption value) {
           selectOption: (_SelectOption value) {
-            _makeOptionAsSelected(value.optionId);
+            _onSelectOption(value.optionId);
           },
           },
         );
         );
       },
       },
@@ -60,27 +48,21 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
 
 
   @override
   @override
   Future<void> close() async {
   Future<void> close() async {
-    _delayOperation?.cancel();
-    await _fieldListener.stop();
-    await _cellListener.stop();
+    if (_onCellChangedFn != null) {
+      cellContext.removeListener(_onCellChangedFn!);
+      _onCellChangedFn = null;
+    }
+    cellContext.dispose();
     return super.close();
     return super.close();
   }
   }
 
 
   void _createOption(String name) async {
   void _createOption(String name) async {
-    final result = await _selectOptionService.create(
-      gridId: state.gridId,
-      fieldId: state.field.id,
-      rowId: state.rowId,
-      name: name,
-    );
-    result.fold((l) => _loadOptions(), (err) => Log.error(err));
+    final result = await _selectOptionService.create(name: name);
+    result.fold((l) => {}, (err) => Log.error(err));
   }
   }
 
 
   void _deleteOption(SelectOption option) async {
   void _deleteOption(SelectOption option) async {
     final result = await _selectOptionService.delete(
     final result = await _selectOptionService.delete(
-      gridId: state.gridId,
-      fieldId: state.field.id,
-      rowId: state.rowId,
       option: option,
       option: option,
     );
     );
 
 
@@ -89,72 +71,38 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
 
 
   void _updateOption(SelectOption option) async {
   void _updateOption(SelectOption option) async {
     final result = await _selectOptionService.update(
     final result = await _selectOptionService.update(
-      gridId: state.gridId,
-      fieldId: state.field.id,
-      rowId: state.rowId,
       option: option,
       option: option,
     );
     );
 
 
     result.fold((l) => null, (err) => Log.error(err));
     result.fold((l) => null, (err) => Log.error(err));
   }
   }
 
 
-  void _makeOptionAsSelected(String optionId) {
-    _selectOptionService.select(
-      gridId: state.gridId,
-      fieldId: state.field.id,
-      rowId: state.rowId,
-      optionId: optionId,
-    );
+  void _onSelectOption(String optionId) {
+    final hasSelected = state.selectedOptions.firstWhereOrNull((option) => option.id == optionId);
+    if (hasSelected != null) {
+      _selectOptionService.unSelect(optionId: optionId);
+    } else {
+      _selectOptionService.select(optionId: optionId);
+    }
   }
   }
 
 
-  void _loadOptions() async {
-    _delayOperation?.cancel();
-    _delayOperation = Timer(
-      const Duration(milliseconds: 1),
-      () async {
-        final result = await _selectOptionService.getOpitonContext(
-          gridId: state.gridId,
-          fieldId: state.field.id,
-          rowId: state.rowId,
-        );
-        if (isClosed) {
-          return;
-        }
-
-        result.fold(
-          (selectOptionContext) => add(SelectOptionEditorEvent.didReceiveOptions(
+  void _startListening() {
+    _onCellChangedFn = cellContext.startListening(
+      onCellChanged: ((selectOptionContext) {
+        if (!isClosed) {
+          add(SelectOptionEditorEvent.didReceiveOptions(
             selectOptionContext.options,
             selectOptionContext.options,
             selectOptionContext.selectOptions,
             selectOptionContext.selectOptions,
-          )),
-          (err) => Log.error(err),
-        );
-      },
+          ));
+        }
+      }),
     );
     );
   }
   }
-
-  void _startListening() {
-    _cellListener.updateCellNotifier?.addPublishListener((result) {
-      result.fold(
-        (notificationData) => _loadOptions(),
-        (err) => Log.error(err),
-      );
-    });
-    _cellListener.start();
-
-    _fieldListener.updateFieldNotifier?.addPublishListener((result) {
-      result.fold(
-        (field) => add(SelectOptionEditorEvent.didReceiveFieldUpdate(field)),
-        (err) => Log.error(err),
-      );
-    }, listenWhen: () => !isClosed);
-    _fieldListener.start();
-  }
 }
 }
 
 
 @freezed
 @freezed
 class SelectOptionEditorEvent with _$SelectOptionEditorEvent {
 class SelectOptionEditorEvent with _$SelectOptionEditorEvent {
   const factory SelectOptionEditorEvent.initial() = _Initial;
   const factory SelectOptionEditorEvent.initial() = _Initial;
-  const factory SelectOptionEditorEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
   const factory SelectOptionEditorEvent.didReceiveOptions(
   const factory SelectOptionEditorEvent.didReceiveOptions(
       List<SelectOption> options, List<SelectOption> selectedOptions) = _DidReceiveOptions;
       List<SelectOption> options, List<SelectOption> selectedOptions) = _DidReceiveOptions;
   const factory SelectOptionEditorEvent.newOption(String optionName) = _NewOption;
   const factory SelectOptionEditorEvent.newOption(String optionName) = _NewOption;
@@ -166,24 +114,15 @@ class SelectOptionEditorEvent with _$SelectOptionEditorEvent {
 @freezed
 @freezed
 class SelectOptionEditorState with _$SelectOptionEditorState {
 class SelectOptionEditorState with _$SelectOptionEditorState {
   const factory SelectOptionEditorState({
   const factory SelectOptionEditorState({
-    required String gridId,
-    required Field field,
-    required String rowId,
     required List<SelectOption> options,
     required List<SelectOption> options,
     required List<SelectOption> selectedOptions,
     required List<SelectOption> selectedOptions,
   }) = _SelectOptionEditorState;
   }) = _SelectOptionEditorState;
 
 
-  factory SelectOptionEditorState.initial(
-    GridCell cellData,
-    List<SelectOption> options,
-    List<SelectOption> selectedOptions,
-  ) {
+  factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) {
+    final data = context.getCellData();
     return SelectOptionEditorState(
     return SelectOptionEditorState(
-      gridId: cellData.gridId,
-      field: cellData.field,
-      rowId: cellData.rowId,
-      options: options,
-      selectedOptions: selectedOptions,
+      options: data?.options ?? [],
+      selectedOptions: data?.selectOptions ?? [],
     );
     );
   }
   }
 }
 }

+ 19 - 54
frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart

@@ -1,23 +1,17 @@
-import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
-import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell;
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell;
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
 import 'dart:async';
-import 'cell_listener.dart';
 import 'cell_service.dart';
 import 'cell_service.dart';
 
 
 part 'text_cell_bloc.freezed.dart';
 part 'text_cell_bloc.freezed.dart';
 
 
 class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
 class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
-  final CellService _service;
-  final CellListener _cellListener;
-
+  final GridDefaultCellContext cellContext;
+  void Function()? _onCellChangedFn;
   TextCellBloc({
   TextCellBloc({
-    required GridCell cellData,
-  })  : _service = CellService(),
-        _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
-        super(TextCellState.initial(cellData)) {
+    required this.cellContext,
+  }) : super(TextCellState.initial(cellContext)) {
     on<TextCellEvent>(
     on<TextCellEvent>(
       (event, emit) async {
       (event, emit) async {
         await event.map(
         await event.map(
@@ -25,18 +19,14 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
             _startListening();
             _startListening();
           },
           },
           updateText: (_UpdateText value) {
           updateText: (_UpdateText value) {
-            updateCellContent(value.text);
+            cellContext.saveCellData(value.text);
             emit(state.copyWith(content: value.text));
             emit(state.copyWith(content: value.text));
           },
           },
           didReceiveCellData: (_DidReceiveCellData value) {
           didReceiveCellData: (_DidReceiveCellData value) {
-            emit(state.copyWith(
-              cellData: value.cellData,
-              content: value.cellData.cell?.content ?? "",
-            ));
+            emit(state.copyWith(content: value.cellData.cell?.content ?? ""));
           },
           },
           didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
           didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
             emit(state.copyWith(
             emit(state.copyWith(
-              cellData: state.cellData.copyWith(cell: value.cell),
               content: value.cell.content,
               content: value.cell.content,
             ));
             ));
           },
           },
@@ -47,44 +37,21 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
 
 
   @override
   @override
   Future<void> close() async {
   Future<void> close() async {
-    await _cellListener.stop();
+    if (_onCellChangedFn != null) {
+      cellContext.removeListener(_onCellChangedFn!);
+      _onCellChangedFn = null;
+    }
+    cellContext.dispose();
     return super.close();
     return super.close();
   }
   }
 
 
-  void updateCellContent(String content) {
-    final fieldId = state.cellData.field.id;
-    final gridId = state.cellData.gridId;
-    final rowId = state.cellData.rowId;
-    _service.updateCell(
-      data: content,
-      fieldId: fieldId,
-      gridId: gridId,
-      rowId: rowId,
-    );
-  }
-
   void _startListening() {
   void _startListening() {
-    _cellListener.updateCellNotifier?.addPublishListener((result) {
-      result.fold(
-        (notificationData) async => await _loadCellData(),
-        (err) => Log.error(err),
-      );
-    });
-    _cellListener.start();
-  }
-
-  Future<void> _loadCellData() async {
-    final result = await _service.getCell(
-      gridId: state.cellData.gridId,
-      fieldId: state.cellData.field.id,
-      rowId: state.cellData.rowId,
-    );
-    if (isClosed) {
-      return;
-    }
-    result.fold(
-      (cell) => add(TextCellEvent.didReceiveCellUpdate(cell)),
-      (err) => Log.error(err),
+    _onCellChangedFn = cellContext.startListening(
+      onCellChanged: ((cell) {
+        if (!isClosed) {
+          add(TextCellEvent.didReceiveCellUpdate(cell));
+        }
+      }),
     );
     );
   }
   }
 }
 }
@@ -101,11 +68,9 @@ class TextCellEvent with _$TextCellEvent {
 class TextCellState with _$TextCellState {
 class TextCellState with _$TextCellState {
   const factory TextCellState({
   const factory TextCellState({
     required String content,
     required String content,
-    required GridCell cellData,
   }) = _TextCellState;
   }) = _TextCellState;
 
 
-  factory TextCellState.initial(GridCell cellData) => TextCellState(
-        content: cellData.cell?.content ?? "",
-        cellData: cellData,
+  factory TextCellState.initial(GridDefaultCellContext context) => TextCellState(
+        content: context.getCellData()?.content ?? "",
       );
       );
 }
 }

+ 5 - 3
frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart

@@ -45,13 +45,15 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
   }
   }
 
 
   void _startListening() {
   void _startListening() {
-    _fieldListener.updateFieldNotifier?.addPublishListener((result) {
+    _fieldListener.start(onFieldChanged: (result) {
+      if (isClosed) {
+        return;
+      }
       result.fold(
       result.fold(
         (field) => add(FieldCellEvent.didReceiveFieldUpdate(field)),
         (field) => add(FieldCellEvent.didReceiveFieldUpdate(field)),
         (err) => Log.error(err),
         (err) => Log.error(err),
       );
       );
-    }, listenWhen: () => !isClosed);
-    _fieldListener.start();
+    });
   }
   }
 }
 }
 
 

+ 7 - 6
frontend/app_flowy/lib/workspace/application/grid/field/field_listener.dart

@@ -11,12 +11,13 @@ typedef UpdateFieldNotifiedValue = Either<Field, FlowyError>;
 
 
 class SingleFieldListener {
 class SingleFieldListener {
   final String fieldId;
   final String fieldId;
-  PublishNotifier<UpdateFieldNotifiedValue>? updateFieldNotifier = PublishNotifier();
+  PublishNotifier<UpdateFieldNotifiedValue>? _updateFieldNotifier = PublishNotifier();
   GridNotificationListener? _listener;
   GridNotificationListener? _listener;
 
 
   SingleFieldListener({required this.fieldId});
   SingleFieldListener({required this.fieldId});
 
 
-  void start() {
+  void start({required void Function(UpdateFieldNotifiedValue) onFieldChanged}) {
+    _updateFieldNotifier?.addPublishListener(onFieldChanged);
     _listener = GridNotificationListener(
     _listener = GridNotificationListener(
       objectId: fieldId,
       objectId: fieldId,
       handler: _handler,
       handler: _handler,
@@ -30,8 +31,8 @@ class SingleFieldListener {
     switch (ty) {
     switch (ty) {
       case GridNotification.DidUpdateField:
       case GridNotification.DidUpdateField:
         result.fold(
         result.fold(
-          (payload) => updateFieldNotifier?.value = left(Field.fromBuffer(payload)),
-          (error) => updateFieldNotifier?.value = right(error),
+          (payload) => _updateFieldNotifier?.value = left(Field.fromBuffer(payload)),
+          (error) => _updateFieldNotifier?.value = right(error),
         );
         );
         break;
         break;
       default:
       default:
@@ -41,7 +42,7 @@ class SingleFieldListener {
 
 
   Future<void> stop() async {
   Future<void> stop() async {
     await _listener?.stop();
     await _listener?.stop();
-    updateFieldNotifier?.dispose();
-    updateFieldNotifier = null;
+    _updateFieldNotifier?.dispose();
+    _updateFieldNotifier = null;
   }
   }
 }
 }

+ 2 - 1
frontend/app_flowy/lib/workspace/application/grid/field/grid_listenr.dart

@@ -15,7 +15,8 @@ class GridFieldsListener {
   GridNotificationListener? _listener;
   GridNotificationListener? _listener;
   GridFieldsListener({required this.gridId});
   GridFieldsListener({required this.gridId});
 
 
-  void start() {
+  void start({required void Function(UpdateFieldNotifiedValue) onFieldsChanged}) {
+    updateFieldsNotifier?.addPublishListener(onFieldsChanged);
     _listener = GridNotificationListener(
     _listener = GridNotificationListener(
       objectId: gridId,
       objectId: gridId,
       handler: _handler,
       handler: _handler,

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

@@ -5,6 +5,7 @@ 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:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
+import 'cell/cell_service.dart';
 import 'grid_service.dart';
 import 'grid_service.dart';
 import 'row/row_service.dart';
 import 'row/row_service.dart';
 
 
@@ -14,6 +15,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
   final GridService _gridService;
   final GridService _gridService;
   final GridFieldCache fieldCache;
   final GridFieldCache fieldCache;
   late final GridRowCache rowCache;
   late final GridRowCache rowCache;
+  late final GridCellCache cellCache;
 
 
   GridBloc({required View view})
   GridBloc({required View view})
       : _gridService = GridService(gridId: view.id),
       : _gridService = GridService(gridId: view.id),
@@ -21,7 +23,12 @@ class GridBloc extends Bloc<GridEvent, GridState> {
         super(GridState.initial(view.id)) {
         super(GridState.initial(view.id)) {
     rowCache = GridRowCache(
     rowCache = GridRowCache(
       gridId: view.id,
       gridId: view.id,
-      dataDelegate: GridRowDataDelegateAdaptor(fieldCache),
+      fieldDelegate: GridRowCacheDelegateImpl(fieldCache),
+    );
+
+    cellCache = GridCellCache(
+      gridId: view.id,
+      fieldDelegate: GridCellCacheDelegateImpl(fieldCache),
     );
     );
 
 
     on<GridEvent>(
     on<GridEvent>(
@@ -48,8 +55,9 @@ class GridBloc extends Bloc<GridEvent, GridState> {
   @override
   @override
   Future<void> close() async {
   Future<void> close() async {
     await _gridService.closeGrid();
     await _gridService.closeGrid();
-    await fieldCache.dispose();
+    await cellCache.dispose();
     await rowCache.dispose();
     await rowCache.dispose();
+    await fieldCache.dispose();
     return super.close();
     return super.close();
   }
   }
 
 
@@ -81,7 +89,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
       () => result.fold(
       () => result.fold(
         (fields) {
         (fields) {
           fieldCache.fields = fields.items;
           fieldCache.fields = fields.items;
-          rowCache.updateWithBlock(grid.blockOrders);
+          rowCache.resetRows(grid.blockOrders);
 
 
           emit(state.copyWith(
           emit(state.copyWith(
             grid: Some(grid),
             grid: Some(grid),

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

@@ -8,6 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 
 
+import 'cell/cell_service.dart';
 import 'row/row_service.dart';
 import 'row/row_service.dart';
 
 
 class GridService {
 class GridService {
@@ -53,23 +54,29 @@ class FieldsNotifier extends ChangeNotifier {
   List<Field> get fields => _fields;
   List<Field> get fields => _fields;
 }
 }
 
 
+typedef ChangesetListener = void Function(GridFieldChangeset);
+
 class GridFieldCache {
 class GridFieldCache {
   final String gridId;
   final String gridId;
   late final GridFieldsListener _fieldListener;
   late final GridFieldsListener _fieldListener;
   final FieldsNotifier _fieldNotifier = FieldsNotifier();
   final FieldsNotifier _fieldNotifier = FieldsNotifier();
+  final List<ChangesetListener> _changesetListener = [];
+
   GridFieldCache({required this.gridId}) {
   GridFieldCache({required this.gridId}) {
     _fieldListener = GridFieldsListener(gridId: gridId);
     _fieldListener = GridFieldsListener(gridId: gridId);
-    _fieldListener.updateFieldsNotifier?.addPublishListener((result) {
+    _fieldListener.start(onFieldsChanged: (result) {
       result.fold(
       result.fold(
         (changeset) {
         (changeset) {
           _deleteFields(changeset.deletedFields);
           _deleteFields(changeset.deletedFields);
           _insertFields(changeset.insertedFields);
           _insertFields(changeset.insertedFields);
           _updateFields(changeset.updatedFields);
           _updateFields(changeset.updatedFields);
+          for (final listener in _changesetListener) {
+            listener(changeset);
+          }
         },
         },
         (err) => Log.error(err),
         (err) => Log.error(err),
       );
       );
     });
     });
-    _fieldListener.start();
   }
   }
 
 
   Future<void> dispose() async {
   Future<void> dispose() async {
@@ -77,8 +84,6 @@ class GridFieldCache {
     _fieldNotifier.dispose();
     _fieldNotifier.dispose();
   }
   }
 
 
-  void applyChangeset(GridFieldChangeset changeset) {}
-
   UnmodifiableListView<Field> get unmodifiableFields => UnmodifiableListView(_fieldNotifier.fields);
   UnmodifiableListView<Field> get unmodifiableFields => UnmodifiableListView(_fieldNotifier.fields);
 
 
   List<Field> get clonedFields => [..._fieldNotifier.fields];
   List<Field> get clonedFields => [..._fieldNotifier.fields];
@@ -111,6 +116,17 @@ class GridFieldCache {
     _fieldNotifier.removeListener(f);
     _fieldNotifier.removeListener(f);
   }
   }
 
 
+  void addChangesetListener(ChangesetListener listener) {
+    _changesetListener.add(listener);
+  }
+
+  void removeChangesetListener(ChangesetListener listener) {
+    final index = _changesetListener.indexWhere((element) => element == listener);
+    if (index != -1) {
+      _changesetListener.removeAt(index);
+    }
+  }
+
   void _deleteFields(List<FieldOrder> deletedFields) {
   void _deleteFields(List<FieldOrder> deletedFields) {
     if (deletedFields.isEmpty) {
     if (deletedFields.isEmpty) {
       return;
       return;
@@ -155,43 +171,43 @@ class GridFieldCache {
   }
   }
 }
 }
 
 
-class GridRowDataDelegateAdaptor extends GridRowDataDelegate {
+class GridRowCacheDelegateImpl extends GridRowFieldDelegate {
   final GridFieldCache _cache;
   final GridFieldCache _cache;
+  GridRowCacheDelegateImpl(GridFieldCache cache) : _cache = cache;
 
 
-  GridRowDataDelegateAdaptor(GridFieldCache cache) : _cache = cache;
   @override
   @override
   UnmodifiableListView<Field> get fields => _cache.unmodifiableFields;
   UnmodifiableListView<Field> get fields => _cache.unmodifiableFields;
 
 
-  @override
-  GridRow buildGridRow(RowOrder rowOrder) {
-    return GridRow(
-      gridId: _cache.gridId,
-      fields: _cache.unmodifiableFields,
-      rowId: rowOrder.rowId,
-      height: rowOrder.height.toDouble(),
-    );
-  }
-
   @override
   @override
   void onFieldChanged(FieldDidUpdateCallback callback) {
   void onFieldChanged(FieldDidUpdateCallback callback) {
     _cache.addListener(listener: () {
     _cache.addListener(listener: () {
       callback();
       callback();
     });
     });
   }
   }
+}
+
+class GridCellCacheDelegateImpl extends GridCellFieldDelegate {
+  final GridFieldCache _cache;
+  ChangesetListener? _changesetFn;
+  GridCellCacheDelegateImpl(GridFieldCache cache) : _cache = cache;
 
 
   @override
   @override
-  CellDataMap buildCellDataMap(Row rowData) {
-    var map = CellDataMap.new();
-    for (final field in fields) {
-      if (field.visibility) {
-        map[field.id] = GridCell(
-          rowId: rowData.id,
-          gridId: _cache.gridId,
-          cell: rowData.cellByFieldId[field.id],
-          field: field,
-        );
+  void onFieldChanged(void Function(String) callback) {
+    changesetFn(GridFieldChangeset changeset) {
+      for (final updatedField in changeset.updatedFields) {
+        callback(updatedField.id);
       }
       }
     }
     }
-    return map;
+
+    _cache.addChangesetListener(changesetFn);
+    _changesetFn = changesetFn;
+  }
+
+  @override
+  void dispose() {
+    if (_changesetFn != null) {
+      _cache.removeChangesetListener(_changesetFn!);
+      _changesetFn = null;
+    }
   }
   }
 }
 }

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

@@ -1,9 +1,9 @@
 import 'dart:collection';
 import 'dart:collection';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
 import 'dart:async';
 import 'row_service.dart';
 import 'row_service.dart';
-import 'package:dartz/dartz.dart';
 
 
 part 'row_bloc.freezed.dart';
 part 'row_bloc.freezed.dart';
 
 
@@ -17,19 +17,18 @@ class RowBloc extends Bloc<RowEvent, RowState> {
     required GridRowCache rowCache,
     required GridRowCache rowCache,
   })  : _rowService = RowService(gridId: rowData.gridId, rowId: rowData.rowId),
   })  : _rowService = RowService(gridId: rowData.gridId, rowId: rowData.rowId),
         _rowCache = rowCache,
         _rowCache = rowCache,
-        super(RowState.initial(rowData)) {
+        super(RowState.initial(rowData, rowCache.loadGridCells(rowData.rowId))) {
     on<RowEvent>(
     on<RowEvent>(
       (event, emit) async {
       (event, emit) async {
         await event.map(
         await event.map(
           initial: (_InitialRow value) async {
           initial: (_InitialRow value) async {
             await _startListening();
             await _startListening();
-            await _loadRow(emit);
           },
           },
           createRow: (_CreateRow value) {
           createRow: (_CreateRow value) {
             _rowService.createRow();
             _rowService.createRow();
           },
           },
           didReceiveCellDatas: (_DidReceiveCellDatas value) async {
           didReceiveCellDatas: (_DidReceiveCellDatas value) async {
-            emit(state.copyWith(cellDataMap: Some(value.cellData)));
+            emit(state.copyWith(cellDataMap: value.cellData));
           },
           },
         );
         );
       },
       },
@@ -41,6 +40,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
     if (_rowListenFn != null) {
     if (_rowListenFn != null) {
       _rowCache.removeRowListener(_rowListenFn!);
       _rowCache.removeRowListener(_rowListenFn!);
     }
     }
+
     return super.close();
     return super.close();
   }
   }
 
 
@@ -51,33 +51,24 @@ class RowBloc extends Bloc<RowEvent, RowState> {
       listenWhen: () => !isClosed,
       listenWhen: () => !isClosed,
     );
     );
   }
   }
-
-  Future<void> _loadRow(Emitter<RowState> emit) async {
-    final data = _rowCache.loadCellData(state.rowData.rowId);
-    data.foldRight(null, (cellDatas, _) {
-      if (!isClosed) {
-        add(RowEvent.didReceiveCellDatas(cellDatas));
-      }
-    });
-  }
 }
 }
 
 
 @freezed
 @freezed
 class RowEvent with _$RowEvent {
 class RowEvent with _$RowEvent {
   const factory RowEvent.initial() = _InitialRow;
   const factory RowEvent.initial() = _InitialRow;
   const factory RowEvent.createRow() = _CreateRow;
   const factory RowEvent.createRow() = _CreateRow;
-  const factory RowEvent.didReceiveCellDatas(CellDataMap cellData) = _DidReceiveCellDatas;
+  const factory RowEvent.didReceiveCellDatas(GridCellMap cellData) = _DidReceiveCellDatas;
 }
 }
 
 
 @freezed
 @freezed
 class RowState with _$RowState {
 class RowState with _$RowState {
   const factory RowState({
   const factory RowState({
     required GridRow rowData,
     required GridRow rowData,
-    required Option<CellDataMap> cellDataMap,
+    required GridCellMap cellDataMap,
   }) = _RowState;
   }) = _RowState;
 
 
-  factory RowState.initial(GridRow rowData) => RowState(
+  factory RowState.initial(GridRow rowData, GridCellMap cellDataMap) => RowState(
         rowData: rowData,
         rowData: rowData,
-        cellDataMap: none(),
+        cellDataMap: cellDataMap,
       );
       );
 }
 }

+ 9 - 10
frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart

@@ -1,3 +1,4 @@
+import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
 import 'dart:async';
@@ -23,7 +24,7 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
             _loadCellData();
             _loadCellData();
           },
           },
           didReceiveCellDatas: (_DidReceiveCellDatas value) {
           didReceiveCellDatas: (_DidReceiveCellDatas value) {
-            emit(state.copyWith(cellDatas: value.cellDatas));
+            emit(state.copyWith(gridCells: value.gridCells));
           },
           },
         );
         );
       },
       },
@@ -47,28 +48,26 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
   }
   }
 
 
   Future<void> _loadCellData() async {
   Future<void> _loadCellData() async {
-    final data = _rowCache.loadCellData(rowData.rowId);
-    data.foldRight(null, (cellDataMap, _) {
-      if (!isClosed) {
-        add(RowDetailEvent.didReceiveCellDatas(cellDataMap.values.toList()));
-      }
-    });
+    final cellDataMap = _rowCache.loadGridCells(rowData.rowId);
+    if (!isClosed) {
+      add(RowDetailEvent.didReceiveCellDatas(cellDataMap.values.toList()));
+    }
   }
   }
 }
 }
 
 
 @freezed
 @freezed
 class RowDetailEvent with _$RowDetailEvent {
 class RowDetailEvent with _$RowDetailEvent {
   const factory RowDetailEvent.initial() = _Initial;
   const factory RowDetailEvent.initial() = _Initial;
-  const factory RowDetailEvent.didReceiveCellDatas(List<GridCell> cellDatas) = _DidReceiveCellDatas;
+  const factory RowDetailEvent.didReceiveCellDatas(List<GridCell> gridCells) = _DidReceiveCellDatas;
 }
 }
 
 
 @freezed
 @freezed
 class RowDetailState with _$RowDetailState {
 class RowDetailState with _$RowDetailState {
   const factory RowDetailState({
   const factory RowDetailState({
-    required List<GridCell> cellDatas,
+    required List<GridCell> gridCells,
   }) = _RowDetailState;
   }) = _RowDetailState;
 
 
   factory RowDetailState.initial() => RowDetailState(
   factory RowDetailState.initial() => RowDetailState(
-        cellDatas: List.empty(),
+        gridCells: List.empty(),
       );
       );
 }
 }

+ 66 - 54
frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart

@@ -1,5 +1,6 @@
 import 'dart:collection';
 import 'dart:collection';
 
 
+import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
 import 'package:dartz/dartz.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/log.dart';
@@ -13,38 +14,43 @@ part 'row_service.freezed.dart';
 
 
 typedef RowUpdateCallback = void Function();
 typedef RowUpdateCallback = void Function();
 typedef FieldDidUpdateCallback = void Function();
 typedef FieldDidUpdateCallback = void Function();
-typedef CellDataMap = LinkedHashMap<String, GridCell>;
 
 
-abstract class GridRowDataDelegate {
+abstract class GridRowFieldDelegate {
   UnmodifiableListView<Field> get fields;
   UnmodifiableListView<Field> get fields;
-  GridRow buildGridRow(RowOrder rowOrder);
-  CellDataMap buildCellDataMap(Row rowData);
   void onFieldChanged(FieldDidUpdateCallback callback);
   void onFieldChanged(FieldDidUpdateCallback callback);
 }
 }
 
 
 class GridRowCache {
 class GridRowCache {
   final String gridId;
   final String gridId;
-  final RowsNotifier _rowNotifier;
+  final RowsNotifier _rowsNotifier;
   final GridRowListener _rowsListener;
   final GridRowListener _rowsListener;
-  final GridRowDataDelegate _dataDelegate;
-
-  List<GridRow> get clonedRows => _rowNotifier.clonedRows;
-
-  GridRowCache({required this.gridId, required GridRowDataDelegate dataDelegate})
-      : _rowNotifier = RowsNotifier(rowBuilder: dataDelegate.buildGridRow),
+  final GridRowFieldDelegate _fieldDelegate;
+  List<GridRow> get clonedRows => _rowsNotifier.clonedRows;
+
+  GridRowCache({required this.gridId, required GridRowFieldDelegate fieldDelegate})
+      : _rowsNotifier = RowsNotifier(
+          rowBuilder: (rowOrder) {
+            return GridRow(
+              gridId: gridId,
+              fields: fieldDelegate.fields,
+              rowId: rowOrder.rowId,
+              height: rowOrder.height.toDouble(),
+            );
+          },
+        ),
         _rowsListener = GridRowListener(gridId: gridId),
         _rowsListener = GridRowListener(gridId: gridId),
-        _dataDelegate = dataDelegate {
+        _fieldDelegate = fieldDelegate {
     //
     //
-    dataDelegate.onFieldChanged(() => _rowNotifier.fieldDidChange());
+    fieldDelegate.onFieldChanged(() => _rowsNotifier.fieldDidChange());
 
 
     // listen on the row update
     // listen on the row update
     _rowsListener.rowsUpdateNotifier.addPublishListener((result) {
     _rowsListener.rowsUpdateNotifier.addPublishListener((result) {
       result.fold(
       result.fold(
         (changesets) {
         (changesets) {
           for (final changeset in changesets) {
           for (final changeset in changesets) {
-            _rowNotifier.deleteRows(changeset.deletedRows);
-            _rowNotifier.insertRows(changeset.insertedRows);
-            _rowNotifier.updateRows(changeset.updatedRows);
+            _rowsNotifier.deleteRows(changeset.deletedRows);
+            _rowsNotifier.insertRows(changeset.insertedRows);
+            _rowsNotifier.updateRows(changeset.updatedRows);
           }
           }
         },
         },
         (err) => Log.error(err),
         (err) => Log.error(err),
@@ -55,14 +61,14 @@ class GridRowCache {
 
 
   Future<void> dispose() async {
   Future<void> dispose() async {
     await _rowsListener.stop();
     await _rowsListener.stop();
-    _rowNotifier.dispose();
+    _rowsNotifier.dispose();
   }
   }
 
 
   void addListener({
   void addListener({
     void Function(List<GridRow>, GridRowChangeReason)? onChanged,
     void Function(List<GridRow>, GridRowChangeReason)? onChanged,
     bool Function()? listenWhen,
     bool Function()? listenWhen,
   }) {
   }) {
-    _rowNotifier.addListener(() {
+    _rowsNotifier.addListener(() {
       if (onChanged == null) {
       if (onChanged == null) {
         return;
         return;
       }
       }
@@ -71,16 +77,16 @@ class GridRowCache {
         return;
         return;
       }
       }
 
 
-      onChanged(clonedRows, _rowNotifier._changeReason);
+      onChanged(clonedRows, _rowsNotifier._changeReason);
     });
     });
   }
   }
 
 
   RowUpdateCallback addRowListener({
   RowUpdateCallback addRowListener({
     required String rowId,
     required String rowId,
-    void Function(CellDataMap)? onUpdated,
+    void Function(GridCellMap)? onUpdated,
     bool Function()? listenWhen,
     bool Function()? listenWhen,
   }) {
   }) {
-    listenrHandler() {
+    listenrHandler() async {
       if (onUpdated == null) {
       if (onUpdated == null) {
         return;
         return;
       }
       }
@@ -90,14 +96,14 @@ class GridRowCache {
       }
       }
 
 
       notify() {
       notify() {
-        final row = _rowNotifier.rowDataWithId(rowId);
+        final row = _rowsNotifier.rowDataWithId(rowId);
         if (row != null) {
         if (row != null) {
-          final cellDataMap = _dataDelegate.buildCellDataMap(row);
+          final GridCellMap cellDataMap = _makeGridCells(rowId, row);
           onUpdated(cellDataMap);
           onUpdated(cellDataMap);
         }
         }
       }
       }
 
 
-      _rowNotifier._changeReason.whenOrNull(
+      _rowsNotifier._changeReason.whenOrNull(
         update: (indexs) {
         update: (indexs) {
           if (indexs[rowId] != null) {
           if (indexs[rowId] != null) {
             notify();
             notify();
@@ -107,36 +113,52 @@ class GridRowCache {
       );
       );
     }
     }
 
 
-    _rowNotifier.addListener(listenrHandler);
+    _rowsNotifier.addListener(listenrHandler);
     return listenrHandler;
     return listenrHandler;
   }
   }
 
 
   void removeRowListener(VoidCallback callback) {
   void removeRowListener(VoidCallback callback) {
-    _rowNotifier.removeListener(callback);
+    _rowsNotifier.removeListener(callback);
   }
   }
 
 
-  Option<CellDataMap> loadCellData(String rowId) {
-    final Row? data = _rowNotifier.rowDataWithId(rowId);
-    if (data != null) {
-      return Some(_dataDelegate.buildCellDataMap(data));
+  GridCellMap loadGridCells(String rowId) {
+    final Row? data = _rowsNotifier.rowDataWithId(rowId);
+    if (data == null) {
+      _loadRow(rowId);
     }
     }
+    return _makeGridCells(rowId, data);
+  }
+
+  void resetRows(List<GridBlockOrder> blocks) {
+    final rowOrders = blocks.expand((block) => block.rowOrders).toList();
+    _rowsNotifier.reset(rowOrders);
+  }
 
 
+  Future<void> _loadRow(String rowId) async {
     final payload = RowIdentifierPayload.create()
     final payload = RowIdentifierPayload.create()
       ..gridId = gridId
       ..gridId = gridId
       ..rowId = rowId;
       ..rowId = rowId;
 
 
-    GridEventGetRow(payload).send().then((result) {
-      result.fold(
-        (rowData) => _rowNotifier.rowData = rowData,
-        (err) => Log.error(err),
-      );
-    });
-    return none();
+    final result = await GridEventGetRow(payload).send();
+    result.fold(
+      (rowData) => _rowsNotifier.rowData = rowData,
+      (err) => Log.error(err),
+    );
   }
   }
 
 
-  void updateWithBlock(List<GridBlockOrder> blocks) {
-    final rowOrders = blocks.expand((block) => block.rowOrders).toList();
-    _rowNotifier.reset(rowOrders);
+  GridCellMap _makeGridCells(String rowId, Row? row) {
+    var cellDataMap = GridCellMap.new();
+    for (final field in _fieldDelegate.fields) {
+      if (field.visibility) {
+        cellDataMap[field.id] = GridCell(
+          rowId: rowId,
+          gridId: gridId,
+          cell: row?.cellByFieldId[field.id],
+          field: field,
+        );
+      }
+    }
+    return cellDataMap;
   }
   }
 }
 }
 
 
@@ -194,18 +216,18 @@ class RowsNotifier extends ChangeNotifier {
     _update(newRows, GridRowChangeReason.insert(insertIndexs));
     _update(newRows, GridRowChangeReason.insert(insertIndexs));
   }
   }
 
 
-  void updateRows(List<RowOrder> updatedRows) {
+  void updateRows(List<UpdatedRowOrder> updatedRows) {
     if (updatedRows.isEmpty) {
     if (updatedRows.isEmpty) {
       return;
       return;
     }
     }
 
 
     final UpdatedIndexs updatedIndexs = UpdatedIndexs();
     final UpdatedIndexs updatedIndexs = UpdatedIndexs();
     final List<GridRow> newRows = clonedRows;
     final List<GridRow> newRows = clonedRows;
-    for (final rowOrder in updatedRows) {
+    for (final updatedRow in updatedRows) {
+      final rowOrder = updatedRow.rowOrder;
       final index = newRows.indexWhere((row) => row.rowId == rowOrder.rowId);
       final index = newRows.indexWhere((row) => row.rowId == rowOrder.rowId);
       if (index != -1) {
       if (index != -1) {
-        // Remove the old row data, the data will be filled if the loadRow method gets called.
-        _rowDataMap.remove(rowOrder.rowId);
+        _rowDataMap[rowOrder.rowId] = updatedRow.row;
 
 
         newRows.removeAt(index);
         newRows.removeAt(index);
         newRows.insert(index, rowBuilder(rowOrder));
         newRows.insert(index, rowBuilder(rowOrder));
@@ -323,16 +345,6 @@ class GridRow with _$GridRow {
   }) = _GridRow;
   }) = _GridRow;
 }
 }
 
 
-@freezed
-class GridCell with _$GridCell {
-  const factory GridCell({
-    required String gridId,
-    required String rowId,
-    required Field field,
-    Cell? cell,
-  }) = _GridCell;
-}
-
 typedef InsertedIndexs = List<InsertedIndex>;
 typedef InsertedIndexs = List<InsertedIndex>;
 typedef DeletedIndexs = List<DeletedIndex>;
 typedef DeletedIndexs = List<DeletedIndex>;
 typedef UpdatedIndexs = LinkedHashMap<String, UpdatedIndex>;
 typedef UpdatedIndexs = LinkedHashMap<String, UpdatedIndex>;

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

@@ -213,7 +213,7 @@ class _GridRowsState extends State<_GridRows> {
           key: _key,
           key: _key,
           initialItemCount: context.read<GridBloc>().state.rows.length,
           initialItemCount: context.read<GridBloc>().state.rows.length,
           itemBuilder: (BuildContext context, int index, Animation<double> animation) {
           itemBuilder: (BuildContext context, int index, Animation<double> animation) {
-            final rowData = context.read<GridBloc>().state.rows[index];
+            final GridRow rowData = context.read<GridBloc>().state.rows[index];
             return _renderRow(context, rowData, animation);
             return _renderRow(context, rowData, animation);
           },
           },
         );
         );
@@ -227,11 +227,13 @@ class _GridRowsState extends State<_GridRows> {
     Animation<double> animation,
     Animation<double> animation,
   ) {
   ) {
     final rowCache = context.read<GridBloc>().rowCache;
     final rowCache = context.read<GridBloc>().rowCache;
+    final cellCache = context.read<GridBloc>().cellCache;
     return SizeTransition(
     return SizeTransition(
       sizeFactor: animation,
       sizeFactor: animation,
       child: GridRowWidget(
       child: GridRowWidget(
         rowData: rowData,
         rowData: rowData,
         rowCache: rowCache,
         rowCache: rowCache,
+        cellCache: cellCache,
         key: ValueKey(rowData.rowId),
         key: ValueKey(rowData.rowId),
       ),
       ),
     );
     );

+ 42 - 10
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart

@@ -1,4 +1,5 @@
-import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
 import 'package:flutter/widgets.dart';
 import 'package:flutter/widgets.dart';
@@ -8,21 +9,52 @@ import 'number_cell.dart';
 import 'selection_cell/selection_cell.dart';
 import 'selection_cell/selection_cell.dart';
 import 'text_cell.dart';
 import 'text_cell.dart';
 
 
-GridCellWidget buildGridCell(GridCell cellData, {GridCellStyle? style}) {
-  final key = ValueKey(cellData.field.id + cellData.rowId);
-  switch (cellData.field.fieldType) {
+GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) {
+  final key = ValueKey(gridCell.rowId + gridCell.field.id);
+
+  final cellContext = makeCellContext(gridCell, cellCache);
+
+  switch (gridCell.field.fieldType) {
     case FieldType.Checkbox:
     case FieldType.Checkbox:
-      return CheckboxCell(cellData: cellData, key: key);
+      return CheckboxCell(cellContext: cellContext, key: key);
     case FieldType.DateTime:
     case FieldType.DateTime:
-      return DateCell(cellData: cellData, key: key);
+      return DateCell(cellContext: cellContext, key: key);
     case FieldType.MultiSelect:
     case FieldType.MultiSelect:
-      return MultiSelectCell(cellData: cellData, key: key);
+      return MultiSelectCell(cellContext: cellContext as GridSelectOptionCellContext, style: style, key: key);
     case FieldType.Number:
     case FieldType.Number:
-      return NumberCell(cellData: cellData, key: key);
+      return NumberCell(cellContext: cellContext, key: key);
     case FieldType.RichText:
     case FieldType.RichText:
-      return GridTextCell(cellData: cellData, key: key, style: style);
+      return GridTextCell(cellContext: cellContext, style: style, key: key);
+    case FieldType.SingleSelect:
+      return SingleSelectCell(cellContext: cellContext as GridSelectOptionCellContext, style: style, key: key);
+    default:
+      throw UnimplementedError;
+  }
+}
+
+GridCellContext makeCellContext(GridCell gridCell, GridCellCache cellCache) {
+  switch (gridCell.field.fieldType) {
+    case FieldType.Checkbox:
+    case FieldType.DateTime:
+    case FieldType.Number:
+      return GridDefaultCellContext(
+        gridCell: gridCell,
+        cellCache: cellCache,
+        cellDataLoader: DefaultCellDataLoader(gridCell: gridCell, reloadOnCellChanged: true),
+      );
+    case FieldType.RichText:
+      return GridDefaultCellContext(
+        gridCell: gridCell,
+        cellCache: cellCache,
+        cellDataLoader: DefaultCellDataLoader(gridCell: gridCell),
+      );
+    case FieldType.MultiSelect:
     case FieldType.SingleSelect:
     case FieldType.SingleSelect:
-      return SingleSelectCell(cellData: cellData, key: key);
+      return GridSelectOptionCellContext(
+        gridCell: gridCell,
+        cellCache: cellCache,
+        cellDataLoader: SelectOptionCellDataLoader(gridCell: gridCell),
+      );
     default:
     default:
       throw UnimplementedError;
       throw UnimplementedError;
   }
   }

+ 3 - 3
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart

@@ -7,10 +7,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'cell_builder.dart';
 import 'cell_builder.dart';
 
 
 class CheckboxCell extends GridCellWidget {
 class CheckboxCell extends GridCellWidget {
-  final GridCell cellData;
+  final GridCellContext cellContext;
 
 
   CheckboxCell({
   CheckboxCell({
-    required this.cellData,
+    required this.cellContext,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
@@ -23,7 +23,7 @@ class _CheckboxCellState extends State<CheckboxCell> {
 
 
   @override
   @override
   void initState() {
   void initState() {
-    _cellBloc = getIt<CheckboxCellBloc>(param1: widget.cellData)..add(const CheckboxCellEvent.initial());
+    _cellBloc = getIt<CheckboxCellBloc>(param1: widget.cellContext)..add(const CheckboxCellEvent.initial());
     super.initState();
     super.initState();
   }
   }
 
 

+ 3 - 3
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell.dart

@@ -14,10 +14,10 @@ abstract class GridCellDelegate {
 }
 }
 
 
 class DateCell extends GridCellWidget {
 class DateCell extends GridCellWidget {
-  final GridCell cellData;
+  final GridCellContext cellContext;
 
 
   DateCell({
   DateCell({
-    required this.cellData,
+    required this.cellContext,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
@@ -30,7 +30,7 @@ class _DateCellState extends State<DateCell> {
 
 
   @override
   @override
   void initState() {
   void initState() {
-    _cellBloc = getIt<DateCellBloc>(param1: widget.cellData)..add(const DateCellEvent.initial());
+    _cellBloc = getIt<DateCellBloc>(param1: widget.cellContext)..add(const DateCellEvent.initial());
     super.initState();
     super.initState();
   }
   }
 
 

+ 3 - 3
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart

@@ -8,10 +8,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'cell_builder.dart';
 import 'cell_builder.dart';
 
 
 class NumberCell extends GridCellWidget {
 class NumberCell extends GridCellWidget {
-  final GridCell cellData;
+  final GridCellContext cellContext;
 
 
   NumberCell({
   NumberCell({
-    required this.cellData,
+    required this.cellContext,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
@@ -27,7 +27,7 @@ class _NumberCellState extends State<NumberCell> {
 
 
   @override
   @override
   void initState() {
   void initState() {
-    _cellBloc = getIt<NumberCellBloc>(param1: widget.cellData)..add(const NumberCellEvent.initial());
+    _cellBloc = getIt<NumberCellBloc>(param1: widget.cellContext)..add(const NumberCellEvent.initial());
     _controller = TextEditingController(text: _cellBloc.state.content);
     _controller = TextEditingController(text: _cellBloc.state.content);
     _focusNode = FocusNode();
     _focusNode = FocusNode();
     _focusNode.addListener(() {
     _focusNode.addListener(() {

+ 62 - 16
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart

@@ -1,19 +1,39 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
+// ignore: unused_import
+import 'package:flowy_sdk/log.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 
 import 'extension.dart';
 import 'extension.dart';
 import 'selection_editor.dart';
 import 'selection_editor.dart';
 
 
+class SelectOptionCellStyle extends GridCellStyle {
+  String placeholder;
+
+  SelectOptionCellStyle({
+    required this.placeholder,
+  });
+}
+
 class SingleSelectCell extends GridCellWidget {
 class SingleSelectCell extends GridCellWidget {
-  final GridCell cellData;
+  final GridSelectOptionCellContext cellContext;
+  late final SelectOptionCellStyle? cellStyle;
 
 
   SingleSelectCell({
   SingleSelectCell({
-    required this.cellData,
+    required this.cellContext,
+    GridCellStyle? style,
     Key? key,
     Key? key,
-  }) : super(key: key);
+  }) : super(key: key) {
+    if (style != null) {
+      cellStyle = (style as SelectOptionCellStyle);
+    } else {
+      cellStyle = null;
+    }
+  }
 
 
   @override
   @override
   State<SingleSelectCell> createState() => _SingleSelectCellState();
   State<SingleSelectCell> createState() => _SingleSelectCellState();
@@ -24,26 +44,32 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
 
 
   @override
   @override
   void initState() {
   void initState() {
-    _cellBloc = getIt<SelectionCellBloc>(param1: widget.cellData)..add(const SelectionCellEvent.initial());
+    // Log.trace("init widget $hashCode");
+    _cellBloc = getIt<SelectionCellBloc>(param1: widget.cellContext)..add(const SelectionCellEvent.initial());
     super.initState();
     super.initState();
   }
   }
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    // Log.trace("build widget $hashCode");
     return BlocProvider.value(
     return BlocProvider.value(
       value: _cellBloc,
       value: _cellBloc,
       child: BlocBuilder<SelectionCellBloc, SelectionCellState>(
       child: BlocBuilder<SelectionCellBloc, SelectionCellState>(
         builder: (context, state) {
         builder: (context, state) {
-          final children = state.selectedOptions.map((option) => SelectOptionTag(option: option)).toList();
+          List<Widget> children = [];
+          children.addAll(state.selectedOptions.map((option) => SelectOptionTag(option: option)).toList());
+
+          if (children.isEmpty && widget.cellStyle != null) {
+            children.add(FlowyText.medium(widget.cellStyle!.placeholder, fontSize: 14, color: theme.shader3));
+          }
           return SizedBox.expand(
           return SizedBox.expand(
             child: InkWell(
             child: InkWell(
               onTap: () {
               onTap: () {
                 widget.onFocus.value = true;
                 widget.onFocus.value = true;
                 SelectOptionCellEditor.show(
                 SelectOptionCellEditor.show(
                   context,
                   context,
-                  state.cellData,
-                  state.options,
-                  state.selectedOptions,
+                  widget.cellContext.clone(),
                   () => widget.onFocus.value = false,
                   () => widget.onFocus.value = false,
                 );
                 );
               },
               },
@@ -55,8 +81,17 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
     );
     );
   }
   }
 
 
+  @override
+  void didUpdateWidget(covariant SingleSelectCell oldWidget) {
+    if (oldWidget.cellContext != widget.cellContext) {
+      // Log.trace("did update widget $hashCode");
+    }
+    super.didUpdateWidget(oldWidget);
+  }
+
   @override
   @override
   Future<void> dispose() async {
   Future<void> dispose() async {
+    // Log.trace("dispose widget $hashCode");
     _cellBloc.close();
     _cellBloc.close();
     super.dispose();
     super.dispose();
   }
   }
@@ -64,12 +99,20 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
 
 
 //----------------------------------------------------------------
 //----------------------------------------------------------------
 class MultiSelectCell extends GridCellWidget {
 class MultiSelectCell extends GridCellWidget {
-  final GridCell cellData;
+  final GridSelectOptionCellContext cellContext;
+  late final SelectOptionCellStyle? cellStyle;
 
 
   MultiSelectCell({
   MultiSelectCell({
-    required this.cellData,
+    required this.cellContext,
+    GridCellStyle? style,
     Key? key,
     Key? key,
-  }) : super(key: key);
+  }) : super(key: key) {
+    if (style != null) {
+      cellStyle = (style as SelectOptionCellStyle);
+    } else {
+      cellStyle = null;
+    }
+  }
 
 
   @override
   @override
   State<MultiSelectCell> createState() => _MultiSelectCellState();
   State<MultiSelectCell> createState() => _MultiSelectCellState();
@@ -80,7 +123,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
 
 
   @override
   @override
   void initState() {
   void initState() {
-    _cellBloc = getIt<SelectionCellBloc>(param1: widget.cellData)..add(const SelectionCellEvent.initial());
+    _cellBloc = getIt<SelectionCellBloc>(param1: widget.cellContext)..add(const SelectionCellEvent.initial());
     super.initState();
     super.initState();
   }
   }
 
 
@@ -90,16 +133,19 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
       value: _cellBloc,
       value: _cellBloc,
       child: BlocBuilder<SelectionCellBloc, SelectionCellState>(
       child: BlocBuilder<SelectionCellBloc, SelectionCellState>(
         builder: (context, state) {
         builder: (context, state) {
-          final children = state.selectedOptions.map((option) => SelectOptionTag(option: option)).toList();
+          List<Widget> children = state.selectedOptions.map((option) => SelectOptionTag(option: option)).toList();
+
+          if (children.isEmpty && widget.cellStyle != null) {
+            children.add(FlowyText.medium(widget.cellStyle!.placeholder, fontSize: 14));
+          }
+
           return SizedBox.expand(
           return SizedBox.expand(
             child: InkWell(
             child: InkWell(
               onTap: () {
               onTap: () {
                 widget.onFocus.value = true;
                 widget.onFocus.value = true;
                 SelectOptionCellEditor.show(
                 SelectOptionCellEditor.show(
                   context,
                   context,
-                  state.cellData,
-                  state.options,
-                  state.selectedOptions,
+                  widget.cellContext,
                   () => widget.onFocus.value = false,
                   () => widget.onFocus.value = false,
                 );
                 );
               },
               },

+ 6 - 16
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart

@@ -1,6 +1,6 @@
 import 'dart:collection';
 import 'dart:collection';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
 import 'package:app_flowy/workspace/application/grid/cell/selection_editor_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/cell/selection_editor_bloc.dart';
-import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/widget.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/widget.dart';
@@ -25,15 +25,11 @@ import 'text_field.dart';
 const double _editorPannelWidth = 300;
 const double _editorPannelWidth = 300;
 
 
 class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
 class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
-  final GridCell cellData;
-  final List<SelectOption> options;
-  final List<SelectOption> selectedOptions;
+  final GridSelectOptionCellContext cellContext;
   final VoidCallback onDismissed;
   final VoidCallback onDismissed;
 
 
   const SelectOptionCellEditor({
   const SelectOptionCellEditor({
-    required this.cellData,
-    required this.options,
-    required this.selectedOptions,
+    required this.cellContext,
     required this.onDismissed,
     required this.onDismissed,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
@@ -42,9 +38,7 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return BlocProvider(
     return BlocProvider(
       create: (context) => SelectOptionEditorBloc(
       create: (context) => SelectOptionEditorBloc(
-        cellData: cellData,
-        options: options,
-        selectedOptions: selectedOptions,
+        cellContext: cellContext,
       )..add(const SelectOptionEditorEvent.initial()),
       )..add(const SelectOptionEditorEvent.initial()),
       child: BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
       child: BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
         builder: (context, state) {
         builder: (context, state) {
@@ -66,16 +60,12 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
 
 
   static void show(
   static void show(
     BuildContext context,
     BuildContext context,
-    GridCell cellData,
-    List<SelectOption> options,
-    List<SelectOption> selectedOptions,
+    GridSelectOptionCellContext cellContext,
     VoidCallback onDismissed,
     VoidCallback onDismissed,
   ) {
   ) {
     SelectOptionCellEditor.remove(context);
     SelectOptionCellEditor.remove(context);
     final editor = SelectOptionCellEditor(
     final editor = SelectOptionCellEditor(
-      cellData: cellData,
-      options: options,
-      selectedOptions: selectedOptions,
+      cellContext: cellContext,
       onDismissed: onDismissed,
       onDismissed: onDismissed,
     );
     );
 
 

+ 3 - 3
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart

@@ -14,10 +14,10 @@ class GridTextCellStyle extends GridCellStyle {
 }
 }
 
 
 class GridTextCell extends GridCellWidget {
 class GridTextCell extends GridCellWidget {
-  final GridCell cellData;
+  final GridCellContext cellContext;
   late final GridTextCellStyle? cellStyle;
   late final GridTextCellStyle? cellStyle;
   GridTextCell({
   GridTextCell({
-    required this.cellData,
+    required this.cellContext,
     GridCellStyle? style,
     GridCellStyle? style,
     Key? key,
     Key? key,
   }) : super(key: key) {
   }) : super(key: key) {
@@ -41,7 +41,7 @@ class _GridTextCellState extends State<GridTextCell> {
 
 
   @override
   @override
   void initState() {
   void initState() {
-    _cellBloc = getIt<TextCellBloc>(param1: widget.cellData);
+    _cellBloc = getIt<TextCellBloc>(param1: widget.cellContext);
     _cellBloc.add(const TextCellEvent.initial());
     _cellBloc.add(const TextCellEvent.initial());
     _controller = TextEditingController(text: _cellBloc.state.content);
     _controller = TextEditingController(text: _cellBloc.state.content);
     _focusNode = FocusNode();
     _focusNode = FocusNode();

+ 26 - 23
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart

@@ -8,17 +8,18 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:provider/provider.dart';
 import 'package:provider/provider.dart';
 import 'row_action_sheet.dart';
 import 'row_action_sheet.dart';
-import 'package:dartz/dartz.dart' show Option;
 
 
 import 'row_detail.dart';
 import 'row_detail.dart';
 
 
 class GridRowWidget extends StatefulWidget {
 class GridRowWidget extends StatefulWidget {
   final GridRow rowData;
   final GridRow rowData;
   final GridRowCache rowCache;
   final GridRowCache rowCache;
+  final GridCellCache cellCache;
 
 
   const GridRowWidget({
   const GridRowWidget({
     required this.rowData,
     required this.rowData,
     required this.rowCache,
     required this.rowCache,
+    required this.cellCache,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
@@ -49,7 +50,7 @@ class _GridRowWidgetState extends State<GridRowWidget> {
           builder: (context, state) {
           builder: (context, state) {
             final children = [
             final children = [
               const _RowLeading(),
               const _RowLeading(),
-              _RowCells(onExpand: () => onExpandCell(context)),
+              _RowCells(cellCache: widget.cellCache, onExpand: () => onExpandCell(context)),
               const _RowTrailing(),
               const _RowTrailing(),
             ];
             ];
 
 
@@ -73,7 +74,11 @@ class _GridRowWidgetState extends State<GridRowWidget> {
   }
   }
 
 
   void onExpandCell(BuildContext context) {
   void onExpandCell(BuildContext context) {
-    final page = RowDetailPage(rowData: widget.rowData, rowCache: widget.rowCache);
+    final page = RowDetailPage(
+      rowData: widget.rowData,
+      rowCache: widget.rowCache,
+      cellCache: widget.cellCache,
+    );
     page.show(context);
     page.show(context);
   }
   }
 }
 }
@@ -147,13 +152,14 @@ class _DeleteRowButton extends StatelessWidget {
 }
 }
 
 
 class _RowCells extends StatelessWidget {
 class _RowCells extends StatelessWidget {
+  final GridCellCache cellCache;
   final VoidCallback onExpand;
   final VoidCallback onExpand;
-  const _RowCells({required this.onExpand, Key? key}) : super(key: key);
+  const _RowCells({required this.cellCache, required this.onExpand, Key? key}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return BlocBuilder<RowBloc, RowState>(
     return BlocBuilder<RowBloc, RowState>(
-      buildWhen: (previous, current) => previous.cellDataMap != current.cellDataMap,
+      buildWhen: (previous, current) => previous.cellDataMap.length != current.cellDataMap.length,
       builder: (context, state) {
       builder: (context, state) {
         return Row(
         return Row(
           mainAxisSize: MainAxisSize.min,
           mainAxisSize: MainAxisSize.min,
@@ -164,24 +170,21 @@ class _RowCells extends StatelessWidget {
     );
     );
   }
   }
 
 
-  List<Widget> _makeCells(Option<CellDataMap> data) {
-    return data.fold(
-      () => [],
-      (cellDataMap) => cellDataMap.values.map(
-        (cellData) {
-          Widget? expander;
-          if (cellData.field.isPrimary) {
-            expander = _CellExpander(onExpand: onExpand);
-          }
-
-          return CellContainer(
-            width: cellData.field.width.toDouble(),
-            child: buildGridCell(cellData),
-            expander: expander,
-          );
-        },
-      ).toList(),
-    );
+  List<Widget> _makeCells(GridCellMap gridCellMap) {
+    return gridCellMap.values.map(
+      (gridCell) {
+        Widget? expander;
+        if (gridCell.field.isPrimary) {
+          expander = _CellExpander(onExpand: onExpand);
+        }
+
+        return CellContainer(
+          width: gridCell.field.width.toDouble(),
+          child: buildGridCellWidget(gridCell, cellCache),
+          expander: expander,
+        );
+      },
+    ).toList();
   }
   }
 }
 }
 
 

+ 54 - 25
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart

@@ -1,12 +1,15 @@
+import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
+import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
 import 'package:easy_localization/easy_localization.dart';
 import 'package:easy_localization/easy_localization.dart';
@@ -18,10 +21,12 @@ import 'package:window_size/window_size.dart';
 class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
 class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
   final GridRow rowData;
   final GridRow rowData;
   final GridRowCache rowCache;
   final GridRowCache rowCache;
+  final GridCellCache cellCache;
 
 
   const RowDetailPage({
   const RowDetailPage({
     required this.rowData,
     required this.rowData,
     required this.rowCache,
     required this.rowCache,
+    required this.cellCache,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
@@ -59,32 +64,45 @@ class _RowDetailPageState extends State<RowDetailPage> {
         bloc.add(const RowDetailEvent.initial());
         bloc.add(const RowDetailEvent.initial());
         return bloc;
         return bloc;
       },
       },
-      child: const Padding(
-        padding: EdgeInsets.symmetric(horizontal: 80, vertical: 40),
-        child: _PropertyList(),
+      child: Padding(
+        padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 40),
+        child: _PropertyList(cellCache: widget.cellCache),
       ),
       ),
     );
     );
   }
   }
 }
 }
 
 
 class _PropertyList extends StatelessWidget {
 class _PropertyList extends StatelessWidget {
-  const _PropertyList({
+  final GridCellCache cellCache;
+  final ScrollController _scrollController;
+  _PropertyList({
+    required this.cellCache,
     Key? key,
     Key? key,
-  }) : super(key: key);
+  })  : _scrollController = ScrollController(),
+        super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return BlocBuilder<RowDetailBloc, RowDetailState>(
     return BlocBuilder<RowDetailBloc, RowDetailState>(
-      buildWhen: (previous, current) => previous.cellDatas != current.cellDatas,
+      buildWhen: (previous, current) => previous.gridCells != current.gridCells,
       builder: (context, state) {
       builder: (context, state) {
-        return ListView.separated(
-          itemCount: state.cellDatas.length,
-          itemBuilder: (BuildContext context, int index) {
-            return _RowDetailCell(cellData: state.cellDatas[index]);
-          },
-          separatorBuilder: (BuildContext context, int index) {
-            return const VSpace(2);
-          },
+        return ScrollbarListStack(
+          axis: Axis.vertical,
+          controller: _scrollController,
+          barSize: GridSize.scrollBarSize,
+          child: ListView.separated(
+            controller: _scrollController,
+            itemCount: state.gridCells.length,
+            itemBuilder: (BuildContext context, int index) {
+              return _RowDetailCell(
+                gridCell: state.gridCells[index],
+                cellCache: cellCache,
+              );
+            },
+            separatorBuilder: (BuildContext context, int index) {
+              return const VSpace(2);
+            },
+          ),
         );
         );
       },
       },
     );
     );
@@ -92,15 +110,22 @@ class _PropertyList extends StatelessWidget {
 }
 }
 
 
 class _RowDetailCell extends StatelessWidget {
 class _RowDetailCell extends StatelessWidget {
-  final GridCell cellData;
-  const _RowDetailCell({required this.cellData, Key? key}) : super(key: key);
+  final GridCell gridCell;
+  final GridCellCache cellCache;
+  const _RowDetailCell({
+    required this.gridCell,
+    required this.cellCache,
+    Key? key,
+  }) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     final theme = context.watch<AppTheme>();
-    final cell = buildGridCell(
-      cellData,
-      style: _buildCellStyle(theme, cellData.field.fieldType),
+
+    final cell = buildGridCellWidget(
+      gridCell,
+      cellCache,
+      style: _buildCellStyle(theme, gridCell.field.fieldType),
     );
     );
     return SizedBox(
     return SizedBox(
       height: 36,
       height: 36,
@@ -110,7 +135,7 @@ class _RowDetailCell extends StatelessWidget {
         children: [
         children: [
           SizedBox(
           SizedBox(
             width: 150,
             width: 150,
-            child: FieldCellButton(field: cellData.field, onTap: () => _showFieldEditor(context)),
+            child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)),
           ),
           ),
           const HSpace(10),
           const HSpace(10),
           Expanded(
           Expanded(
@@ -126,10 +151,10 @@ class _RowDetailCell extends StatelessWidget {
 
 
   void _showFieldEditor(BuildContext context) {
   void _showFieldEditor(BuildContext context) {
     FieldEditor(
     FieldEditor(
-      gridId: cellData.gridId,
+      gridId: gridCell.gridId,
       fieldContextLoader: FieldContextLoaderAdaptor(
       fieldContextLoader: FieldContextLoaderAdaptor(
-        gridId: cellData.gridId,
-        field: cellData.field,
+        gridId: gridCell.gridId,
+        field: gridCell.field,
       ),
       ),
     ).show(context);
     ).show(context);
   }
   }
@@ -142,7 +167,9 @@ GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
     case FieldType.DateTime:
     case FieldType.DateTime:
       return null;
       return null;
     case FieldType.MultiSelect:
     case FieldType.MultiSelect:
-      return null;
+      return SelectOptionCellStyle(
+        placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
+      );
     case FieldType.Number:
     case FieldType.Number:
       return null;
       return null;
     case FieldType.RichText:
     case FieldType.RichText:
@@ -150,7 +177,9 @@ GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
         placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
         placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
       );
       );
     case FieldType.SingleSelect:
     case FieldType.SingleSelect:
-      return null;
+      return SelectOptionCellStyle(
+        placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
+      );
     default:
     default:
       return null;
       return null;
   }
   }

+ 1 - 1
frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart

@@ -46,7 +46,7 @@ class FlowyText extends StatelessWidget {
         style: TextStyle(
         style: TextStyle(
           color: color ?? theme.textColor,
           color: color ?? theme.textColor,
           fontWeight: fontWeight,
           fontWeight: fontWeight,
-          fontSize: fontSize + 2,
+          fontSize: fontSize,
           fontFamily: 'Mulish',
           fontFamily: 'Mulish',
         ));
         ));
   }
   }

+ 1 - 1
frontend/app_flowy/packages/flowy_sdk/lib/log.dart

@@ -31,7 +31,7 @@ class Log {
   }
   }
 
 
   static void trace(dynamic msg) {
   static void trace(dynamic msg) {
-    Log.shared._logger.d(msg);
+    Log.shared._logger.v(msg);
   }
   }
 
 
   static void error(dynamic msg) {
   static void error(dynamic msg) {

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

@@ -1081,12 +1081,77 @@ class IndexRowOrder extends $pb.GeneratedMessage {
   void clearIndex() => clearField(2);
   void clearIndex() => clearField(2);
 }
 }
 
 
+class UpdatedRowOrder extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UpdatedRowOrder', createEmptyInstance: create)
+    ..aOM<RowOrder>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowOrder', subBuilder: RowOrder.create)
+    ..aOM<Row>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'row', subBuilder: Row.create)
+    ..hasRequiredFields = false
+  ;
+
+  UpdatedRowOrder._() : super();
+  factory UpdatedRowOrder({
+    RowOrder? rowOrder,
+    Row? row,
+  }) {
+    final _result = create();
+    if (rowOrder != null) {
+      _result.rowOrder = rowOrder;
+    }
+    if (row != null) {
+      _result.row = row;
+    }
+    return _result;
+  }
+  factory UpdatedRowOrder.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory UpdatedRowOrder.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  UpdatedRowOrder clone() => UpdatedRowOrder()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  UpdatedRowOrder copyWith(void Function(UpdatedRowOrder) updates) => super.copyWith((message) => updates(message as UpdatedRowOrder)) as UpdatedRowOrder; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static UpdatedRowOrder create() => UpdatedRowOrder._();
+  UpdatedRowOrder createEmptyInstance() => create();
+  static $pb.PbList<UpdatedRowOrder> createRepeated() => $pb.PbList<UpdatedRowOrder>();
+  @$core.pragma('dart2js:noInline')
+  static UpdatedRowOrder getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UpdatedRowOrder>(create);
+  static UpdatedRowOrder? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  RowOrder get rowOrder => $_getN(0);
+  @$pb.TagNumber(1)
+  set rowOrder(RowOrder v) { setField(1, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasRowOrder() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearRowOrder() => clearField(1);
+  @$pb.TagNumber(1)
+  RowOrder ensureRowOrder() => $_ensure(0);
+
+  @$pb.TagNumber(2)
+  Row get row => $_getN(1);
+  @$pb.TagNumber(2)
+  set row(Row v) { setField(2, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasRow() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearRow() => clearField(2);
+  @$pb.TagNumber(2)
+  Row ensureRow() => $_ensure(1);
+}
+
 class GridRowsChangeset extends $pb.GeneratedMessage {
 class GridRowsChangeset extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridRowsChangeset', createEmptyInstance: create)
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridRowsChangeset', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blockId')
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blockId')
     ..pc<IndexRowOrder>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'insertedRows', $pb.PbFieldType.PM, subBuilder: IndexRowOrder.create)
     ..pc<IndexRowOrder>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'insertedRows', $pb.PbFieldType.PM, subBuilder: IndexRowOrder.create)
     ..pc<RowOrder>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deletedRows', $pb.PbFieldType.PM, subBuilder: RowOrder.create)
     ..pc<RowOrder>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deletedRows', $pb.PbFieldType.PM, subBuilder: RowOrder.create)
-    ..pc<RowOrder>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'updatedRows', $pb.PbFieldType.PM, subBuilder: RowOrder.create)
+    ..pc<UpdatedRowOrder>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'updatedRows', $pb.PbFieldType.PM, subBuilder: UpdatedRowOrder.create)
     ..hasRequiredFields = false
     ..hasRequiredFields = false
   ;
   ;
 
 
@@ -1095,7 +1160,7 @@ class GridRowsChangeset extends $pb.GeneratedMessage {
     $core.String? blockId,
     $core.String? blockId,
     $core.Iterable<IndexRowOrder>? insertedRows,
     $core.Iterable<IndexRowOrder>? insertedRows,
     $core.Iterable<RowOrder>? deletedRows,
     $core.Iterable<RowOrder>? deletedRows,
-    $core.Iterable<RowOrder>? updatedRows,
+    $core.Iterable<UpdatedRowOrder>? updatedRows,
   }) {
   }) {
     final _result = create();
     final _result = create();
     if (blockId != null) {
     if (blockId != null) {
@@ -1149,7 +1214,7 @@ class GridRowsChangeset extends $pb.GeneratedMessage {
   $core.List<RowOrder> get deletedRows => $_getList(2);
   $core.List<RowOrder> get deletedRows => $_getList(2);
 
 
   @$pb.TagNumber(4)
   @$pb.TagNumber(4)
-  $core.List<RowOrder> get updatedRows => $_getList(3);
+  $core.List<UpdatedRowOrder> get updatedRows => $_getList(3);
 }
 }
 
 
 class GridBlock extends $pb.GeneratedMessage {
 class GridBlock extends $pb.GeneratedMessage {
@@ -1268,108 +1333,6 @@ class Cell extends $pb.GeneratedMessage {
   void clearContent() => clearField(2);
   void clearContent() => clearField(2);
 }
 }
 
 
-enum CellNotificationData_OneOfContent {
-  content, 
-  notSet
-}
-
-class CellNotificationData extends $pb.GeneratedMessage {
-  static const $core.Map<$core.int, CellNotificationData_OneOfContent> _CellNotificationData_OneOfContentByTag = {
-    4 : CellNotificationData_OneOfContent.content,
-    0 : CellNotificationData_OneOfContent.notSet
-  };
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CellNotificationData', createEmptyInstance: create)
-    ..oo(0, [4])
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
-    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowId')
-    ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'content')
-    ..hasRequiredFields = false
-  ;
-
-  CellNotificationData._() : super();
-  factory CellNotificationData({
-    $core.String? gridId,
-    $core.String? fieldId,
-    $core.String? rowId,
-    $core.String? content,
-  }) {
-    final _result = create();
-    if (gridId != null) {
-      _result.gridId = gridId;
-    }
-    if (fieldId != null) {
-      _result.fieldId = fieldId;
-    }
-    if (rowId != null) {
-      _result.rowId = rowId;
-    }
-    if (content != null) {
-      _result.content = content;
-    }
-    return _result;
-  }
-  factory CellNotificationData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory CellNotificationData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
-  @$core.Deprecated(
-  'Using this can add significant overhead to your binary. '
-  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
-  'Will be removed in next major version')
-  CellNotificationData clone() => CellNotificationData()..mergeFromMessage(this);
-  @$core.Deprecated(
-  'Using this can add significant overhead to your binary. '
-  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
-  'Will be removed in next major version')
-  CellNotificationData copyWith(void Function(CellNotificationData) updates) => super.copyWith((message) => updates(message as CellNotificationData)) as CellNotificationData; // ignore: deprecated_member_use
-  $pb.BuilderInfo get info_ => _i;
-  @$core.pragma('dart2js:noInline')
-  static CellNotificationData create() => CellNotificationData._();
-  CellNotificationData createEmptyInstance() => create();
-  static $pb.PbList<CellNotificationData> createRepeated() => $pb.PbList<CellNotificationData>();
-  @$core.pragma('dart2js:noInline')
-  static CellNotificationData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CellNotificationData>(create);
-  static CellNotificationData? _defaultInstance;
-
-  CellNotificationData_OneOfContent whichOneOfContent() => _CellNotificationData_OneOfContentByTag[$_whichOneof(0)]!;
-  void clearOneOfContent() => clearField($_whichOneof(0));
-
-  @$pb.TagNumber(1)
-  $core.String get gridId => $_getSZ(0);
-  @$pb.TagNumber(1)
-  set gridId($core.String v) { $_setString(0, v); }
-  @$pb.TagNumber(1)
-  $core.bool hasGridId() => $_has(0);
-  @$pb.TagNumber(1)
-  void clearGridId() => clearField(1);
-
-  @$pb.TagNumber(2)
-  $core.String get fieldId => $_getSZ(1);
-  @$pb.TagNumber(2)
-  set fieldId($core.String v) { $_setString(1, v); }
-  @$pb.TagNumber(2)
-  $core.bool hasFieldId() => $_has(1);
-  @$pb.TagNumber(2)
-  void clearFieldId() => clearField(2);
-
-  @$pb.TagNumber(3)
-  $core.String get rowId => $_getSZ(2);
-  @$pb.TagNumber(3)
-  set rowId($core.String v) { $_setString(2, v); }
-  @$pb.TagNumber(3)
-  $core.bool hasRowId() => $_has(2);
-  @$pb.TagNumber(3)
-  void clearRowId() => clearField(3);
-
-  @$pb.TagNumber(4)
-  $core.String get content => $_getSZ(3);
-  @$pb.TagNumber(4)
-  set content($core.String v) { $_setString(3, v); }
-  @$pb.TagNumber(4)
-  $core.bool hasContent() => $_has(3);
-  @$pb.TagNumber(4)
-  void clearContent() => clearField(4);
-}
-
 class RepeatedCell extends $pb.GeneratedMessage {
 class RepeatedCell extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RepeatedCell', createEmptyInstance: create)
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RepeatedCell', createEmptyInstance: create)
     ..pc<Cell>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: Cell.create)
     ..pc<Cell>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: Cell.create)

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

@@ -236,6 +236,17 @@ const IndexRowOrder$json = const {
 
 
 /// Descriptor for `IndexRowOrder`. Decode as a `google.protobuf.DescriptorProto`.
 /// Descriptor for `IndexRowOrder`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List indexRowOrderDescriptor = $convert.base64Decode('Cg1JbmRleFJvd09yZGVyEiYKCXJvd19vcmRlchgBIAEoCzIJLlJvd09yZGVyUghyb3dPcmRlchIWCgVpbmRleBgCIAEoBUgAUgVpbmRleEIOCgxvbmVfb2ZfaW5kZXg=');
 final $typed_data.Uint8List indexRowOrderDescriptor = $convert.base64Decode('Cg1JbmRleFJvd09yZGVyEiYKCXJvd19vcmRlchgBIAEoCzIJLlJvd09yZGVyUghyb3dPcmRlchIWCgVpbmRleBgCIAEoBUgAUgVpbmRleEIOCgxvbmVfb2ZfaW5kZXg=');
+@$core.Deprecated('Use updatedRowOrderDescriptor instead')
+const UpdatedRowOrder$json = const {
+  '1': 'UpdatedRowOrder',
+  '2': const [
+    const {'1': 'row_order', '3': 1, '4': 1, '5': 11, '6': '.RowOrder', '10': 'rowOrder'},
+    const {'1': 'row', '3': 2, '4': 1, '5': 11, '6': '.Row', '10': 'row'},
+  ],
+};
+
+/// Descriptor for `UpdatedRowOrder`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List updatedRowOrderDescriptor = $convert.base64Decode('Cg9VcGRhdGVkUm93T3JkZXISJgoJcm93X29yZGVyGAEgASgLMgkuUm93T3JkZXJSCHJvd09yZGVyEhYKA3JvdxgCIAEoCzIELlJvd1IDcm93');
 @$core.Deprecated('Use gridRowsChangesetDescriptor instead')
 @$core.Deprecated('Use gridRowsChangesetDescriptor instead')
 const GridRowsChangeset$json = const {
 const GridRowsChangeset$json = const {
   '1': 'GridRowsChangeset',
   '1': 'GridRowsChangeset',
@@ -243,12 +254,12 @@ const GridRowsChangeset$json = const {
     const {'1': 'block_id', '3': 1, '4': 1, '5': 9, '10': 'blockId'},
     const {'1': 'block_id', '3': 1, '4': 1, '5': 9, '10': 'blockId'},
     const {'1': 'inserted_rows', '3': 2, '4': 3, '5': 11, '6': '.IndexRowOrder', '10': 'insertedRows'},
     const {'1': 'inserted_rows', '3': 2, '4': 3, '5': 11, '6': '.IndexRowOrder', '10': 'insertedRows'},
     const {'1': 'deleted_rows', '3': 3, '4': 3, '5': 11, '6': '.RowOrder', '10': 'deletedRows'},
     const {'1': 'deleted_rows', '3': 3, '4': 3, '5': 11, '6': '.RowOrder', '10': 'deletedRows'},
-    const {'1': 'updated_rows', '3': 4, '4': 3, '5': 11, '6': '.RowOrder', '10': 'updatedRows'},
+    const {'1': 'updated_rows', '3': 4, '4': 3, '5': 11, '6': '.UpdatedRowOrder', '10': 'updatedRows'},
   ],
   ],
 };
 };
 
 
 /// Descriptor for `GridRowsChangeset`. Decode as a `google.protobuf.DescriptorProto`.
 /// Descriptor for `GridRowsChangeset`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List gridRowsChangesetDescriptor = $convert.base64Decode('ChFHcmlkUm93c0NoYW5nZXNldBIZCghibG9ja19pZBgBIAEoCVIHYmxvY2tJZBIzCg1pbnNlcnRlZF9yb3dzGAIgAygLMg4uSW5kZXhSb3dPcmRlclIMaW5zZXJ0ZWRSb3dzEiwKDGRlbGV0ZWRfcm93cxgDIAMoCzIJLlJvd09yZGVyUgtkZWxldGVkUm93cxIsCgx1cGRhdGVkX3Jvd3MYBCADKAsyCS5Sb3dPcmRlclILdXBkYXRlZFJvd3M=');
+final $typed_data.Uint8List gridRowsChangesetDescriptor = $convert.base64Decode('ChFHcmlkUm93c0NoYW5nZXNldBIZCghibG9ja19pZBgBIAEoCVIHYmxvY2tJZBIzCg1pbnNlcnRlZF9yb3dzGAIgAygLMg4uSW5kZXhSb3dPcmRlclIMaW5zZXJ0ZWRSb3dzEiwKDGRlbGV0ZWRfcm93cxgDIAMoCzIJLlJvd09yZGVyUgtkZWxldGVkUm93cxIzCgx1cGRhdGVkX3Jvd3MYBCADKAsyEC5VcGRhdGVkUm93T3JkZXJSC3VwZGF0ZWRSb3dz');
 @$core.Deprecated('Use gridBlockDescriptor instead')
 @$core.Deprecated('Use gridBlockDescriptor instead')
 const GridBlock$json = const {
 const GridBlock$json = const {
   '1': 'GridBlock',
   '1': 'GridBlock',
@@ -271,22 +282,6 @@ const Cell$json = const {
 
 
 /// Descriptor for `Cell`. Decode as a `google.protobuf.DescriptorProto`.
 /// Descriptor for `Cell`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List cellDescriptor = $convert.base64Decode('CgRDZWxsEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEhgKB2NvbnRlbnQYAiABKAlSB2NvbnRlbnQ=');
 final $typed_data.Uint8List cellDescriptor = $convert.base64Decode('CgRDZWxsEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEhgKB2NvbnRlbnQYAiABKAlSB2NvbnRlbnQ=');
-@$core.Deprecated('Use cellNotificationDataDescriptor instead')
-const CellNotificationData$json = const {
-  '1': 'CellNotificationData',
-  '2': const [
-    const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
-    const {'1': 'field_id', '3': 2, '4': 1, '5': 9, '10': 'fieldId'},
-    const {'1': 'row_id', '3': 3, '4': 1, '5': 9, '10': 'rowId'},
-    const {'1': 'content', '3': 4, '4': 1, '5': 9, '9': 0, '10': 'content'},
-  ],
-  '8': const [
-    const {'1': 'one_of_content'},
-  ],
-};
-
-/// Descriptor for `CellNotificationData`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List cellNotificationDataDescriptor = $convert.base64Decode('ChRDZWxsTm90aWZpY2F0aW9uRGF0YRIXCgdncmlkX2lkGAEgASgJUgZncmlkSWQSGQoIZmllbGRfaWQYAiABKAlSB2ZpZWxkSWQSFQoGcm93X2lkGAMgASgJUgVyb3dJZBIaCgdjb250ZW50GAQgASgJSABSB2NvbnRlbnRCEAoOb25lX29mX2NvbnRlbnQ=');
 @$core.Deprecated('Use repeatedCellDescriptor instead')
 @$core.Deprecated('Use repeatedCellDescriptor instead')
 const RepeatedCell$json = const {
 const RepeatedCell$json = const {
   '1': 'RepeatedCell',
   '1': 'RepeatedCell',

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

@@ -96,6 +96,12 @@ impl ClientGridBlockMetaEditor {
         Ok(())
         Ok(())
     }
     }
 
 
+    pub async fn get_row_meta(&self, row_id: &str) -> FlowyResult<Option<Arc<RowMeta>>> {
+        let row_ids = vec![Cow::Borrowed(row_id)];
+        let row_meta = self.get_row_metas(Some(row_ids)).await?.pop();
+        Ok(row_meta)
+    }
+
     pub async fn get_row_metas<T>(&self, row_ids: Option<Vec<Cow<'_, T>>>) -> FlowyResult<Vec<Arc<RowMeta>>>
     pub async fn get_row_metas<T>(&self, row_ids: Option<Vec<Cow<'_, T>>>) -> FlowyResult<Vec<Arc<RowMeta>>>
     where
     where
         T: AsRef<str> + ToOwned + ?Sized,
         T: AsRef<str> + ToOwned + ?Sized,
@@ -113,6 +119,11 @@ impl ClientGridBlockMetaEditor {
         Ok(cell_metas)
         Ok(cell_metas)
     }
     }
 
 
+    pub async fn get_row_order(&self, row_id: &str) -> FlowyResult<Option<RowOrder>> {
+        let row_ids = Some(vec![Cow::Borrowed(row_id)]);
+        Ok(self.get_row_orders(row_ids).await?.pop())
+    }
+
     pub async fn get_row_orders<T>(&self, row_ids: Option<Vec<Cow<'_, T>>>) -> FlowyResult<Vec<RowOrder>>
     pub async fn get_row_orders<T>(&self, row_ids: Option<Vec<Cow<'_, T>>>) -> FlowyResult<Vec<RowOrder>>
     where
     where
         T: AsRef<str> + ToOwned + ?Sized,
         T: AsRef<str> + ToOwned + ?Sized,

+ 35 - 41
frontend/rust-lib/flowy-grid/src/services/block_meta_manager.rs

@@ -3,16 +3,15 @@ use crate::manager::GridUser;
 use crate::services::block_meta_editor::ClientGridBlockMetaEditor;
 use crate::services::block_meta_editor::ClientGridBlockMetaEditor;
 use crate::services::persistence::block_index::BlockIndexPersistence;
 use crate::services::persistence::block_index::BlockIndexPersistence;
 use crate::services::row::{group_row_orders, GridBlockSnapshot};
 use crate::services::row::{group_row_orders, GridBlockSnapshot};
-use std::borrow::Cow;
-
 use dashmap::DashMap;
 use dashmap::DashMap;
 use flowy_error::FlowyResult;
 use flowy_error::FlowyResult;
 use flowy_grid_data_model::entities::{
 use flowy_grid_data_model::entities::{
-    CellChangeset, CellMeta, CellNotificationData, GridBlockMeta, GridBlockMetaChangeset, GridRowsChangeset,
-    IndexRowOrder, RowMeta, RowMetaChangeset, RowOrder,
+    CellChangeset, CellMeta, GridBlockMeta, GridBlockMetaChangeset, GridRowsChangeset, IndexRowOrder, Row, RowMeta,
+    RowMetaChangeset, RowOrder, UpdatedRowOrder,
 };
 };
 use flowy_revision::disk::SQLiteGridBlockMetaRevisionPersistence;
 use flowy_revision::disk::SQLiteGridBlockMetaRevisionPersistence;
 use flowy_revision::{RevisionManager, RevisionPersistence};
 use flowy_revision::{RevisionManager, RevisionPersistence};
+use std::borrow::Cow;
 use std::collections::HashMap;
 use std::collections::HashMap;
 use std::sync::Arc;
 use std::sync::Arc;
 
 
@@ -108,10 +107,22 @@ impl GridBlockMetaEditorManager {
         Ok(changesets)
         Ok(changesets)
     }
     }
 
 
-    pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
+    pub async fn update_row<F>(&self, changeset: RowMetaChangeset, row_builder: F) -> FlowyResult<()>
+    where
+        F: FnOnce(Arc<RowMeta>) -> Option<Row>,
+    {
         let editor = self.get_editor_from_row_id(&changeset.row_id).await?;
         let editor = self.get_editor_from_row_id(&changeset.row_id).await?;
         let _ = editor.update_row(changeset.clone()).await?;
         let _ = editor.update_row(changeset.clone()).await?;
-        let _ = self.notify_did_update_block_row(&changeset.row_id).await?;
+        match editor.get_row_meta(&changeset.row_id).await? {
+            None => tracing::error!("Internal error: can't find the row with id: {}", changeset.row_id),
+            Some(row_meta) => {
+                if let Some(row) = row_builder(row_meta.clone()) {
+                    let row_order = UpdatedRowOrder::new(&row_meta, row);
+                    let block_order_changeset = GridRowsChangeset::update(&editor.block_id, vec![row_order]);
+                    let _ = self.notify_did_update_block(block_order_changeset).await?;
+                }
+            }
+        }
         Ok(())
         Ok(())
     }
     }
 
 
@@ -119,11 +130,15 @@ impl GridBlockMetaEditorManager {
         let row_id = row_id.to_owned();
         let row_id = row_id.to_owned();
         let block_id = self.persistence.get_block_id(&row_id)?;
         let block_id = self.persistence.get_block_id(&row_id)?;
         let editor = self.get_editor(&block_id).await?;
         let editor = self.get_editor(&block_id).await?;
-        let row_orders = editor.get_row_orders(Some(vec![Cow::Borrowed(&row_id)])).await?;
-        let _ = editor.delete_rows(vec![Cow::Borrowed(&row_id)]).await?;
-        let _ = self
-            .notify_did_update_block(GridRowsChangeset::delete(&block_id, row_orders))
-            .await?;
+        match editor.get_row_order(&row_id).await? {
+            None => {}
+            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]))
+                    .await?;
+            }
+        }
 
 
         Ok(())
         Ok(())
     }
     }
@@ -171,18 +186,13 @@ impl GridBlockMetaEditorManager {
         Ok(())
         Ok(())
     }
     }
 
 
-    pub async fn update_cell(&self, changeset: CellChangeset) -> FlowyResult<()> {
+    pub async fn update_cell<F>(&self, changeset: CellChangeset, row_builder: F) -> FlowyResult<()>
+    where
+        F: FnOnce(Arc<RowMeta>) -> Option<Row>,
+    {
         let row_changeset: RowMetaChangeset = changeset.clone().into();
         let row_changeset: RowMetaChangeset = changeset.clone().into();
-        let _ = self.update_row(row_changeset).await?;
-
-        let cell_notification_data = CellNotificationData {
-            grid_id: changeset.grid_id,
-            field_id: changeset.field_id,
-            row_id: changeset.row_id,
-            content: changeset.data,
-        };
-        self.notify_did_update_cell(cell_notification_data).await?;
-
+        let _ = self.update_row(row_changeset, row_builder).await?;
+        self.notify_did_update_cell(changeset).await?;
         Ok(())
         Ok(())
     }
     }
 
 
@@ -229,20 +239,6 @@ impl GridBlockMetaEditorManager {
         Ok(block_cell_metas)
         Ok(block_cell_metas)
     }
     }
 
 
-    async fn notify_did_update_block_row(&self, row_id: &str) -> FlowyResult<()> {
-        let editor = self.get_editor_from_row_id(row_id).await?;
-        let row_ids = Some(vec![Cow::Borrowed(&row_id)]);
-        match editor.get_row_orders(row_ids).await?.pop() {
-            None => {}
-            Some(row_order) => {
-                let block_order_changeset = GridRowsChangeset::update(&editor.block_id, vec![row_order]);
-                let _ = self.notify_did_update_block(block_order_changeset).await?;
-            }
-        }
-
-        Ok(())
-    }
-
     async fn notify_did_update_block(&self, changeset: GridRowsChangeset) -> FlowyResult<()> {
     async fn notify_did_update_block(&self, changeset: GridRowsChangeset) -> FlowyResult<()> {
         send_dart_notification(&self.grid_id, GridNotification::DidUpdateGridRow)
         send_dart_notification(&self.grid_id, GridNotification::DidUpdateGridRow)
             .payload(changeset)
             .payload(changeset)
@@ -250,11 +246,9 @@ impl GridBlockMetaEditorManager {
         Ok(())
         Ok(())
     }
     }
 
 
-    async fn notify_did_update_cell(&self, data: CellNotificationData) -> FlowyResult<()> {
-        let id = format!("{}:{}", data.row_id, data.field_id);
-        send_dart_notification(&id, GridNotification::DidUpdateCell)
-            .payload(data)
-            .send();
+    async fn notify_did_update_cell(&self, changeset: CellChangeset) -> FlowyResult<()> {
+        let id = format!("{}:{}", changeset.row_id, changeset.field_id);
+        send_dart_notification(&id, GridNotification::DidUpdateCell).send();
         Ok(())
         Ok(())
     }
     }
 }
 }

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

@@ -247,7 +247,10 @@ impl ClientGridEditor {
     }
     }
 
 
     pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
     pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
-        self.block_meta_manager.update_row(changeset).await
+        let field_metas = self.get_field_metas::<FieldOrder>(None).await?;
+        self.block_meta_manager
+            .update_row(changeset, |row_meta| make_row_from_row_meta(&field_metas, row_meta))
+            .await
     }
     }
 
 
     pub async fn get_rows(&self, block_id: &str) -> FlowyResult<RepeatedRow> {
     pub async fn get_rows(&self, block_id: &str) -> FlowyResult<RepeatedRow> {
@@ -322,7 +325,11 @@ impl ClientGridEditor {
             Some((_, field_meta)) => {
             Some((_, field_meta)) => {
                 // Update the changeset.data property with the return value.
                 // Update the changeset.data property with the return value.
                 changeset.data = Some(apply_cell_data_changeset(cell_data_changeset, cell_meta, field_meta)?);
                 changeset.data = Some(apply_cell_data_changeset(cell_data_changeset, cell_meta, field_meta)?);
-                let _ = self.block_meta_manager.update_cell(changeset).await?;
+                let field_metas = self.get_field_metas::<FieldOrder>(None).await?;
+                let _ = self
+                    .block_meta_manager
+                    .update_cell(changeset, |row_meta| make_row_from_row_meta(&field_metas, row_meta))
+                    .await?;
                 Ok(())
                 Ok(())
             }
             }
         }
         }

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

@@ -47,6 +47,10 @@ pub(crate) fn make_row_orders_from_row_metas(row_metas: &[Arc<RowMeta>]) -> Vec<
     row_metas.iter().map(RowOrder::from).collect::<Vec<_>>()
     row_metas.iter().map(RowOrder::from).collect::<Vec<_>>()
 }
 }
 
 
+pub(crate) fn make_row_from_row_meta(fields: &[FieldMeta], row_meta: Arc<RowMeta>) -> Option<Row> {
+    make_rows_from_row_metas(fields, &[row_meta]).pop()
+}
+
 pub(crate) fn make_rows_from_row_metas(fields: &[FieldMeta], row_metas: &[Arc<RowMeta>]) -> Vec<Row> {
 pub(crate) fn make_rows_from_row_metas(fields: &[FieldMeta], row_metas: &[Arc<RowMeta>]) -> Vec<Row> {
     let field_meta_map = fields
     let field_meta_map = fields
         .iter()
         .iter()

+ 21 - 18
shared-lib/flowy-grid-data-model/src/entities/grid.rs

@@ -352,7 +352,25 @@ pub struct IndexRowOrder {
     pub index: Option<i32>,
     pub index: Option<i32>,
 }
 }
 
 
-#[derive(Debug, Clone, Default, ProtoBuf)]
+#[derive(Debug, Default, ProtoBuf)]
+pub struct UpdatedRowOrder {
+    #[pb(index = 1)]
+    pub row_order: RowOrder,
+
+    #[pb(index = 2)]
+    pub row: Row,
+}
+
+impl UpdatedRowOrder {
+    pub fn new(row_meta: &RowMeta, row: Row) -> Self {
+        Self {
+            row_order: RowOrder::from(row_meta),
+            row,
+        }
+    }
+}
+
+#[derive(Debug, Default, ProtoBuf)]
 pub struct GridRowsChangeset {
 pub struct GridRowsChangeset {
     #[pb(index = 1)]
     #[pb(index = 1)]
     pub block_id: String,
     pub block_id: String,
@@ -364,7 +382,7 @@ pub struct GridRowsChangeset {
     pub deleted_rows: Vec<RowOrder>,
     pub deleted_rows: Vec<RowOrder>,
 
 
     #[pb(index = 4)]
     #[pb(index = 4)]
-    pub updated_rows: Vec<RowOrder>,
+    pub updated_rows: Vec<UpdatedRowOrder>,
 }
 }
 
 
 impl std::convert::From<RowOrder> for IndexRowOrder {
 impl std::convert::From<RowOrder> for IndexRowOrder {
@@ -399,7 +417,7 @@ impl GridRowsChangeset {
         }
         }
     }
     }
 
 
-    pub fn update(block_id: &str, updated_rows: Vec<RowOrder>) -> Self {
+    pub fn update(block_id: &str, updated_rows: Vec<UpdatedRowOrder>) -> Self {
         Self {
         Self {
             block_id: block_id.to_owned(),
             block_id: block_id.to_owned(),
             inserted_rows: vec![],
             inserted_rows: vec![],
@@ -445,21 +463,6 @@ impl Cell {
     }
     }
 }
 }
 
 
-#[derive(Debug, Clone, Default, ProtoBuf)]
-pub struct CellNotificationData {
-    #[pb(index = 1)]
-    pub grid_id: String,
-
-    #[pb(index = 2)]
-    pub field_id: String,
-
-    #[pb(index = 3)]
-    pub row_id: String,
-
-    #[pb(index = 4, one_of)]
-    pub content: Option<String>,
-}
-
 #[derive(Debug, Default, ProtoBuf)]
 #[derive(Debug, Default, ProtoBuf)]
 pub struct RepeatedCell {
 pub struct RepeatedCell {
     #[pb(index = 1)]
     #[pb(index = 1)]

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

@@ -3701,13 +3701,244 @@ impl ::protobuf::reflect::ProtobufValue for IndexRowOrder {
     }
     }
 }
 }
 
 
+#[derive(PartialEq,Clone,Default)]
+pub struct UpdatedRowOrder {
+    // message fields
+    pub row_order: ::protobuf::SingularPtrField<RowOrder>,
+    pub row: ::protobuf::SingularPtrField<Row>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a UpdatedRowOrder {
+    fn default() -> &'a UpdatedRowOrder {
+        <UpdatedRowOrder as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl UpdatedRowOrder {
+    pub fn new() -> UpdatedRowOrder {
+        ::std::default::Default::default()
+    }
+
+    // .RowOrder row_order = 1;
+
+
+    pub fn get_row_order(&self) -> &RowOrder {
+        self.row_order.as_ref().unwrap_or_else(|| <RowOrder as ::protobuf::Message>::default_instance())
+    }
+    pub fn clear_row_order(&mut self) {
+        self.row_order.clear();
+    }
+
+    pub fn has_row_order(&self) -> bool {
+        self.row_order.is_some()
+    }
+
+    // Param is passed by value, moved
+    pub fn set_row_order(&mut self, v: RowOrder) {
+        self.row_order = ::protobuf::SingularPtrField::some(v);
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_row_order(&mut self) -> &mut RowOrder {
+        if self.row_order.is_none() {
+            self.row_order.set_default();
+        }
+        self.row_order.as_mut().unwrap()
+    }
+
+    // Take field
+    pub fn take_row_order(&mut self) -> RowOrder {
+        self.row_order.take().unwrap_or_else(|| RowOrder::new())
+    }
+
+    // .Row row = 2;
+
+
+    pub fn get_row(&self) -> &Row {
+        self.row.as_ref().unwrap_or_else(|| <Row as ::protobuf::Message>::default_instance())
+    }
+    pub fn clear_row(&mut self) {
+        self.row.clear();
+    }
+
+    pub fn has_row(&self) -> bool {
+        self.row.is_some()
+    }
+
+    // Param is passed by value, moved
+    pub fn set_row(&mut self, v: Row) {
+        self.row = ::protobuf::SingularPtrField::some(v);
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_row(&mut self) -> &mut Row {
+        if self.row.is_none() {
+            self.row.set_default();
+        }
+        self.row.as_mut().unwrap()
+    }
+
+    // Take field
+    pub fn take_row(&mut self) -> Row {
+        self.row.take().unwrap_or_else(|| Row::new())
+    }
+}
+
+impl ::protobuf::Message for UpdatedRowOrder {
+    fn is_initialized(&self) -> bool {
+        for v in &self.row_order {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
+        for v in &self.row {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.row_order)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.row)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if let Some(ref v) = self.row_order.as_ref() {
+            let len = v.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        }
+        if let Some(ref v) = self.row.as_ref() {
+            let len = v.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if let Some(ref v) = self.row_order.as_ref() {
+            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        }
+        if let Some(ref v) = self.row.as_ref() {
+            os.write_tag(2, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> UpdatedRowOrder {
+        UpdatedRowOrder::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<RowOrder>>(
+                "row_order",
+                |m: &UpdatedRowOrder| { &m.row_order },
+                |m: &mut UpdatedRowOrder| { &mut m.row_order },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<Row>>(
+                "row",
+                |m: &UpdatedRowOrder| { &m.row },
+                |m: &mut UpdatedRowOrder| { &mut m.row },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<UpdatedRowOrder>(
+                "UpdatedRowOrder",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static UpdatedRowOrder {
+        static instance: ::protobuf::rt::LazyV2<UpdatedRowOrder> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(UpdatedRowOrder::new)
+    }
+}
+
+impl ::protobuf::Clear for UpdatedRowOrder {
+    fn clear(&mut self) {
+        self.row_order.clear();
+        self.row.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for UpdatedRowOrder {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for UpdatedRowOrder {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(PartialEq,Clone,Default)]
 #[derive(PartialEq,Clone,Default)]
 pub struct GridRowsChangeset {
 pub struct GridRowsChangeset {
     // message fields
     // message fields
     pub block_id: ::std::string::String,
     pub block_id: ::std::string::String,
     pub inserted_rows: ::protobuf::RepeatedField<IndexRowOrder>,
     pub inserted_rows: ::protobuf::RepeatedField<IndexRowOrder>,
     pub deleted_rows: ::protobuf::RepeatedField<RowOrder>,
     pub deleted_rows: ::protobuf::RepeatedField<RowOrder>,
-    pub updated_rows: ::protobuf::RepeatedField<RowOrder>,
+    pub updated_rows: ::protobuf::RepeatedField<UpdatedRowOrder>,
     // special fields
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
     pub cached_size: ::protobuf::CachedSize,
@@ -3800,10 +4031,10 @@ impl GridRowsChangeset {
         ::std::mem::replace(&mut self.deleted_rows, ::protobuf::RepeatedField::new())
         ::std::mem::replace(&mut self.deleted_rows, ::protobuf::RepeatedField::new())
     }
     }
 
 
-    // repeated .RowOrder updated_rows = 4;
+    // repeated .UpdatedRowOrder updated_rows = 4;
 
 
 
 
-    pub fn get_updated_rows(&self) -> &[RowOrder] {
+    pub fn get_updated_rows(&self) -> &[UpdatedRowOrder] {
         &self.updated_rows
         &self.updated_rows
     }
     }
     pub fn clear_updated_rows(&mut self) {
     pub fn clear_updated_rows(&mut self) {
@@ -3811,17 +4042,17 @@ impl GridRowsChangeset {
     }
     }
 
 
     // Param is passed by value, moved
     // Param is passed by value, moved
-    pub fn set_updated_rows(&mut self, v: ::protobuf::RepeatedField<RowOrder>) {
+    pub fn set_updated_rows(&mut self, v: ::protobuf::RepeatedField<UpdatedRowOrder>) {
         self.updated_rows = v;
         self.updated_rows = v;
     }
     }
 
 
     // Mutable pointer to the field.
     // Mutable pointer to the field.
-    pub fn mut_updated_rows(&mut self) -> &mut ::protobuf::RepeatedField<RowOrder> {
+    pub fn mut_updated_rows(&mut self) -> &mut ::protobuf::RepeatedField<UpdatedRowOrder> {
         &mut self.updated_rows
         &mut self.updated_rows
     }
     }
 
 
     // Take field
     // Take field
-    pub fn take_updated_rows(&mut self) -> ::protobuf::RepeatedField<RowOrder> {
+    pub fn take_updated_rows(&mut self) -> ::protobuf::RepeatedField<UpdatedRowOrder> {
         ::std::mem::replace(&mut self.updated_rows, ::protobuf::RepeatedField::new())
         ::std::mem::replace(&mut self.updated_rows, ::protobuf::RepeatedField::new())
     }
     }
 }
 }
@@ -3966,7 +4197,7 @@ impl ::protobuf::Message for GridRowsChangeset {
                 |m: &GridRowsChangeset| { &m.deleted_rows },
                 |m: &GridRowsChangeset| { &m.deleted_rows },
                 |m: &mut GridRowsChangeset| { &mut m.deleted_rows },
                 |m: &mut GridRowsChangeset| { &mut m.deleted_rows },
             ));
             ));
-            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<RowOrder>>(
+            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<UpdatedRowOrder>>(
                 "updated_rows",
                 "updated_rows",
                 |m: &GridRowsChangeset| { &m.updated_rows },
                 |m: &GridRowsChangeset| { &m.updated_rows },
                 |m: &mut GridRowsChangeset| { &mut m.updated_rows },
                 |m: &mut GridRowsChangeset| { &mut m.updated_rows },
@@ -4416,331 +4647,6 @@ impl ::protobuf::reflect::ProtobufValue for Cell {
     }
     }
 }
 }
 
 
-#[derive(PartialEq,Clone,Default)]
-pub struct CellNotificationData {
-    // message fields
-    pub grid_id: ::std::string::String,
-    pub field_id: ::std::string::String,
-    pub row_id: ::std::string::String,
-    // message oneof groups
-    pub one_of_content: ::std::option::Option<CellNotificationData_oneof_one_of_content>,
-    // special fields
-    pub unknown_fields: ::protobuf::UnknownFields,
-    pub cached_size: ::protobuf::CachedSize,
-}
-
-impl<'a> ::std::default::Default for &'a CellNotificationData {
-    fn default() -> &'a CellNotificationData {
-        <CellNotificationData as ::protobuf::Message>::default_instance()
-    }
-}
-
-#[derive(Clone,PartialEq,Debug)]
-pub enum CellNotificationData_oneof_one_of_content {
-    content(::std::string::String),
-}
-
-impl CellNotificationData {
-    pub fn new() -> CellNotificationData {
-        ::std::default::Default::default()
-    }
-
-    // string grid_id = 1;
-
-
-    pub fn get_grid_id(&self) -> &str {
-        &self.grid_id
-    }
-    pub fn clear_grid_id(&mut self) {
-        self.grid_id.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_grid_id(&mut self, v: ::std::string::String) {
-        self.grid_id = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_grid_id(&mut self) -> &mut ::std::string::String {
-        &mut self.grid_id
-    }
-
-    // Take field
-    pub fn take_grid_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.grid_id, ::std::string::String::new())
-    }
-
-    // string field_id = 2;
-
-
-    pub fn get_field_id(&self) -> &str {
-        &self.field_id
-    }
-    pub fn clear_field_id(&mut self) {
-        self.field_id.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_field_id(&mut self, v: ::std::string::String) {
-        self.field_id = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_field_id(&mut self) -> &mut ::std::string::String {
-        &mut self.field_id
-    }
-
-    // Take field
-    pub fn take_field_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.field_id, ::std::string::String::new())
-    }
-
-    // string row_id = 3;
-
-
-    pub fn get_row_id(&self) -> &str {
-        &self.row_id
-    }
-    pub fn clear_row_id(&mut self) {
-        self.row_id.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_row_id(&mut self, v: ::std::string::String) {
-        self.row_id = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_row_id(&mut self) -> &mut ::std::string::String {
-        &mut self.row_id
-    }
-
-    // Take field
-    pub fn take_row_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.row_id, ::std::string::String::new())
-    }
-
-    // string content = 4;
-
-
-    pub fn get_content(&self) -> &str {
-        match self.one_of_content {
-            ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(ref v)) => v,
-            _ => "",
-        }
-    }
-    pub fn clear_content(&mut self) {
-        self.one_of_content = ::std::option::Option::None;
-    }
-
-    pub fn has_content(&self) -> bool {
-        match self.one_of_content {
-            ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(..)) => true,
-            _ => false,
-        }
-    }
-
-    // Param is passed by value, moved
-    pub fn set_content(&mut self, v: ::std::string::String) {
-        self.one_of_content = ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(v))
-    }
-
-    // Mutable pointer to the field.
-    pub fn mut_content(&mut self) -> &mut ::std::string::String {
-        if let ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(_)) = self.one_of_content {
-        } else {
-            self.one_of_content = ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(::std::string::String::new()));
-        }
-        match self.one_of_content {
-            ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(ref mut v)) => v,
-            _ => panic!(),
-        }
-    }
-
-    // Take field
-    pub fn take_content(&mut self) -> ::std::string::String {
-        if self.has_content() {
-            match self.one_of_content.take() {
-                ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(v)) => v,
-                _ => panic!(),
-            }
-        } else {
-            ::std::string::String::new()
-        }
-    }
-}
-
-impl ::protobuf::Message for CellNotificationData {
-    fn is_initialized(&self) -> bool {
-        true
-    }
-
-    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        while !is.eof()? {
-            let (field_number, wire_type) = is.read_tag_unpack()?;
-            match field_number {
-                1 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?;
-                },
-                2 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?;
-                },
-                3 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.row_id)?;
-                },
-                4 => {
-                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
-                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
-                    }
-                    self.one_of_content = ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(is.read_string()?));
-                },
-                _ => {
-                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
-                },
-            };
-        }
-        ::std::result::Result::Ok(())
-    }
-
-    // Compute sizes of nested messages
-    #[allow(unused_variables)]
-    fn compute_size(&self) -> u32 {
-        let mut my_size = 0;
-        if !self.grid_id.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.grid_id);
-        }
-        if !self.field_id.is_empty() {
-            my_size += ::protobuf::rt::string_size(2, &self.field_id);
-        }
-        if !self.row_id.is_empty() {
-            my_size += ::protobuf::rt::string_size(3, &self.row_id);
-        }
-        if let ::std::option::Option::Some(ref v) = self.one_of_content {
-            match v {
-                &CellNotificationData_oneof_one_of_content::content(ref v) => {
-                    my_size += ::protobuf::rt::string_size(4, &v);
-                },
-            };
-        }
-        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
-        self.cached_size.set(my_size);
-        my_size
-    }
-
-    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        if !self.grid_id.is_empty() {
-            os.write_string(1, &self.grid_id)?;
-        }
-        if !self.field_id.is_empty() {
-            os.write_string(2, &self.field_id)?;
-        }
-        if !self.row_id.is_empty() {
-            os.write_string(3, &self.row_id)?;
-        }
-        if let ::std::option::Option::Some(ref v) = self.one_of_content {
-            match v {
-                &CellNotificationData_oneof_one_of_content::content(ref v) => {
-                    os.write_string(4, v)?;
-                },
-            };
-        }
-        os.write_unknown_fields(self.get_unknown_fields())?;
-        ::std::result::Result::Ok(())
-    }
-
-    fn get_cached_size(&self) -> u32 {
-        self.cached_size.get()
-    }
-
-    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
-        &self.unknown_fields
-    }
-
-    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
-        &mut self.unknown_fields
-    }
-
-    fn as_any(&self) -> &dyn (::std::any::Any) {
-        self as &dyn (::std::any::Any)
-    }
-    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
-        self as &mut dyn (::std::any::Any)
-    }
-    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
-        self
-    }
-
-    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
-        Self::descriptor_static()
-    }
-
-    fn new() -> CellNotificationData {
-        CellNotificationData::new()
-    }
-
-    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
-        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
-        descriptor.get(|| {
-            let mut fields = ::std::vec::Vec::new();
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "grid_id",
-                |m: &CellNotificationData| { &m.grid_id },
-                |m: &mut CellNotificationData| { &mut m.grid_id },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "field_id",
-                |m: &CellNotificationData| { &m.field_id },
-                |m: &mut CellNotificationData| { &mut m.field_id },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "row_id",
-                |m: &CellNotificationData| { &m.row_id },
-                |m: &mut CellNotificationData| { &mut m.row_id },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
-                "content",
-                CellNotificationData::has_content,
-                CellNotificationData::get_content,
-            ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CellNotificationData>(
-                "CellNotificationData",
-                fields,
-                file_descriptor_proto()
-            )
-        })
-    }
-
-    fn default_instance() -> &'static CellNotificationData {
-        static instance: ::protobuf::rt::LazyV2<CellNotificationData> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(CellNotificationData::new)
-    }
-}
-
-impl ::protobuf::Clear for CellNotificationData {
-    fn clear(&mut self) {
-        self.grid_id.clear();
-        self.field_id.clear();
-        self.row_id.clear();
-        self.one_of_content = ::std::option::Option::None;
-        self.unknown_fields.clear();
-    }
-}
-
-impl ::std::fmt::Debug for CellNotificationData {
-    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
-        ::protobuf::text_format::fmt(self, f)
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for CellNotificationData {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Message(self)
-    }
-}
-
 #[derive(PartialEq,Clone,Default)]
 #[derive(PartialEq,Clone,Default)]
 pub struct RepeatedCell {
 pub struct RepeatedCell {
     // message fields
     // message fields
@@ -7843,56 +7749,54 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\x12(\n\nrow_orders\x18\
     \x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\x12(\n\nrow_orders\x18\
     \x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\"_\n\rIndexRowOrder\x12&\n\tro\
     \x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\"_\n\rIndexRowOrder\x12&\n\tro\
     w_order\x18\x01\x20\x01(\x0b2\t.RowOrderR\x08rowOrder\x12\x16\n\x05index\
     w_order\x18\x01\x20\x01(\x0b2\t.RowOrderR\x08rowOrder\x12\x16\n\x05index\
-    \x18\x02\x20\x01(\x05H\0R\x05indexB\x0e\n\x0cone_of_index\"\xbf\x01\n\
-    \x11GridRowsChangeset\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07block\
-    Id\x123\n\rinserted_rows\x18\x02\x20\x03(\x0b2\x0e.IndexRowOrderR\x0cins\
-    ertedRows\x12,\n\x0cdeleted_rows\x18\x03\x20\x03(\x0b2\t.RowOrderR\x0bde\
-    letedRows\x12,\n\x0cupdated_rows\x18\x04\x20\x03(\x0b2\t.RowOrderR\x0bup\
-    datedRows\"E\n\tGridBlock\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\x07conte\
-    nt\x18\x02\x20\x01(\tR\x07content\"\x8f\x01\n\x14CellNotificationData\
-    \x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_i\
-    d\x18\x02\x20\x01(\tR\x07fieldId\x12\x15\n\x06row_id\x18\x03\x20\x01(\tR\
-    \x05rowId\x12\x1a\n\x07content\x18\x04\x20\x01(\tH\0R\x07contentB\x10\n\
-    \x0eone_of_content\"+\n\x0cRepeatedCell\x12\x1b\n\x05items\x18\x01\x20\
-    \x03(\x0b2\x05.CellR\x05items\"'\n\x11CreateGridPayload\x12\x12\n\x04nam\
-    e\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\0\
-    R\nstartRowIdB\x15\n\x13one_of_start_row_id\"\xb6\x01\n\x12InsertFieldPa\
-    yload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x1c\n\x05fi\
-    eld\x18\x02\x20\x01(\x0b2\x06.FieldR\x05field\x12(\n\x10type_option_data\
-    \x18\x03\x20\x01(\x0cR\x0etypeOptionData\x12&\n\x0estart_field_id\x18\
-    \x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\x15one_of_start_field_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\x16QueryGridBlocksPayload\x12\x17\n\x07grid_id\x18\
-    \x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orders\x18\x02\x20\x03(\x0b2\
-    \x0f.GridBlockOrderR\x0bblockOrders\"\xa8\x03\n\x15FieldChangesetPayload\
-    \x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x17\n\x07grid_\
-    id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04name\x18\x03\x20\x01(\tH\0\
-    R\x04name\x12\x14\n\x04desc\x18\x04\x20\x01(\tH\x01R\x04desc\x12+\n\nfie\
-    ld_type\x18\x05\x20\x01(\x0e2\n.FieldTypeH\x02R\tfieldType\x12\x18\n\x06\
-    frozen\x18\x06\x20\x01(\x08H\x03R\x06frozen\x12\x20\n\nvisibility\x18\
-    \x07\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05width\x18\x08\x20\x01(\
-    \x05H\x05R\x05width\x12*\n\x10type_option_data\x18\t\x20\x01(\x0cH\x06R\
-    \x0etypeOptionDataB\r\n\x0bone_of_nameB\r\n\x0bone_of_descB\x13\n\x11one\
-    _of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_of_visibilityB\x0e\n\
-    \x0cone_of_widthB\x19\n\x17one_of_type_option_data\"\x9c\x01\n\x0fMoveIt\
-    emPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x17\n\
-    \x07item_id\x18\x02\x20\x01(\tR\x06itemId\x12\x1d\n\nfrom_index\x18\x03\
-    \x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_index\x18\x04\x20\x01(\x05R\
-    \x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\x0e2\r.MoveItemTypeR\x02ty\
-    \"\x7f\n\rCellChangeset\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06grid\
-    Id\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08field_i\
-    d\x18\x03\x20\x01(\tR\x07fieldId\x12\x14\n\x04data\x18\x04\x20\x01(\tH\0\
-    R\x04dataB\r\n\x0bone_of_data**\n\x0cMoveItemType\x12\r\n\tMoveField\x10\
-    \0\x12\x0b\n\x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\x08RichText\x10\
-    \0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\x0c\
-    SingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08Check\
-    box\x10\x05b\x06proto3\
+    \x18\x02\x20\x01(\x05H\0R\x05indexB\x0e\n\x0cone_of_index\"Q\n\x0fUpdate\
+    dRowOrder\x12&\n\trow_order\x18\x01\x20\x01(\x0b2\t.RowOrderR\x08rowOrde\
+    r\x12\x16\n\x03row\x18\x02\x20\x01(\x0b2\x04.RowR\x03row\"\xc6\x01\n\x11\
+    GridRowsChangeset\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\
+    \x123\n\rinserted_rows\x18\x02\x20\x03(\x0b2\x0e.IndexRowOrderR\x0cinser\
+    tedRows\x12,\n\x0cdeleted_rows\x18\x03\x20\x03(\x0b2\t.RowOrderR\x0bdele\
+    tedRows\x123\n\x0cupdated_rows\x18\x04\x20\x03(\x0b2\x10.UpdatedRowOrder\
+    R\x0bupdatedRows\"E\n\tGridBlock\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02\
+    id\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(\x0b2\x05.CellR\x05items\"'\n\x11CreateGridP\
+    ayload\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\nstartRowIdB\x15\n\x13one_of_start_row_id\"\
+    \xb6\x01\n\x12InsertFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\t\
+    R\x06gridId\x12\x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05field\
+    \x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\x12&\
+    \n\x0estart_field_id\x18\x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\x15on\
+    e_of_start_field_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\x16QueryGridBlocksPayload\
+    \x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orde\
+    rs\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrders\"\xa8\x03\n\
+    \x15FieldChangesetPayload\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07f\
+    ieldId\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04n\
+    ame\x18\x03\x20\x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\x04\x20\x01(\tH\
+    \x01R\x04desc\x12+\n\nfield_type\x18\x05\x20\x01(\x0e2\n.FieldTypeH\x02R\
+    \tfieldType\x12\x18\n\x06frozen\x18\x06\x20\x01(\x08H\x03R\x06frozen\x12\
+    \x20\n\nvisibility\x18\x07\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05w\
+    idth\x18\x08\x20\x01(\x05H\x05R\x05width\x12*\n\x10type_option_data\x18\
+    \t\x20\x01(\x0cH\x06R\x0etypeOptionDataB\r\n\x0bone_of_nameB\r\n\x0bone_\
+    of_descB\x13\n\x11one_of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_\
+    of_visibilityB\x0e\n\x0cone_of_widthB\x19\n\x17one_of_type_option_data\"\
+    \x9c\x01\n\x0fMoveItemPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
+    \x06gridId\x12\x17\n\x07item_id\x18\x02\x20\x01(\tR\x06itemId\x12\x1d\n\
+    \nfrom_index\x18\x03\x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_index\x18\
+    \x04\x20\x01(\x05R\x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\x0e2\r.Mo\
+    veItemTypeR\x02ty\"\x7f\n\rCellChangeset\x12\x17\n\x07grid_id\x18\x01\
+    \x20\x01(\tR\x06gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\
+    \x12\x19\n\x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x12\x14\n\x04data\
+    \x18\x04\x20\x01(\tH\0R\x04dataB\r\n\x0bone_of_data**\n\x0cMoveItemType\
+    \x12\r\n\tMoveField\x10\0\x12\x0b\n\x07MoveRow\x10\x01*d\n\tFieldType\
+    \x12\x0c\n\x08RichText\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08Date\
+    Time\x10\x02\x12\x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\
+    \x10\x04\x12\x0c\n\x08Checkbox\x10\x05b\x06proto3\
 ";
 ";
 
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -73,11 +73,15 @@ message IndexRowOrder {
     RowOrder row_order = 1;
     RowOrder row_order = 1;
     oneof one_of_index { int32 index = 2; };
     oneof one_of_index { int32 index = 2; };
 }
 }
+message UpdatedRowOrder {
+    RowOrder row_order = 1;
+    Row row = 2;
+}
 message GridRowsChangeset {
 message GridRowsChangeset {
     string block_id = 1;
     string block_id = 1;
     repeated IndexRowOrder inserted_rows = 2;
     repeated IndexRowOrder inserted_rows = 2;
     repeated RowOrder deleted_rows = 3;
     repeated RowOrder deleted_rows = 3;
-    repeated RowOrder updated_rows = 4;
+    repeated UpdatedRowOrder updated_rows = 4;
 }
 }
 message GridBlock {
 message GridBlock {
     string id = 1;
     string id = 1;
@@ -87,12 +91,6 @@ message Cell {
     string field_id = 1;
     string field_id = 1;
     string content = 2;
     string content = 2;
 }
 }
-message CellNotificationData {
-    string grid_id = 1;
-    string field_id = 2;
-    string row_id = 3;
-    oneof one_of_content { string content = 4; };
-}
 message RepeatedCell {
 message RepeatedCell {
     repeated Cell items = 1;
     repeated Cell items = 1;
 }
 }