Prechádzať zdrojové kódy

chore: different color when create option for single-select or multi-select

appflowy 3 rokov pred
rodič
commit
5ee4b8a2bc
25 zmenil súbory, kde vykonal 281 pridanie a 400 odobranie
  1. 0 10
      frontend/app_flowy/lib/startup/deps_resolver.dart
  2. 2 1
      frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart
  3. 4 4
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_bloc.dart
  4. 4 5
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_bloc.dart
  5. 29 2
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart
  6. 23 21
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart
  7. 6 11
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart
  8. 6 11
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart
  9. 1 1
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart
  10. 11 56
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_entities.pb.dart
  11. 2 12
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_entities.pbjson.dart
  12. 1 3
      frontend/rust-lib/flowy-grid/Flowy.toml
  13. 15 23
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  14. 1 1
      frontend/rust-lib/flowy-grid/src/event_map.rs
  15. 32 191
      frontend/rust-lib/flowy-grid/src/protobuf/model/cell_entities.rs
  16. 2 4
      frontend/rust-lib/flowy-grid/src/protobuf/proto/cell_entities.proto
  17. 0 3
      frontend/rust-lib/flowy-grid/src/services/cell/mod.rs
  18. 13 10
      frontend/rust-lib/flowy-grid/src/services/entities/cell_entities.rs
  19. 0 0
      frontend/rust-lib/flowy-grid/src/services/entities/field_entities.rs
  20. 7 0
      frontend/rust-lib/flowy-grid/src/services/entities/mod.rs
  21. 31 0
      frontend/rust-lib/flowy-grid/src/services/entities/row_entities.rs
  22. 0 2
      frontend/rust-lib/flowy-grid/src/services/field/mod.rs
  23. 58 2
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs
  24. 32 26
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  25. 1 1
      frontend/rust-lib/flowy-grid/src/services/mod.rs

+ 0 - 10
frontend/app_flowy/lib/startup/deps_resolver.dart

@@ -3,7 +3,6 @@ import 'package:app_flowy/user/application/user_listener.dart';
 import 'package:app_flowy/user/application/user_service.dart';
 import 'package:app_flowy/workspace/application/app/prelude.dart';
 import 'package:app_flowy/workspace/application/doc/prelude.dart';
-import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
 import 'package:app_flowy/workspace/application/trash/prelude.dart';
 import 'package:app_flowy/workspace/application/workspace/prelude.dart';
@@ -19,7 +18,6 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
 import 'package:get_it/get_it.dart';
 
@@ -206,14 +204,6 @@ void _resolveGridDeps(GetIt getIt) {
     (context, _) => FieldSwitcherBloc(context),
   );
 
-  getIt.registerFactoryParam<SingleSelectTypeOptionBloc, SingleSelectTypeOption, String>(
-    (typeOption, fieldId) => SingleSelectTypeOptionBloc(typeOption, fieldId),
-  );
-
-  getIt.registerFactoryParam<MultiSelectTypeOptionBloc, MultiSelectTypeOption, String>(
-    (typeOption, fieldId) => MultiSelectTypeOptionBloc(typeOption, fieldId),
-  );
-
   getIt.registerFactoryParam<DateTypeOptionBloc, DateTypeOption, void>(
     (typeOption, _) => DateTypeOptionBloc(typeOption: typeOption),
   );

+ 2 - 1
frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart

@@ -1,3 +1,4 @@
+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:flowy_sdk/dispatch/dispatch.dart';
@@ -13,7 +14,7 @@ class SelectOptionService {
     required String rowId,
     required String name,
   }) {
-    return GridEventNewSelectOption(SelectOptionName.create()..name = name).send().then(
+    return TypeOptionService(gridId: gridId, fieldId: fieldId).newOption(name: name).then(
       (result) {
         return result.fold(
           (option) {

+ 4 - 4
frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_bloc.dart

@@ -11,14 +11,14 @@ part 'multi_select_bloc.freezed.dart';
 class MultiSelectTypeOptionBloc extends Bloc<MultiSelectTypeOptionEvent, MultiSelectTypeOptionState> {
   final TypeOptionService service;
 
-  MultiSelectTypeOptionBloc(MultiSelectTypeOption typeOption, String fieldId)
-      : service = TypeOptionService(fieldId: fieldId),
-        super(MultiSelectTypeOptionState.initial(typeOption)) {
+  MultiSelectTypeOptionBloc(TypeOptionContext typeOptionContext)
+      : service = TypeOptionService(gridId: typeOptionContext.gridId, fieldId: typeOptionContext.field.id),
+        super(MultiSelectTypeOptionState.initial(MultiSelectTypeOption.fromBuffer(typeOptionContext.data))) {
     on<MultiSelectTypeOptionEvent>(
       (event, emit) async {
         await event.map(
           createOption: (_CreateOption value) async {
-            final result = await service.newOption(value.optionName);
+            final result = await service.newOption(name: value.optionName);
             result.fold(
               (option) {
                 emit(state.copyWith(typeOption: _insertOption(option)));

+ 4 - 5
frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_bloc.dart

@@ -12,17 +12,16 @@ class SingleSelectTypeOptionBloc extends Bloc<SingleSelectTypeOptionEvent, Singl
   final TypeOptionService service;
 
   SingleSelectTypeOptionBloc(
-    SingleSelectTypeOption typeOption,
-    String fieldId,
-  )   : service = TypeOptionService(fieldId: fieldId),
+    TypeOptionContext typeOptionContext,
+  )   : service = TypeOptionService(gridId: typeOptionContext.gridId, fieldId: typeOptionContext.field.id),
         super(
-          SingleSelectTypeOptionState.initial(typeOption),
+          SingleSelectTypeOptionState.initial(SingleSelectTypeOption.fromBuffer(typeOptionContext.data)),
         ) {
     on<SingleSelectTypeOptionEvent>(
       (event, emit) async {
         await event.map(
           createOption: (_CreateOption value) async {
-            final result = await service.newOption(value.optionName);
+            final result = await service.newOption(name: value.optionName);
             result.fold(
               (option) {
                 emit(state.copyWith(typeOption: _insertOption(option)));

+ 29 - 2
frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart

@@ -1,17 +1,44 @@
+import 'dart:typed_data';
+
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 
 class TypeOptionService {
+  final String gridId;
   final String fieldId;
+
   TypeOptionService({
+    required this.gridId,
     required this.fieldId,
   });
 
-  Future<Either<SelectOption, FlowyError>> newOption(String name, {bool selected = false}) {
-    final payload = SelectOptionName.create()..name = name;
+  Future<Either<SelectOption, FlowyError>> newOption({
+    required String name,
+  }) {
+    final fieldIdentifier = FieldIdentifierPayload.create()
+      ..gridId = gridId
+      ..fieldId = fieldId;
+
+    final payload = CreateSelectOptionPayload.create()
+      ..optionName = name
+      ..fieldIdentifier = fieldIdentifier;
+
     return GridEventNewSelectOption(payload).send();
   }
 }
+
+class TypeOptionContext {
+  final String gridId;
+  final Field field;
+  final Uint8List data;
+  const TypeOptionContext({
+    required this.gridId,
+    required this.field,
+    required this.data,
+  });
+}

+ 23 - 21
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart

@@ -1,7 +1,7 @@
 import 'dart:typed_data';
 
-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/date.dart';
+import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
+import 'package:dartz/dartz.dart' show Either;
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -14,11 +14,14 @@ import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pbserver.dart
 import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
+
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart';
+
 import 'field_type_extension.dart';
-import 'package:dartz/dartz.dart' show Either;
 import 'type_option/multi_select.dart';
 import 'type_option/number.dart';
 import 'type_option/single_select.dart';
@@ -58,11 +61,7 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
         },
         builder: (context, state) {
           List<Widget> children = [_switchFieldTypeButton(context, state.field)];
-          final typeOptionWidget = _typeOptionWidget(
-            context: context,
-            field: state.field,
-            data: state.typeOptionData,
-          );
+          final typeOptionWidget = _typeOptionWidget(context: context, state: state);
 
           if (typeOptionWidget != null) {
             children.add(typeOptionWidget);
@@ -111,8 +110,7 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
 
   Widget? _typeOptionWidget({
     required BuildContext context,
-    required Field field,
-    required TypeOptionData data,
+    required FieldSwitchState state,
   }) {
     final overlayDelegate = TypeOptionOverlayDelegate(
       showOverlay: _showOverlay,
@@ -123,9 +121,14 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
       context.read<FieldSwitcherBloc>().add(FieldSwitchEvent.didUpdateTypeOptionData(data));
     });
 
+    final typeOptionContext = TypeOptionContext(
+      gridId: state.gridId,
+      field: state.field,
+      data: state.typeOptionData,
+    );
+
     final builder = _makeTypeOptionBuild(
-      field: field,
-      data: data,
+      typeOptionContext: typeOptionContext,
       overlayDelegate: overlayDelegate,
       dataDelegate: dataDelegate,
     );
@@ -165,24 +168,23 @@ abstract class TypeOptionBuilder {
 }
 
 TypeOptionBuilder _makeTypeOptionBuild({
-  required Field field,
-  required TypeOptionData data,
+  required TypeOptionContext typeOptionContext,
   required TypeOptionOverlayDelegate overlayDelegate,
   required TypeOptionDataDelegate dataDelegate,
 }) {
-  switch (field.fieldType) {
+  switch (typeOptionContext.field.fieldType) {
     case FieldType.Checkbox:
-      return CheckboxTypeOptionBuilder(data);
+      return CheckboxTypeOptionBuilder(typeOptionContext.data);
     case FieldType.DateTime:
-      return DateTypeOptionBuilder(data, overlayDelegate, dataDelegate);
+      return DateTypeOptionBuilder(typeOptionContext.data, overlayDelegate, dataDelegate);
     case FieldType.SingleSelect:
-      return SingleSelectTypeOptionBuilder(field.id, data, overlayDelegate, dataDelegate);
+      return SingleSelectTypeOptionBuilder(typeOptionContext, overlayDelegate, dataDelegate);
     case FieldType.MultiSelect:
-      return MultiSelectTypeOptionBuilder(field.id, data, overlayDelegate, dataDelegate);
+      return MultiSelectTypeOptionBuilder(typeOptionContext, overlayDelegate, dataDelegate);
     case FieldType.Number:
-      return NumberTypeOptionBuilder(data, overlayDelegate, dataDelegate);
+      return NumberTypeOptionBuilder(typeOptionContext.data, overlayDelegate, dataDelegate);
     case FieldType.RichText:
-      return RichTextTypeOptionBuilder(data);
+      return RichTextTypeOptionBuilder(typeOptionContext.data);
 
     default:
       throw UnimplementedError;

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

@@ -1,7 +1,6 @@
-import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_bloc.dart';
+import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
@@ -11,13 +10,11 @@ class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
   final MultiSelectTypeOptionWidget _widget;
 
   MultiSelectTypeOptionBuilder(
-    String fieldId,
-    TypeOptionData typeOptionData,
+    TypeOptionContext typeOptionContext,
     TypeOptionOverlayDelegate overlayDelegate,
     TypeOptionDataDelegate dataDelegate,
   ) : _widget = MultiSelectTypeOptionWidget(
-          fieldId: fieldId,
-          typeOption: MultiSelectTypeOption.fromBuffer(typeOptionData),
+          typeOptionContext: typeOptionContext,
           overlayDelegate: overlayDelegate,
           dataDelegate: dataDelegate,
         );
@@ -27,13 +24,11 @@ class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
 }
 
 class MultiSelectTypeOptionWidget extends TypeOptionWidget {
-  final String fieldId;
-  final MultiSelectTypeOption typeOption;
+  final TypeOptionContext typeOptionContext;
   final TypeOptionOverlayDelegate overlayDelegate;
   final TypeOptionDataDelegate dataDelegate;
   const MultiSelectTypeOptionWidget({
-    required this.fieldId,
-    required this.typeOption,
+    required this.typeOptionContext,
     required this.overlayDelegate,
     required this.dataDelegate,
     Key? key,
@@ -42,7 +37,7 @@ class MultiSelectTypeOptionWidget extends TypeOptionWidget {
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => getIt<MultiSelectTypeOptionBloc>(param1: typeOption, param2: fieldId),
+      create: (context) => MultiSelectTypeOptionBloc(typeOptionContext),
       child: BlocConsumer<MultiSelectTypeOptionBloc, MultiSelectTypeOptionState>(
         listener: (context, state) {
           dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer());

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

@@ -1,7 +1,6 @@
-import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/field/type_option/single_select_bloc.dart';
+import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'field_option_pannel.dart';
@@ -10,13 +9,11 @@ class SingleSelectTypeOptionBuilder extends TypeOptionBuilder {
   final SingleSelectTypeOptionWidget _widget;
 
   SingleSelectTypeOptionBuilder(
-    String fieldId,
-    TypeOptionData typeOptionData,
+    TypeOptionContext typeOptionContext,
     TypeOptionOverlayDelegate overlayDelegate,
     TypeOptionDataDelegate dataDelegate,
   ) : _widget = SingleSelectTypeOptionWidget(
-          fieldId: fieldId,
-          typeOption: SingleSelectTypeOption.fromBuffer(typeOptionData),
+          typeOptionContext: typeOptionContext,
           dataDelegate: dataDelegate,
           overlayDelegate: overlayDelegate,
         );
@@ -26,13 +23,11 @@ class SingleSelectTypeOptionBuilder extends TypeOptionBuilder {
 }
 
 class SingleSelectTypeOptionWidget extends TypeOptionWidget {
-  final String fieldId;
-  final SingleSelectTypeOption typeOption;
+  final TypeOptionContext typeOptionContext;
   final TypeOptionOverlayDelegate overlayDelegate;
   final TypeOptionDataDelegate dataDelegate;
   const SingleSelectTypeOptionWidget({
-    required this.fieldId,
-    required this.typeOption,
+    required this.typeOptionContext,
     required this.dataDelegate,
     required this.overlayDelegate,
     Key? key,
@@ -41,7 +36,7 @@ class SingleSelectTypeOptionWidget extends TypeOptionWidget {
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => getIt<SingleSelectTypeOptionBloc>(param1: typeOption, param2: fieldId),
+      create: (context) => SingleSelectTypeOptionBloc(typeOptionContext),
       child: BlocConsumer<SingleSelectTypeOptionBloc, SingleSelectTypeOptionState>(
         listener: (context, state) {
           dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer());

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

@@ -172,7 +172,7 @@ class GridEventMoveItem {
 }
 
 class GridEventNewSelectOption {
-     SelectOptionName request;
+     CreateSelectOptionPayload request;
      GridEventNewSelectOption(this.request);
 
     Future<Either<SelectOption, FlowyError>> send() {

+ 11 - 56
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_entities.pb.dart

@@ -9,21 +9,23 @@ import 'dart:core' as $core;
 
 import 'package:protobuf/protobuf.dart' as $pb;
 
+import 'field_entities.pb.dart' as $0;
+
 class CreateSelectOptionPayload extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CreateSelectOptionPayload', createEmptyInstance: create)
-    ..aOM<CellIdentifierPayload>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'cellIdentifier', subBuilder: CellIdentifierPayload.create)
+    ..aOM<$0.FieldIdentifierPayload>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldIdentifier', subBuilder: $0.FieldIdentifierPayload.create)
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'optionName')
     ..hasRequiredFields = false
   ;
 
   CreateSelectOptionPayload._() : super();
   factory CreateSelectOptionPayload({
-    CellIdentifierPayload? cellIdentifier,
+    $0.FieldIdentifierPayload? fieldIdentifier,
     $core.String? optionName,
   }) {
     final _result = create();
-    if (cellIdentifier != null) {
-      _result.cellIdentifier = cellIdentifier;
+    if (fieldIdentifier != null) {
+      _result.fieldIdentifier = fieldIdentifier;
     }
     if (optionName != null) {
       _result.optionName = optionName;
@@ -52,15 +54,15 @@ class CreateSelectOptionPayload extends $pb.GeneratedMessage {
   static CreateSelectOptionPayload? _defaultInstance;
 
   @$pb.TagNumber(1)
-  CellIdentifierPayload get cellIdentifier => $_getN(0);
+  $0.FieldIdentifierPayload get fieldIdentifier => $_getN(0);
   @$pb.TagNumber(1)
-  set cellIdentifier(CellIdentifierPayload v) { setField(1, v); }
+  set fieldIdentifier($0.FieldIdentifierPayload v) { setField(1, v); }
   @$pb.TagNumber(1)
-  $core.bool hasCellIdentifier() => $_has(0);
+  $core.bool hasFieldIdentifier() => $_has(0);
   @$pb.TagNumber(1)
-  void clearCellIdentifier() => clearField(1);
+  void clearFieldIdentifier() => clearField(1);
   @$pb.TagNumber(1)
-  CellIdentifierPayload ensureCellIdentifier() => $_ensure(0);
+  $0.FieldIdentifierPayload ensureFieldIdentifier() => $_ensure(0);
 
   @$pb.TagNumber(2)
   $core.String get optionName => $_getSZ(1);
@@ -147,50 +149,3 @@ class CellIdentifierPayload extends $pb.GeneratedMessage {
   void clearRowId() => clearField(3);
 }
 
-class SelectOptionName extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SelectOptionName', createEmptyInstance: create)
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
-    ..hasRequiredFields = false
-  ;
-
-  SelectOptionName._() : super();
-  factory SelectOptionName({
-    $core.String? name,
-  }) {
-    final _result = create();
-    if (name != null) {
-      _result.name = name;
-    }
-    return _result;
-  }
-  factory SelectOptionName.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory SelectOptionName.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')
-  SelectOptionName clone() => SelectOptionName()..mergeFromMessage(this);
-  @$core.Deprecated(
-  'Using this can add significant overhead to your binary. '
-  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
-  'Will be removed in next major version')
-  SelectOptionName copyWith(void Function(SelectOptionName) updates) => super.copyWith((message) => updates(message as SelectOptionName)) as SelectOptionName; // ignore: deprecated_member_use
-  $pb.BuilderInfo get info_ => _i;
-  @$core.pragma('dart2js:noInline')
-  static SelectOptionName create() => SelectOptionName._();
-  SelectOptionName createEmptyInstance() => create();
-  static $pb.PbList<SelectOptionName> createRepeated() => $pb.PbList<SelectOptionName>();
-  @$core.pragma('dart2js:noInline')
-  static SelectOptionName getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SelectOptionName>(create);
-  static SelectOptionName? _defaultInstance;
-
-  @$pb.TagNumber(1)
-  $core.String get name => $_getSZ(0);
-  @$pb.TagNumber(1)
-  set name($core.String v) { $_setString(0, v); }
-  @$pb.TagNumber(1)
-  $core.bool hasName() => $_has(0);
-  @$pb.TagNumber(1)
-  void clearName() => clearField(1);
-}
-

+ 2 - 12
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/cell_entities.pbjson.dart

@@ -12,13 +12,13 @@ import 'dart:typed_data' as $typed_data;
 const CreateSelectOptionPayload$json = const {
   '1': 'CreateSelectOptionPayload',
   '2': const [
-    const {'1': 'cell_identifier', '3': 1, '4': 1, '5': 11, '6': '.CellIdentifierPayload', '10': 'cellIdentifier'},
+    const {'1': 'field_identifier', '3': 1, '4': 1, '5': 11, '6': '.FieldIdentifierPayload', '10': 'fieldIdentifier'},
     const {'1': 'option_name', '3': 2, '4': 1, '5': 9, '10': 'optionName'},
   ],
 };
 
 /// Descriptor for `CreateSelectOptionPayload`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List createSelectOptionPayloadDescriptor = $convert.base64Decode('ChlDcmVhdGVTZWxlY3RPcHRpb25QYXlsb2FkEj8KD2NlbGxfaWRlbnRpZmllchgBIAEoCzIWLkNlbGxJZGVudGlmaWVyUGF5bG9hZFIOY2VsbElkZW50aWZpZXISHwoLb3B0aW9uX25hbWUYAiABKAlSCm9wdGlvbk5hbWU=');
+final $typed_data.Uint8List createSelectOptionPayloadDescriptor = $convert.base64Decode('ChlDcmVhdGVTZWxlY3RPcHRpb25QYXlsb2FkEkIKEGZpZWxkX2lkZW50aWZpZXIYASABKAsyFy5GaWVsZElkZW50aWZpZXJQYXlsb2FkUg9maWVsZElkZW50aWZpZXISHwoLb3B0aW9uX25hbWUYAiABKAlSCm9wdGlvbk5hbWU=');
 @$core.Deprecated('Use cellIdentifierPayloadDescriptor instead')
 const CellIdentifierPayload$json = const {
   '1': 'CellIdentifierPayload',
@@ -31,13 +31,3 @@ const CellIdentifierPayload$json = const {
 
 /// Descriptor for `CellIdentifierPayload`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List cellIdentifierPayloadDescriptor = $convert.base64Decode('ChVDZWxsSWRlbnRpZmllclBheWxvYWQSFwoHZ3JpZF9pZBgBIAEoCVIGZ3JpZElkEhkKCGZpZWxkX2lkGAIgASgJUgdmaWVsZElkEhUKBnJvd19pZBgDIAEoCVIFcm93SWQ=');
-@$core.Deprecated('Use selectOptionNameDescriptor instead')
-const SelectOptionName$json = const {
-  '1': 'SelectOptionName',
-  '2': const [
-    const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
-  ],
-};
-
-/// Descriptor for `SelectOptionName`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List selectOptionNameDescriptor = $convert.base64Decode('ChBTZWxlY3RPcHRpb25OYW1lEhIKBG5hbWUYASABKAlSBG5hbWU=');

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

@@ -2,9 +2,7 @@
 proto_crates = [
     "src/event_map.rs",
     "src/services/field/type_options",
-    "src/services/field/field_entities.rs",
-    "src/services/cell/cell_entities.rs",
-    "src/services/row/row_entities.rs",
+    "src/services/entities",
     "src/dart_notification.rs"
 ]
 event_files = ["src/event_map.rs"]

+ 15 - 23
frontend/rust-lib/flowy-grid/src/event_handler.rs

@@ -1,10 +1,8 @@
 use crate::manager::GridManager;
-use crate::services::cell::cell_entities::*;
-use crate::services::field::field_entities::*;
+use crate::services::entities::*;
 use crate::services::field::type_options::*;
 use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_json_str};
 use crate::services::grid_editor::ClientGridEditor;
-use crate::services::row::row_entities::*;
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::*;
 use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
@@ -248,9 +246,20 @@ pub(crate) async fn update_cell_handler(
 }
 
 #[tracing::instrument(level = "debug", skip_all, err)]
-pub(crate) async fn new_select_option_handler(data: Data<SelectOptionName>) -> DataResult<SelectOption, FlowyError> {
-    let params = data.into_inner();
-    data_result(SelectOption::new(&params.name))
+pub(crate) async fn new_select_option_handler(
+    data: Data<CreateSelectOptionPayload>,
+    manager: AppData<Arc<GridManager>>,
+) -> DataResult<SelectOption, FlowyError> {
+    let params: CreateSelectOptionParams = data.into_inner().try_into()?;
+    let editor = manager.get_grid_editor(&params.grid_id)?;
+    match editor.get_field_meta(&params.field_id).await {
+        None => Err(ErrorCode::InvalidData.into()),
+        Some(field_meta) => {
+            let type_option = select_option_operation(&field_meta)?;
+            let select_option = type_option.create_option(&params.option_name);
+            data_result(select_option)
+        }
+    }
 }
 
 #[tracing::instrument(level = "debug", skip_all, err)]
@@ -337,20 +346,3 @@ pub(crate) async fn update_cell_select_option_handler(
     let _ = editor.update_cell(changeset).await?;
     Ok(())
 }
-
-fn select_option_operation(field_meta: &FieldMeta) -> FlowyResult<Box<dyn SelectOptionOperation>> {
-    match &field_meta.field_type {
-        FieldType::SingleSelect => {
-            let type_option = SingleSelectTypeOption::from(field_meta);
-            Ok(Box::new(type_option))
-        }
-        FieldType::MultiSelect => {
-            let type_option = MultiSelectTypeOption::from(field_meta);
-            Ok(Box::new(type_option))
-        }
-        ty => {
-            tracing::error!("Unsupported field type: {:?} for this handler", ty);
-            Err(ErrorCode::FieldInvalidOperation.into())
-        }
-    }
-}

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

@@ -69,7 +69,7 @@ pub enum GridEvent {
     #[event(input = "MoveItemPayload")]
     MoveItem = 17,
 
-    #[event(input = "SelectOptionName", output = "SelectOption")]
+    #[event(input = "CreateSelectOptionPayload", output = "SelectOption")]
     NewSelectOption = 30,
 
     #[event(input = "CellIdentifierPayload", output = "SelectOptionContext")]

+ 32 - 191
frontend/rust-lib/flowy-grid/src/protobuf/model/cell_entities.rs

@@ -26,7 +26,7 @@
 #[derive(PartialEq,Clone,Default)]
 pub struct CreateSelectOptionPayload {
     // message fields
-    pub cell_identifier: ::protobuf::SingularPtrField<CellIdentifierPayload>,
+    pub field_identifier: ::protobuf::SingularPtrField<super::field_entities::FieldIdentifierPayload>,
     pub option_name: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
@@ -44,37 +44,37 @@ impl CreateSelectOptionPayload {
         ::std::default::Default::default()
     }
 
-    // .CellIdentifierPayload cell_identifier = 1;
+    // .FieldIdentifierPayload field_identifier = 1;
 
 
-    pub fn get_cell_identifier(&self) -> &CellIdentifierPayload {
-        self.cell_identifier.as_ref().unwrap_or_else(|| <CellIdentifierPayload as ::protobuf::Message>::default_instance())
+    pub fn get_field_identifier(&self) -> &super::field_entities::FieldIdentifierPayload {
+        self.field_identifier.as_ref().unwrap_or_else(|| <super::field_entities::FieldIdentifierPayload as ::protobuf::Message>::default_instance())
     }
-    pub fn clear_cell_identifier(&mut self) {
-        self.cell_identifier.clear();
+    pub fn clear_field_identifier(&mut self) {
+        self.field_identifier.clear();
     }
 
-    pub fn has_cell_identifier(&self) -> bool {
-        self.cell_identifier.is_some()
+    pub fn has_field_identifier(&self) -> bool {
+        self.field_identifier.is_some()
     }
 
     // Param is passed by value, moved
-    pub fn set_cell_identifier(&mut self, v: CellIdentifierPayload) {
-        self.cell_identifier = ::protobuf::SingularPtrField::some(v);
+    pub fn set_field_identifier(&mut self, v: super::field_entities::FieldIdentifierPayload) {
+        self.field_identifier = ::protobuf::SingularPtrField::some(v);
     }
 
     // Mutable pointer to the field.
     // If field is not initialized, it is initialized with default value first.
-    pub fn mut_cell_identifier(&mut self) -> &mut CellIdentifierPayload {
-        if self.cell_identifier.is_none() {
-            self.cell_identifier.set_default();
+    pub fn mut_field_identifier(&mut self) -> &mut super::field_entities::FieldIdentifierPayload {
+        if self.field_identifier.is_none() {
+            self.field_identifier.set_default();
         }
-        self.cell_identifier.as_mut().unwrap()
+        self.field_identifier.as_mut().unwrap()
     }
 
     // Take field
-    pub fn take_cell_identifier(&mut self) -> CellIdentifierPayload {
-        self.cell_identifier.take().unwrap_or_else(|| CellIdentifierPayload::new())
+    pub fn take_field_identifier(&mut self) -> super::field_entities::FieldIdentifierPayload {
+        self.field_identifier.take().unwrap_or_else(|| super::field_entities::FieldIdentifierPayload::new())
     }
 
     // string option_name = 2;
@@ -106,7 +106,7 @@ impl CreateSelectOptionPayload {
 
 impl ::protobuf::Message for CreateSelectOptionPayload {
     fn is_initialized(&self) -> bool {
-        for v in &self.cell_identifier {
+        for v in &self.field_identifier {
             if !v.is_initialized() {
                 return false;
             }
@@ -119,7 +119,7 @@ impl ::protobuf::Message for CreateSelectOptionPayload {
             let (field_number, wire_type) = is.read_tag_unpack()?;
             match field_number {
                 1 => {
-                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.cell_identifier)?;
+                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.field_identifier)?;
                 },
                 2 => {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.option_name)?;
@@ -136,7 +136,7 @@ impl ::protobuf::Message for CreateSelectOptionPayload {
     #[allow(unused_variables)]
     fn compute_size(&self) -> u32 {
         let mut my_size = 0;
-        if let Some(ref v) = self.cell_identifier.as_ref() {
+        if let Some(ref v) = self.field_identifier.as_ref() {
             let len = v.compute_size();
             my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
         }
@@ -149,7 +149,7 @@ impl ::protobuf::Message for CreateSelectOptionPayload {
     }
 
     fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        if let Some(ref v) = self.cell_identifier.as_ref() {
+        if let Some(ref v) = self.field_identifier.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)?;
@@ -195,10 +195,10 @@ impl ::protobuf::Message for CreateSelectOptionPayload {
         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<CellIdentifierPayload>>(
-                "cell_identifier",
-                |m: &CreateSelectOptionPayload| { &m.cell_identifier },
-                |m: &mut CreateSelectOptionPayload| { &mut m.cell_identifier },
+            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<super::field_entities::FieldIdentifierPayload>>(
+                "field_identifier",
+                |m: &CreateSelectOptionPayload| { &m.field_identifier },
+                |m: &mut CreateSelectOptionPayload| { &mut m.field_identifier },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "option_name",
@@ -221,7 +221,7 @@ impl ::protobuf::Message for CreateSelectOptionPayload {
 
 impl ::protobuf::Clear for CreateSelectOptionPayload {
     fn clear(&mut self) {
-        self.cell_identifier.clear();
+        self.field_identifier.clear();
         self.option_name.clear();
         self.unknown_fields.clear();
     }
@@ -482,173 +482,14 @@ impl ::protobuf::reflect::ProtobufValue for CellIdentifierPayload {
     }
 }
 
-#[derive(PartialEq,Clone,Default)]
-pub struct SelectOptionName {
-    // message fields
-    pub name: ::std::string::String,
-    // special fields
-    pub unknown_fields: ::protobuf::UnknownFields,
-    pub cached_size: ::protobuf::CachedSize,
-}
-
-impl<'a> ::std::default::Default for &'a SelectOptionName {
-    fn default() -> &'a SelectOptionName {
-        <SelectOptionName as ::protobuf::Message>::default_instance()
-    }
-}
-
-impl SelectOptionName {
-    pub fn new() -> SelectOptionName {
-        ::std::default::Default::default()
-    }
-
-    // string name = 1;
-
-
-    pub fn get_name(&self) -> &str {
-        &self.name
-    }
-    pub fn clear_name(&mut self) {
-        self.name.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_name(&mut self, v: ::std::string::String) {
-        self.name = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_name(&mut self) -> &mut ::std::string::String {
-        &mut self.name
-    }
-
-    // Take field
-    pub fn take_name(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.name, ::std::string::String::new())
-    }
-}
-
-impl ::protobuf::Message for SelectOptionName {
-    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.name)?;
-                },
-                _ => {
-                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
-                },
-            };
-        }
-        ::std::result::Result::Ok(())
-    }
-
-    // Compute sizes of nested messages
-    #[allow(unused_variables)]
-    fn compute_size(&self) -> u32 {
-        let mut my_size = 0;
-        if !self.name.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.name);
-        }
-        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
-        self.cached_size.set(my_size);
-        my_size
-    }
-
-    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        if !self.name.is_empty() {
-            os.write_string(1, &self.name)?;
-        }
-        os.write_unknown_fields(self.get_unknown_fields())?;
-        ::std::result::Result::Ok(())
-    }
-
-    fn get_cached_size(&self) -> u32 {
-        self.cached_size.get()
-    }
-
-    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
-        &self.unknown_fields
-    }
-
-    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
-        &mut self.unknown_fields
-    }
-
-    fn as_any(&self) -> &dyn (::std::any::Any) {
-        self as &dyn (::std::any::Any)
-    }
-    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
-        self as &mut dyn (::std::any::Any)
-    }
-    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
-        self
-    }
-
-    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
-        Self::descriptor_static()
-    }
-
-    fn new() -> SelectOptionName {
-        SelectOptionName::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>(
-                "name",
-                |m: &SelectOptionName| { &m.name },
-                |m: &mut SelectOptionName| { &mut m.name },
-            ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SelectOptionName>(
-                "SelectOptionName",
-                fields,
-                file_descriptor_proto()
-            )
-        })
-    }
-
-    fn default_instance() -> &'static SelectOptionName {
-        static instance: ::protobuf::rt::LazyV2<SelectOptionName> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(SelectOptionName::new)
-    }
-}
-
-impl ::protobuf::Clear for SelectOptionName {
-    fn clear(&mut self) {
-        self.name.clear();
-        self.unknown_fields.clear();
-    }
-}
-
-impl ::std::fmt::Debug for SelectOptionName {
-    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
-        ::protobuf::text_format::fmt(self, f)
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for SelectOptionName {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Message(self)
-    }
-}
-
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x13cell_entities.proto\"}\n\x19CreateSelectOptionPayload\x12?\n\x0fce\
-    ll_identifier\x18\x01\x20\x01(\x0b2\x16.CellIdentifierPayloadR\x0ecellId\
-    entifier\x12\x1f\n\x0boption_name\x18\x02\x20\x01(\tR\noptionName\"b\n\
-    \x15CellIdentifierPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gr\
-    idId\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\x07fieldId\x12\x15\n\x06r\
-    ow_id\x18\x03\x20\x01(\tR\x05rowId\"&\n\x10SelectOptionName\x12\x12\n\
-    \x04name\x18\x01\x20\x01(\tR\x04nameb\x06proto3\
+    \n\x13cell_entities.proto\x1a\x14field_entities.proto\"\x80\x01\n\x19Cre\
+    ateSelectOptionPayload\x12B\n\x10field_identifier\x18\x01\x20\x01(\x0b2\
+    \x17.FieldIdentifierPayloadR\x0ffieldIdentifier\x12\x1f\n\x0boption_name\
+    \x18\x02\x20\x01(\tR\noptionName\"b\n\x15CellIdentifierPayload\x12\x17\n\
+    \x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_id\x18\x02\
+    \x20\x01(\tR\x07fieldId\x12\x15\n\x06row_id\x18\x03\x20\x01(\tR\x05rowId\
+    b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 2 - 4
frontend/rust-lib/flowy-grid/src/protobuf/proto/cell_entities.proto

@@ -1,7 +1,8 @@
 syntax = "proto3";
+import "field_entities.proto";
 
 message CreateSelectOptionPayload {
-    CellIdentifierPayload cell_identifier = 1;
+    FieldIdentifierPayload field_identifier = 1;
     string option_name = 2;
 }
 message CellIdentifierPayload {
@@ -9,6 +10,3 @@ message CellIdentifierPayload {
     string field_id = 2;
     string row_id = 3;
 }
-message SelectOptionName {
-    string name = 1;
-}

+ 0 - 3
frontend/rust-lib/flowy-grid/src/services/cell/mod.rs

@@ -1,3 +0,0 @@
-pub(crate) mod cell_entities;
-
-pub use cell_entities::*;

+ 13 - 10
frontend/rust-lib/flowy-grid/src/services/cell/cell_entities.rs → frontend/rust-lib/flowy-grid/src/services/entities/cell_entities.rs

@@ -1,3 +1,4 @@
+use crate::services::entities::{FieldIdentifier, FieldIdentifierPayload};
 use flowy_derive::ProtoBuf;
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::parser::NotEmptyStr;
@@ -5,25 +6,33 @@ use flowy_grid_data_model::parser::NotEmptyStr;
 #[derive(ProtoBuf, Default)]
 pub struct CreateSelectOptionPayload {
     #[pb(index = 1)]
-    pub cell_identifier: CellIdentifierPayload,
+    pub field_identifier: FieldIdentifierPayload,
 
     #[pb(index = 2)]
     pub option_name: String,
 }
 
 pub struct CreateSelectOptionParams {
-    pub cell_identifier: CellIdentifier,
+    pub field_identifier: FieldIdentifier,
     pub option_name: String,
 }
 
+impl std::ops::Deref for CreateSelectOptionParams {
+    type Target = FieldIdentifier;
+
+    fn deref(&self) -> &Self::Target {
+        &self.field_identifier
+    }
+}
+
 impl TryInto<CreateSelectOptionParams> for CreateSelectOptionPayload {
     type Error = ErrorCode;
 
     fn try_into(self) -> Result<CreateSelectOptionParams, Self::Error> {
         let option_name = NotEmptyStr::parse(self.option_name).map_err(|_| ErrorCode::SelectOptionNameIsEmpty)?;
-        let cell_identifier = self.cell_identifier.try_into()?;
+        let field_identifier = self.field_identifier.try_into()?;
         Ok(CreateSelectOptionParams {
-            cell_identifier,
+            field_identifier,
             option_name: option_name.0,
         })
     }
@@ -61,9 +70,3 @@ impl TryInto<CellIdentifier> for CellIdentifierPayload {
         })
     }
 }
-
-#[derive(ProtoBuf, Default)]
-pub struct SelectOptionName {
-    #[pb(index = 1)]
-    pub name: String,
-}

+ 0 - 0
frontend/rust-lib/flowy-grid/src/services/field/field_entities.rs → frontend/rust-lib/flowy-grid/src/services/entities/field_entities.rs


+ 7 - 0
frontend/rust-lib/flowy-grid/src/services/entities/mod.rs

@@ -0,0 +1,7 @@
+mod cell_entities;
+mod field_entities;
+mod row_entities;
+
+pub use cell_entities::*;
+pub use field_entities::*;
+pub use row_entities::*;

+ 31 - 0
frontend/rust-lib/flowy-grid/src/services/entities/row_entities.rs

@@ -0,0 +1,31 @@
+use flowy_derive::ProtoBuf;
+use flowy_error::ErrorCode;
+use flowy_grid_data_model::parser::NotEmptyStr;
+
+#[derive(ProtoBuf, Default)]
+pub struct RowIdentifierPayload {
+    #[pb(index = 1)]
+    pub grid_id: String,
+
+    #[pb(index = 3)]
+    pub row_id: String,
+}
+
+pub struct RowIdentifier {
+    pub grid_id: String,
+    pub row_id: String,
+}
+
+impl TryInto<RowIdentifier> for RowIdentifierPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<RowIdentifier, Self::Error> {
+        let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+        let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
+
+        Ok(RowIdentifier {
+            grid_id: grid_id.0,
+            row_id: row_id.0,
+        })
+    }
+}

+ 0 - 2
frontend/rust-lib/flowy-grid/src/services/field/mod.rs

@@ -1,7 +1,5 @@
 mod field_builder;
-pub(crate) mod field_entities;
 pub(crate) mod type_options;
 
 pub use field_builder::*;
-pub use field_entities::*;
 pub use type_options::*;

+ 58 - 2
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs

@@ -1,10 +1,10 @@
 use crate::impl_type_option;
-use crate::services::cell::{CellIdentifier, CellIdentifierPayload};
+use crate::services::entities::{CellIdentifier, CellIdentifierPayload};
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
 use crate::services::row::{CellDataChangeset, CellDataOperation, TypeOptionCellData};
 use bytes::Bytes;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-use flowy_error::{ErrorCode, FlowyError};
+use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
     CellChangeset, CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
 };
@@ -36,10 +36,35 @@ pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync {
         }
     }
 
+    fn create_option(&self, name: &str) -> SelectOption {
+        let color = select_option_color_from_index(self.options().len());
+        SelectOption::with_color(name, color)
+    }
+
     fn option_context(&self, cell_meta: &Option<CellMeta>) -> SelectOptionContext;
+
+    fn options(&self) -> &Vec<SelectOption>;
+
     fn mut_options(&mut self) -> &mut Vec<SelectOption>;
 }
 
+pub fn select_option_operation(field_meta: &FieldMeta) -> FlowyResult<Box<dyn SelectOptionOperation>> {
+    match &field_meta.field_type {
+        FieldType::SingleSelect => {
+            let type_option = SingleSelectTypeOption::from(field_meta);
+            Ok(Box::new(type_option))
+        }
+        FieldType::MultiSelect => {
+            let type_option = MultiSelectTypeOption::from(field_meta);
+            Ok(Box::new(type_option))
+        }
+        ty => {
+            tracing::error!("Unsupported field type: {:?} for this handler", ty);
+            Err(ErrorCode::FieldInvalidOperation.into())
+        }
+    }
+}
+
 // Single select
 #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
 pub struct SingleSelectTypeOption {
@@ -60,6 +85,10 @@ impl SelectOptionOperation for SingleSelectTypeOption {
         }
     }
 
+    fn options(&self) -> &Vec<SelectOption> {
+        &self.options
+    }
+
     fn mut_options(&mut self) -> &mut Vec<SelectOption> {
         &mut self.options
     }
@@ -155,6 +184,10 @@ impl SelectOptionOperation for MultiSelectTypeOption {
         }
     }
 
+    fn options(&self) -> &Vec<SelectOption> {
+        &self.options
+    }
+
     fn mut_options(&mut self) -> &mut Vec<SelectOption> {
         &mut self.options
     }
@@ -265,6 +298,14 @@ impl SelectOption {
             color: SelectOptionColor::default(),
         }
     }
+
+    pub fn with_color(name: &str, color: SelectOptionColor) -> Self {
+        SelectOption {
+            id: nanoid!(4),
+            name: name.to_owned(),
+            color,
+        }
+    }
 }
 
 #[derive(Clone, Debug, Default, ProtoBuf)]
@@ -430,6 +471,21 @@ pub enum SelectOptionColor {
     Blue = 8,
 }
 
+pub fn select_option_color_from_index(index: usize) -> SelectOptionColor {
+    match index % 8 {
+        0 => SelectOptionColor::Purple,
+        1 => SelectOptionColor::Pink,
+        2 => SelectOptionColor::LightPink,
+        3 => SelectOptionColor::Orange,
+        4 => SelectOptionColor::Yellow,
+        5 => SelectOptionColor::Lime,
+        6 => SelectOptionColor::Green,
+        7 => SelectOptionColor::Aqua,
+        8 => SelectOptionColor::Blue,
+        _ => SelectOptionColor::Purple,
+    }
+}
+
 impl std::default::Default for SelectOptionColor {
     fn default() -> Self {
         SelectOptionColor::Purple

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

@@ -1,8 +1,11 @@
 use crate::dart_notification::{send_dart_notification, GridNotification};
 use crate::manager::GridUser;
 use crate::services::block_meta_manager::GridBlockMetaEditorManager;
-use crate::services::cell::CellIdentifier;
-use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder};
+use crate::services::entities::{CellIdentifier, CreateSelectOptionParams};
+use crate::services::field::{
+    default_type_option_builder_from_type, select_option_operation, type_option_builder_from_bytes, FieldBuilder,
+    SelectOption,
+};
 use crate::services::persistence::block_index::BlockIndexPersistence;
 use crate::services::row::*;
 use bytes::Bytes;
@@ -22,7 +25,7 @@ use tokio::sync::RwLock;
 pub struct ClientGridEditor {
     grid_id: String,
     user: Arc<dyn GridUser>,
-    pad: Arc<RwLock<GridMetaPad>>,
+    grid_pad: Arc<RwLock<GridMetaPad>>,
     rev_manager: Arc<RevisionManager>,
     block_meta_manager: Arc<GridBlockMetaEditorManager>,
 }
@@ -38,14 +41,14 @@ impl ClientGridEditor {
         let cloud = Arc::new(GridRevisionCloudService { token });
         let grid_pad = rev_manager.load::<GridPadBuilder>(Some(cloud)).await?;
         let rev_manager = Arc::new(rev_manager);
-        let pad = Arc::new(RwLock::new(grid_pad));
-        let blocks = pad.read().await.get_block_metas();
+        let grid_pad = Arc::new(RwLock::new(grid_pad));
+        let blocks = grid_pad.read().await.get_block_metas();
 
         let block_meta_manager = Arc::new(GridBlockMetaEditorManager::new(grid_id, &user, blocks, persistence).await?);
         Ok(Arc::new(Self {
             grid_id: grid_id.to_owned(),
             user,
-            pad,
+            grid_pad,
             rev_manager,
             block_meta_manager,
         }))
@@ -94,18 +97,18 @@ impl ClientGridEditor {
     }
 
     pub async fn create_next_field_meta(&self, field_type: &FieldType) -> FlowyResult<FieldMeta> {
-        let name = format!("Property {}", self.pad.read().await.fields().len() + 1);
+        let name = format!("Property {}", self.grid_pad.read().await.fields().len() + 1);
         let field_meta = FieldBuilder::from_field_type(field_type).name(&name).build();
         Ok(field_meta)
     }
 
     pub async fn contain_field(&self, field_id: &str) -> bool {
-        self.pad.read().await.contain_field(field_id)
+        self.grid_pad.read().await.contain_field(field_id)
     }
 
     pub async fn update_field(&self, params: FieldChangesetParams) -> FlowyResult<()> {
         let field_id = params.field_id.clone();
-        let json_deserializer = match self.pad.read().await.get_field_meta(params.field_id.as_str()) {
+        let json_deserializer = match self.grid_pad.read().await.get_field_meta(params.field_id.as_str()) {
             None => return Err(ErrorCode::FieldDoesNotExist.into()),
             Some((_, field_meta)) => TypeOptionJsonDeserializer(field_meta.field_type.clone()),
         };
@@ -169,7 +172,7 @@ impl ClientGridEditor {
     }
 
     pub async fn get_field_meta(&self, field_id: &str) -> Option<FieldMeta> {
-        let field_meta = self.pad.read().await.get_field_meta(field_id)?.1.clone();
+        let field_meta = self.grid_pad.read().await.get_field_meta(field_id)?.1.clone();
         Some(field_meta)
     }
 
@@ -178,14 +181,14 @@ impl ClientGridEditor {
         T: Into<FieldOrder>,
     {
         if field_ids.is_none() {
-            let field_metas = self.pad.read().await.get_field_metas(None)?;
+            let field_metas = self.grid_pad.read().await.get_field_metas(None)?;
             return Ok(field_metas);
         }
 
         let to_field_orders = |item: Vec<T>| item.into_iter().map(|data| data.into()).collect();
         let field_orders = field_ids.map_or(vec![], to_field_orders);
         let expected_len = field_orders.len();
-        let field_metas = self.pad.read().await.get_field_metas(Some(field_orders))?;
+        let field_metas = self.grid_pad.read().await.get_field_metas(Some(field_orders))?;
         if expected_len != 0 && field_metas.len() != expected_len {
             tracing::error!(
                 "This is a bug. The len of the field_metas should equal to {}",
@@ -207,7 +210,7 @@ impl ClientGridEditor {
     }
 
     pub async fn create_row(&self, start_row_id: Option<String>) -> FlowyResult<RowOrder> {
-        let field_metas = self.pad.read().await.get_field_metas(None)?;
+        let field_metas = self.grid_pad.read().await.get_field_metas(None)?;
         let block_id = self.block_id().await?;
 
         // insert empty row below the row whose id is upper_row_id
@@ -314,7 +317,7 @@ impl ClientGridEditor {
         let cell_data_changeset = changeset.data.unwrap();
         let cell_meta = self.get_cell_meta(&changeset.row_id, &changeset.field_id).await?;
         tracing::trace!("{}: {:?}", &changeset.field_id, cell_meta);
-        match self.pad.read().await.get_field_meta(&changeset.field_id) {
+        match self.grid_pad.read().await.get_field_meta(&changeset.field_id) {
             None => {
                 let msg = format!("Field not found with id: {}", &changeset.field_id);
                 Err(FlowyError::internal().context(msg))
@@ -334,7 +337,7 @@ impl ClientGridEditor {
     }
 
     pub async fn get_block_metas(&self) -> FlowyResult<Vec<GridBlockMeta>> {
-        let grid_blocks = self.pad.read().await.get_block_metas();
+        let grid_blocks = self.grid_pad.read().await.get_block_metas();
         Ok(grid_blocks)
     }
 
@@ -347,7 +350,7 @@ impl ClientGridEditor {
     }
 
     pub async fn grid_data(&self) -> FlowyResult<Grid> {
-        let pad_read_guard = self.pad.read().await;
+        let pad_read_guard = self.grid_pad.read().await;
         let field_orders = pad_read_guard.get_field_orders();
         let mut block_orders = vec![];
         for block_order in pad_read_guard.get_block_metas() {
@@ -369,7 +372,7 @@ impl ClientGridEditor {
     pub async fn grid_block_snapshots(&self, block_ids: Option<Vec<String>>) -> FlowyResult<Vec<GridBlockSnapshot>> {
         let block_ids = match block_ids {
             None => self
-                .pad
+                .grid_pad
                 .read()
                 .await
                 .get_block_metas()
@@ -396,7 +399,7 @@ impl ClientGridEditor {
         let _ = self
             .modify(|grid_pad| Ok(grid_pad.move_field(field_id, from as usize, to as usize)?))
             .await?;
-        if let Some((index, field_meta)) = self.pad.read().await.get_field_meta(field_id) {
+        if let Some((index, field_meta)) = self.grid_pad.read().await.get_field_meta(field_id) {
             let delete_field_order = FieldOrder::from(field_id);
             let insert_field = IndexField::from_field_meta(field_meta, index);
             let notified_changeset = GridFieldChangeset {
@@ -420,14 +423,14 @@ impl ClientGridEditor {
     }
 
     pub async fn delta_bytes(&self) -> Bytes {
-        self.pad.read().await.delta_bytes()
+        self.grid_pad.read().await.delta_bytes()
     }
 
     async fn modify<F>(&self, f: F) -> FlowyResult<()>
     where
         F: for<'a> FnOnce(&'a mut GridMetaPad) -> FlowyResult<Option<GridChangeset>>,
     {
-        let mut write_guard = self.pad.write().await;
+        let mut write_guard = self.grid_pad.write().await;
         if let Some(changeset) = f(&mut *write_guard)? {
             let _ = self.apply_change(changeset).await?;
         }
@@ -455,7 +458,7 @@ impl ClientGridEditor {
     }
 
     async fn block_id(&self) -> FlowyResult<String> {
-        match self.pad.read().await.get_block_metas().last() {
+        match self.grid_pad.read().await.get_block_metas().last() {
             None => Err(FlowyError::internal().context("There is no grid block in this grid")),
             Some(grid_block) => Ok(grid_block.block_id.clone()),
         }
@@ -463,7 +466,7 @@ impl ClientGridEditor {
 
     #[tracing::instrument(level = "trace", skip_all, err)]
     async fn notify_did_insert_grid_field(&self, field_id: &str) -> FlowyResult<()> {
-        if let Some((index, field_meta)) = self.pad.read().await.get_field_meta(field_id) {
+        if let Some((index, field_meta)) = self.grid_pad.read().await.get_field_meta(field_id) {
             let index_field = IndexField::from_field_meta(field_meta, index);
             let notified_changeset = GridFieldChangeset::insert(&self.grid_id, vec![index_field]);
             let _ = self.notify_did_update_grid(notified_changeset).await?;
@@ -473,10 +476,13 @@ impl ClientGridEditor {
 
     #[tracing::instrument(level = "trace", skip_all, err)]
     async fn notify_did_update_grid_field(&self, field_id: &str) -> FlowyResult<()> {
-        let mut field_metas = self.get_field_metas(Some(vec![field_id])).await?;
-        debug_assert!(field_metas.len() == 1);
-
-        if let Some(field_meta) = field_metas.pop() {
+        if let Some((_, field_meta)) = self
+            .grid_pad
+            .read()
+            .await
+            .get_field_meta(field_id)
+            .map(|(index, field)| (index, field.clone()))
+        {
             let updated_field = Field::from(field_meta);
             let notified_changeset = GridFieldChangeset::update(&self.grid_id, vec![updated_field.clone()]);
             let _ = self.notify_did_update_grid(notified_changeset).await?;

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

@@ -2,7 +2,7 @@ mod util;
 
 pub mod block_meta_editor;
 mod block_meta_manager;
-pub mod cell;
+pub mod entities;
 pub mod field;
 pub mod grid_editor;
 pub mod persistence;