From 81d75147d5323bbb6e5c3d15587fa569711f598a Mon Sep 17 00:00:00 2001
From: appflowy <annie@appflowy.io>
Date: Tue, 10 May 2022 09:33:34 +0800
Subject: [PATCH] chore: update typeOption data

---
 .../application/grid/cell/date_cal_bloc.dart  |  70 ++--
 .../application/grid/field/field_service.dart |  13 +
 .../src/widgets/cell/date_cell/calendar.dart  | 171 ++++++----
 .../dart_event/flowy-grid/dart_event.dart     |  17 +
 .../flowy-grid-data-model/grid.pb.dart        |  75 +++++
 .../flowy-grid-data-model/grid.pbjson.dart    |  12 +
 .../protobuf/flowy-grid/event_map.pbenum.dart |  16 +-
 .../protobuf/flowy-grid/event_map.pbjson.dart |  17 +-
 .../rust-lib/flowy-grid/src/event_handler.rs  |  13 +
 frontend/rust-lib/flowy-grid/src/event_map.rs |  18 +-
 .../src/protobuf/model/event_map.rs           |  51 +--
 .../src/protobuf/proto/event_map.proto        |  15 +-
 .../flowy-grid/src/services/grid_editor.rs    |  28 ++
 .../src/entities/grid.rs                      |  34 ++
 .../src/protobuf/model/grid.rs                | 300 ++++++++++++++++--
 .../src/protobuf/proto/grid.proto             |   5 +
 16 files changed, 701 insertions(+), 154 deletions(-)

diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart
index c33666669d..da1238e758 100644
--- a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart
+++ b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart
@@ -1,5 +1,6 @@
+import 'package:app_flowy/workspace/application/grid/field/field_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;
 import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -8,6 +9,7 @@ import 'dart:async';
 import 'cell_service.dart';
 import 'package:dartz/dartz.dart';
 import 'package:fixnum/fixnum.dart' as $fixnum;
+import 'package:protobuf/protobuf.dart';
 part 'date_cal_bloc.freezed.dart';
 
 class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
@@ -35,14 +37,15 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
             emit(state.copyWith(focusedDay: value.day));
           },
           didReceiveCellUpdate: (_DidReceiveCellUpdate value) {},
-          didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
-            emit(state.copyWith(field: value.field));
+          setIncludeTime: (_IncludeTime value) async {
+            await _updateTypeOption(emit, includeTime: value.includeTime);
           },
-          setIncludeTime: (_IncludeTime value) {
-            emit(state.copyWith(includeTime: value.includeTime));
+          setDateFormat: (_DateFormat value) async {
+            await _updateTypeOption(emit, dateFormat: value.dateFormat);
+          },
+          setTimeFormat: (_TimeFormat value) async {
+            await _updateTypeOption(emit, timeFormat: value.timeFormat);
           },
-          setDateFormat: (_DateFormat value) {},
-          setTimeFormat: (_TimeFormat value) {},
         );
       },
     );
@@ -83,8 +86,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
         }
 
         emit(state.copyWith(
-          typeOptinoData: some(typeOptionData),
-          includeTime: typeOptionData.includeTime,
+          dateTypeOption: some(typeOptionData),
           selectedDay: selectedDay,
         ));
       },
@@ -96,6 +98,43 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
     final data = day.millisecondsSinceEpoch ~/ 1000;
     cellContext.saveCellData(data.toString());
   }
+
+  Future<void>? _updateTypeOption(
+    Emitter<DateCalState> emit, {
+    DateFormat? dateFormat,
+    TimeFormat? timeFormat,
+    bool? includeTime,
+  }) async {
+    final newDateTypeOption = state.dateTypeOption.fold(() => null, (dateTypeOption) {
+      dateTypeOption.freeze();
+      return dateTypeOption.rebuild((typeOption) {
+        if (dateFormat != null) {
+          typeOption.dateFormat = dateFormat;
+        }
+
+        if (timeFormat != null) {
+          typeOption.timeFormat = timeFormat;
+        }
+
+        if (includeTime != null) {
+          typeOption.includeTime = includeTime;
+        }
+      });
+    });
+
+    if (newDateTypeOption != null) {
+      final result = await FieldService.updateFieldTypeOption(
+        gridId: cellContext.gridId,
+        fieldId: cellContext.field.id,
+        typeOptionData: newDateTypeOption.writeToBuffer(),
+      );
+
+      result.fold(
+        (l) => emit(state.copyWith(dateTypeOption: Some(newDateTypeOption))),
+        (err) => Log.error(err),
+      );
+    }
+  }
 }
 
 @freezed
@@ -104,31 +143,26 @@ class DateCalEvent with _$DateCalEvent {
   const factory DateCalEvent.selectDay(DateTime day) = _SelectDay;
   const factory DateCalEvent.setCalFormat(CalendarFormat format) = _CalendarFormat;
   const factory DateCalEvent.setFocusedDay(DateTime day) = _FocusedDay;
-  const factory DateCalEvent.setTimeFormat(TimeFormat value) = _TimeFormat;
-  const factory DateCalEvent.setDateFormat(DateFormat value) = _DateFormat;
+  const factory DateCalEvent.setTimeFormat(TimeFormat timeFormat) = _TimeFormat;
+  const factory DateCalEvent.setDateFormat(DateFormat dateFormat) = _DateFormat;
   const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime;
   const factory DateCalEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
-  const factory DateCalEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
 }
 
 @freezed
 class DateCalState with _$DateCalState {
   const factory DateCalState({
-    required Field field,
-    required Option<DateTypeOption> typeOptinoData,
+    required Option<DateTypeOption> dateTypeOption,
     required CalendarFormat format,
     required DateTime focusedDay,
-    required bool includeTime,
     required Option<String> time,
     DateTime? selectedDay,
   }) = _DateCalState;
 
   factory DateCalState.initial(GridCellContext context) => DateCalState(
-        field: context.field,
-        typeOptinoData: none(),
+        dateTypeOption: none(),
         format: CalendarFormat.month,
         focusedDay: DateTime.now(),
-        includeTime: false,
         time: none(),
       );
 }
diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart
index ef198d5c3a..70c6caf9f8 100644
--- a/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart
+++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart
@@ -99,6 +99,19 @@ class FieldService {
     return GridEventInsertField(payload).send();
   }
 
+  static Future<Either<Unit, FlowyError>> updateFieldTypeOption({
+    required String gridId,
+    required String fieldId,
+    required List<int> typeOptionData,
+  }) {
+    var payload = UpdateFieldTypeOptionPayload.create()
+      ..gridId = gridId
+      ..fieldId = fieldId
+      ..typeOptionData = typeOptionData;
+
+    return GridEventUpdateFieldTypeOption(payload).send();
+  }
+
   Future<Either<Unit, FlowyError>> deleteField() {
     final payload = FieldIdentifierPayload.create()
       ..gridId = gridId
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/calendar.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/calendar.dart
index 311f091e9b..68875d4ce3 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/calendar.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/calendar.dart
@@ -1,5 +1,6 @@
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:app_flowy/workspace/application/grid/cell/date_cal_bloc.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/date.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
@@ -10,10 +11,10 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:table_calendar/table_calendar.dart';
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
+import 'package:dartz/dartz.dart' show Option;
 
 final kToday = DateTime.now();
 final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day);
@@ -39,25 +40,11 @@ class CellCalendar with FlowyOverlayDelegate {
       includeTime: false,
       cellContext: cellContext,
     );
-    // const size = Size(460, 400);
-    // final window = await getWindowInfo();
-    // FlowyOverlay.of(context).insertWithRect(
-    //   widget: OverlayContainer(
-    //     child: calendar,
-    //     constraints: BoxConstraints.loose(const Size(460, 400)),
-    //   ),
-    //   identifier: _CellCalendar.identifier(),
-    //   anchorPosition: Offset(-size.width / 2.0, -size.height / 2.0),
-    //   anchorSize: window.frame.size,
-    //   anchorDirection: AnchorDirection.center,
-    //   style: FlowyOverlayStyle(blur: false),
-    //   delegate: calendar,
-    // );
 
     FlowyOverlay.of(context).insertWithAnchor(
       widget: OverlayContainer(
         child: calendar,
-        constraints: BoxConstraints.tight(const Size(320, 500)),
+        constraints: BoxConstraints.loose(const Size(320, 500)),
       ),
       identifier: CellCalendar.identifier(),
       anchorContext: context,
@@ -114,23 +101,31 @@ class _CellCalendarWidget extends StatelessWidget {
             const VSpace(10),
           ]);
 
-          if (state.includeTime) {
+          state.dateTypeOption.foldRight(null, (dateTypeOption, _) {
             children.addAll([
               const _TimeTextField(),
               const VSpace(10),
             ]);
-          }
+          });
 
           children.addAll([
             Divider(height: 1, color: theme.shader5),
             const _IncludeTimeButton(),
           ]);
 
-          state.typeOptinoData.fold(() => null, (dateTypeOption) {
-            children.add(_DateTypeOptionButton(dateTypeOption: dateTypeOption));
-          });
+          children.add(const _DateTypeOptionButton());
 
-          return Column(children: children);
+          return ListView.separated(
+            shrinkWrap: true,
+            controller: ScrollController(),
+            separatorBuilder: (context, index) {
+              return VSpace(GridSize.typeOptionSeparatorHeight);
+            },
+            itemCount: children.length,
+            itemBuilder: (BuildContext context, int index) {
+              return children[index];
+            },
+          );
         },
       ),
     );
@@ -194,7 +189,7 @@ class _IncludeTimeButton extends StatelessWidget {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     return BlocSelector<DateCalBloc, DateCalState, bool>(
-      selector: (state) => state.includeTime,
+      selector: (state) => state.dateTypeOption.foldRight(false, (option, _) => option.includeTime),
       builder: (context, includeTime) {
         return SizedBox(
           height: 50,
@@ -229,53 +224,117 @@ class _TimeTextField extends StatelessWidget {
 }
 
 class _DateTypeOptionButton extends StatelessWidget {
-  final DateTypeOption dateTypeOption;
-  const _DateTypeOptionButton({required this.dateTypeOption, Key? key}) : super(key: key);
+  const _DateTypeOptionButton({Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     final title = LocaleKeys.grid_field_dateFormat.tr() + " &" + LocaleKeys.grid_field_timeFormat.tr();
-    return FlowyButton(
-      text: FlowyText.medium(title, fontSize: 12),
-      hoverColor: theme.hover,
-      margin: kMargin,
-      onTap: () {
-        final setting = _CalDateTimeSetting(dateTypeOption: dateTypeOption);
-        setting.show(context);
+    return BlocSelector<DateCalBloc, DateCalState, Option<DateTypeOption>>(
+      selector: (state) => state.dateTypeOption,
+      builder: (context, dateTypeOption) {
+        return FlowyButton(
+          text: FlowyText.medium(title, fontSize: 12),
+          hoverColor: theme.hover,
+          margin: kMargin,
+          onTap: () {
+            dateTypeOption.fold(() => null, (dateTypeOption) {
+              final setting = _CalDateTimeSetting(dateTypeOption: dateTypeOption);
+              setting.show(context);
+            });
+          },
+          rightIcon: svgWidget("grid/more", color: theme.iconColor),
+        );
       },
-      rightIcon: svgWidget("grid/more", color: theme.iconColor),
     );
   }
 }
 
-class _CalDateTimeSetting extends StatelessWidget {
+class _CalDateTimeSetting extends StatefulWidget {
   final DateTypeOption dateTypeOption;
   const _CalDateTimeSetting({required this.dateTypeOption, Key? key}) : super(key: key);
 
   @override
-  Widget build(BuildContext context) {
-    return Column(children: [
-      DateFormatButton(onTap: () {
-        final list = DateFormatList(
-          selectedFormat: dateTypeOption.dateFormat,
-          onSelected: (format) {
-            context.read<DateTypeOptionBloc>().add(DateTypeOptionEvent.didSelectDateFormat(format));
-          },
-        );
-      }),
-      TimeFormatButton(
-        timeFormat: dateTypeOption.timeFormat,
-        onTap: () {
-          final list = TimeFormatList(
-              selectedFormat: dateTypeOption.timeFormat,
-              onSelected: (format) {
-                context.read<DateTypeOptionBloc>().add(DateTypeOptionEvent.didSelectTimeFormat(format));
-              });
-        },
-      ),
-    ]);
+  State<_CalDateTimeSetting> createState() => _CalDateTimeSettingState();
+
+  static String identifier() {
+    return (_CalDateTimeSetting).toString();
   }
 
-  void show(BuildContext context) {}
+  void show(BuildContext context) {
+    FlowyOverlay.of(context).insertWithAnchor(
+      widget: OverlayContainer(
+        child: this,
+        constraints: BoxConstraints.loose(const Size(140, 100)),
+      ),
+      identifier: _CalDateTimeSetting.identifier(),
+      anchorContext: context,
+      anchorDirection: AnchorDirection.rightWithCenterAligned,
+    );
+  }
+}
+
+class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
+  String? overlayIdentifier;
+
+  @override
+  Widget build(BuildContext context) {
+    List<Widget> children = [
+      DateFormatButton(onTap: () {
+        final list = DateFormatList(
+          selectedFormat: widget.dateTypeOption.dateFormat,
+          onSelected: (format) {
+            context.read<DateCalBloc>().add(DateCalEvent.setDateFormat(format));
+          },
+        );
+        _showOverlay(context, list);
+      }),
+      TimeFormatButton(
+        timeFormat: widget.dateTypeOption.timeFormat,
+        onTap: () {
+          final list = TimeFormatList(
+            selectedFormat: widget.dateTypeOption.timeFormat,
+            onSelected: (format) {
+              context.read<DateCalBloc>().add(DateCalEvent.setTimeFormat(format));
+            },
+          );
+          _showOverlay(context, list);
+        },
+      ),
+    ];
+
+    return SizedBox(
+      width: 180,
+      child: ListView.separated(
+        shrinkWrap: true,
+        controller: ScrollController(),
+        separatorBuilder: (context, index) {
+          return VSpace(GridSize.typeOptionSeparatorHeight);
+        },
+        itemCount: children.length,
+        itemBuilder: (BuildContext context, int index) {
+          return children[index];
+        },
+      ),
+    );
+  }
+
+  void _showOverlay(BuildContext context, Widget child) {
+    if (overlayIdentifier != null) {
+      FlowyOverlay.of(context).remove(overlayIdentifier!);
+    }
+
+    overlayIdentifier = child.toString();
+    FlowyOverlay.of(context).insertWithAnchor(
+      widget: OverlayContainer(
+        child: child,
+        constraints: BoxConstraints.loose(const Size(460, 440)),
+      ),
+      identifier: overlayIdentifier!,
+      anchorContext: context,
+      anchorDirection: AnchorDirection.rightWithCenterAligned,
+      style: FlowyOverlayStyle(blur: false),
+      anchorOffset: const Offset(-20, 0),
+    );
+  }
 }
diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart
index 339f2b451c..ddc642d913 100644
--- a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart
+++ b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart
@@ -69,6 +69,23 @@ class GridEventUpdateField {
     }
 }
 
+class GridEventUpdateFieldTypeOption {
+     UpdateFieldTypeOptionPayload request;
+     GridEventUpdateFieldTypeOption(this.request);
+
+    Future<Either<Unit, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.UpdateFieldTypeOption.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
 class GridEventInsertField {
      InsertFieldPayload request;
      GridEventInsertField(this.request);
diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
index 968044a972..5cf3a2a83b 100644
--- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
+++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
@@ -1781,6 +1781,81 @@ class InsertFieldPayload extends $pb.GeneratedMessage {
   void clearStartFieldId() => clearField(4);
 }
 
+class UpdateFieldTypeOptionPayload extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UpdateFieldTypeOptionPayload', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
+    ..a<$core.List<$core.int>>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptionData', $pb.PbFieldType.OY)
+    ..hasRequiredFields = false
+  ;
+
+  UpdateFieldTypeOptionPayload._() : super();
+  factory UpdateFieldTypeOptionPayload({
+    $core.String? gridId,
+    $core.String? fieldId,
+    $core.List<$core.int>? typeOptionData,
+  }) {
+    final _result = create();
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
+    if (fieldId != null) {
+      _result.fieldId = fieldId;
+    }
+    if (typeOptionData != null) {
+      _result.typeOptionData = typeOptionData;
+    }
+    return _result;
+  }
+  factory UpdateFieldTypeOptionPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory UpdateFieldTypeOptionPayload.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')
+  UpdateFieldTypeOptionPayload clone() => UpdateFieldTypeOptionPayload()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  UpdateFieldTypeOptionPayload copyWith(void Function(UpdateFieldTypeOptionPayload) updates) => super.copyWith((message) => updates(message as UpdateFieldTypeOptionPayload)) as UpdateFieldTypeOptionPayload; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static UpdateFieldTypeOptionPayload create() => UpdateFieldTypeOptionPayload._();
+  UpdateFieldTypeOptionPayload createEmptyInstance() => create();
+  static $pb.PbList<UpdateFieldTypeOptionPayload> createRepeated() => $pb.PbList<UpdateFieldTypeOptionPayload>();
+  @$core.pragma('dart2js:noInline')
+  static UpdateFieldTypeOptionPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UpdateFieldTypeOptionPayload>(create);
+  static UpdateFieldTypeOptionPayload? _defaultInstance;
+
+  @$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.List<$core.int> get typeOptionData => $_getN(2);
+  @$pb.TagNumber(3)
+  set typeOptionData($core.List<$core.int> v) { $_setBytes(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasTypeOptionData() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearTypeOptionData() => clearField(3);
+}
+
 class QueryFieldPayload extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'QueryFieldPayload', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
index 29dcde046c..9043564b43 100644
--- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
+++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
@@ -367,6 +367,18 @@ const InsertFieldPayload$json = const {
 
 /// Descriptor for `InsertFieldPayload`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List insertFieldPayloadDescriptor = $convert.base64Decode('ChJJbnNlcnRGaWVsZFBheWxvYWQSFwoHZ3JpZF9pZBgBIAEoCVIGZ3JpZElkEhwKBWZpZWxkGAIgASgLMgYuRmllbGRSBWZpZWxkEigKEHR5cGVfb3B0aW9uX2RhdGEYAyABKAxSDnR5cGVPcHRpb25EYXRhEiYKDnN0YXJ0X2ZpZWxkX2lkGAQgASgJSABSDHN0YXJ0RmllbGRJZEIXChVvbmVfb2Zfc3RhcnRfZmllbGRfaWQ=');
+@$core.Deprecated('Use updateFieldTypeOptionPayloadDescriptor instead')
+const UpdateFieldTypeOptionPayload$json = const {
+  '1': 'UpdateFieldTypeOptionPayload',
+  '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': 'type_option_data', '3': 3, '4': 1, '5': 12, '10': 'typeOptionData'},
+  ],
+};
+
+/// Descriptor for `UpdateFieldTypeOptionPayload`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List updateFieldTypeOptionPayloadDescriptor = $convert.base64Decode('ChxVcGRhdGVGaWVsZFR5cGVPcHRpb25QYXlsb2FkEhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIZCghmaWVsZF9pZBgCIAEoCVIHZmllbGRJZBIoChB0eXBlX29wdGlvbl9kYXRhGAMgASgMUg50eXBlT3B0aW9uRGF0YQ==');
 @$core.Deprecated('Use queryFieldPayloadDescriptor instead')
 const QueryFieldPayload$json = const {
   '1': 'QueryFieldPayload',
diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart
index b907873844..4e0db67667 100644
--- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart
+++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart
@@ -14,13 +14,14 @@ class GridEvent extends $pb.ProtobufEnum {
   static const GridEvent GetGridBlocks = GridEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetGridBlocks');
   static const GridEvent GetFields = GridEvent._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetFields');
   static const GridEvent UpdateField = GridEvent._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateField');
-  static const GridEvent InsertField = GridEvent._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InsertField');
-  static const GridEvent DeleteField = GridEvent._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteField');
-  static const GridEvent SwitchToField = GridEvent._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SwitchToField');
-  static const GridEvent DuplicateField = GridEvent._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateField');
-  static const GridEvent GetEditFieldContext = GridEvent._(16, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetEditFieldContext');
-  static const GridEvent MoveItem = GridEvent._(17, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MoveItem');
-  static const GridEvent GetFieldTypeOption = GridEvent._(18, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetFieldTypeOption');
+  static const GridEvent UpdateFieldTypeOption = GridEvent._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateFieldTypeOption');
+  static const GridEvent InsertField = GridEvent._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InsertField');
+  static const GridEvent DeleteField = GridEvent._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteField');
+  static const GridEvent SwitchToField = GridEvent._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SwitchToField');
+  static const GridEvent DuplicateField = GridEvent._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateField');
+  static const GridEvent GetEditFieldContext = GridEvent._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetEditFieldContext');
+  static const GridEvent MoveItem = GridEvent._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MoveItem');
+  static const GridEvent GetFieldTypeOption = GridEvent._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetFieldTypeOption');
   static const GridEvent NewSelectOption = GridEvent._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NewSelectOption');
   static const GridEvent GetSelectOptionContext = GridEvent._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetSelectOptionContext');
   static const GridEvent UpdateSelectOption = GridEvent._(32, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateSelectOption');
@@ -37,6 +38,7 @@ class GridEvent extends $pb.ProtobufEnum {
     GetGridBlocks,
     GetFields,
     UpdateField,
+    UpdateFieldTypeOption,
     InsertField,
     DeleteField,
     SwitchToField,
diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart
index 9d63053af5..6c3f668b74 100644
--- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart
+++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart
@@ -16,13 +16,14 @@ const GridEvent$json = const {
     const {'1': 'GetGridBlocks', '2': 1},
     const {'1': 'GetFields', '2': 10},
     const {'1': 'UpdateField', '2': 11},
-    const {'1': 'InsertField', '2': 12},
-    const {'1': 'DeleteField', '2': 13},
-    const {'1': 'SwitchToField', '2': 14},
-    const {'1': 'DuplicateField', '2': 15},
-    const {'1': 'GetEditFieldContext', '2': 16},
-    const {'1': 'MoveItem', '2': 17},
-    const {'1': 'GetFieldTypeOption', '2': 18},
+    const {'1': 'UpdateFieldTypeOption', '2': 12},
+    const {'1': 'InsertField', '2': 13},
+    const {'1': 'DeleteField', '2': 14},
+    const {'1': 'SwitchToField', '2': 20},
+    const {'1': 'DuplicateField', '2': 21},
+    const {'1': 'GetEditFieldContext', '2': 22},
+    const {'1': 'MoveItem', '2': 23},
+    const {'1': 'GetFieldTypeOption', '2': 24},
     const {'1': 'NewSelectOption', '2': 30},
     const {'1': 'GetSelectOptionContext', '2': 31},
     const {'1': 'UpdateSelectOption', '2': 32},
@@ -37,4 +38,4 @@ const GridEvent$json = const {
 };
 
 /// Descriptor for `GridEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIPCgtJbnNlcnRGaWVsZBAMEg8KC0RlbGV0ZUZpZWxkEA0SEQoNU3dpdGNoVG9GaWVsZBAOEhIKDkR1cGxpY2F0ZUZpZWxkEA8SFwoTR2V0RWRpdEZpZWxkQ29udGV4dBAQEgwKCE1vdmVJdGVtEBESFgoSR2V0RmllbGRUeXBlT3B0aW9uEBISEwoPTmV3U2VsZWN0T3B0aW9uEB4SGgoWR2V0U2VsZWN0T3B0aW9uQ29udGV4dBAfEhYKElVwZGF0ZVNlbGVjdE9wdGlvbhAgEg0KCUNyZWF0ZVJvdxAyEgoKBkdldFJvdxAzEg0KCURlbGV0ZVJvdxA0EhAKDER1cGxpY2F0ZVJvdxA1EgsKB0dldENlbGwQRhIOCgpVcGRhdGVDZWxsEEcSGgoWVXBkYXRlQ2VsbFNlbGVjdE9wdGlvbhBI');
+final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIZChVVcGRhdGVGaWVsZFR5cGVPcHRpb24QDBIPCgtJbnNlcnRGaWVsZBANEg8KC0RlbGV0ZUZpZWxkEA4SEQoNU3dpdGNoVG9GaWVsZBAUEhIKDkR1cGxpY2F0ZUZpZWxkEBUSFwoTR2V0RWRpdEZpZWxkQ29udGV4dBAWEgwKCE1vdmVJdGVtEBcSFgoSR2V0RmllbGRUeXBlT3B0aW9uEBgSEwoPTmV3U2VsZWN0T3B0aW9uEB4SGgoWR2V0U2VsZWN0T3B0aW9uQ29udGV4dBAfEhYKElVwZGF0ZVNlbGVjdE9wdGlvbhAgEg0KCUNyZWF0ZVJvdxAyEgoKBkdldFJvdxAzEg0KCURlbGV0ZVJvdxA0EhAKDER1cGxpY2F0ZVJvdxA1EgsKB0dldENlbGwQRhIOCgpVcGRhdGVDZWxsEEcSGgoWVXBkYXRlQ2VsbFNlbGVjdE9wdGlvbhBI');
diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs
index fd7b838b82..f60f31e87a 100644
--- a/frontend/rust-lib/flowy-grid/src/event_handler.rs
+++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs
@@ -70,6 +70,19 @@ pub(crate) async fn insert_field_handler(
     Ok(())
 }
 
+#[tracing::instrument(level = "debug", skip(data, manager), err)]
+pub(crate) async fn update_field_type_option_handler(
+    data: Data<UpdateFieldTypeOptionPayload>,
+    manager: AppData<Arc<GridManager>>,
+) -> Result<(), FlowyError> {
+    let params: UpdateFieldTypeOptionParams = data.into_inner().try_into()?;
+    let editor = manager.get_grid_editor(&params.grid_id)?;
+    let _ = editor
+        .update_field_type_option(&params.grid_id, &params.field_id, params.type_option_data)
+        .await?;
+    Ok(())
+}
+
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
 pub(crate) async fn delete_field_handler(
     data: Data<FieldIdentifierPayload>,
diff --git a/frontend/rust-lib/flowy-grid/src/event_map.rs b/frontend/rust-lib/flowy-grid/src/event_map.rs
index a90c90ebe2..dab3cefd78 100644
--- a/frontend/rust-lib/flowy-grid/src/event_map.rs
+++ b/frontend/rust-lib/flowy-grid/src/event_map.rs
@@ -14,6 +14,7 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
         .event(GridEvent::GetFields, get_fields_handler)
         .event(GridEvent::UpdateField, update_field_handler)
         .event(GridEvent::InsertField, insert_field_handler)
+        .event(GridEvent::UpdateFieldTypeOption, update_field_type_option_handler)
         .event(GridEvent::DeleteField, delete_field_handler)
         .event(GridEvent::SwitchToField, switch_to_field_handler)
         .event(GridEvent::DuplicateField, duplicate_field_handler)
@@ -52,26 +53,29 @@ pub enum GridEvent {
     #[event(input = "FieldChangesetPayload")]
     UpdateField = 11,
 
+    #[event(input = "UpdateFieldTypeOptionPayload")]
+    UpdateFieldTypeOption = 12,
+
     #[event(input = "InsertFieldPayload")]
-    InsertField = 12,
+    InsertField = 13,
 
     #[event(input = "FieldIdentifierPayload")]
-    DeleteField = 13,
+    DeleteField = 14,
 
     #[event(input = "EditFieldPayload", output = "EditFieldContext")]
-    SwitchToField = 14,
+    SwitchToField = 20,
 
     #[event(input = "FieldIdentifierPayload")]
-    DuplicateField = 15,
+    DuplicateField = 21,
 
     #[event(input = "EditFieldPayload", output = "EditFieldContext")]
-    GetEditFieldContext = 16,
+    GetEditFieldContext = 22,
 
     #[event(input = "MoveItemPayload")]
-    MoveItem = 17,
+    MoveItem = 23,
 
     #[event(input = "EditFieldPayload", output = "FieldTypeOptionData")]
-    GetFieldTypeOption = 18,
+    GetFieldTypeOption = 24,
 
     #[event(input = "CreateSelectOptionPayload", output = "SelectOption")]
     NewSelectOption = 30,
diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs
index 739f8d9767..bf1cb2c71a 100644
--- a/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs
+++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs
@@ -29,13 +29,14 @@ pub enum GridEvent {
     GetGridBlocks = 1,
     GetFields = 10,
     UpdateField = 11,
-    InsertField = 12,
-    DeleteField = 13,
-    SwitchToField = 14,
-    DuplicateField = 15,
-    GetEditFieldContext = 16,
-    MoveItem = 17,
-    GetFieldTypeOption = 18,
+    UpdateFieldTypeOption = 12,
+    InsertField = 13,
+    DeleteField = 14,
+    SwitchToField = 20,
+    DuplicateField = 21,
+    GetEditFieldContext = 22,
+    MoveItem = 23,
+    GetFieldTypeOption = 24,
     NewSelectOption = 30,
     GetSelectOptionContext = 31,
     UpdateSelectOption = 32,
@@ -59,13 +60,14 @@ impl ::protobuf::ProtobufEnum for GridEvent {
             1 => ::std::option::Option::Some(GridEvent::GetGridBlocks),
             10 => ::std::option::Option::Some(GridEvent::GetFields),
             11 => ::std::option::Option::Some(GridEvent::UpdateField),
-            12 => ::std::option::Option::Some(GridEvent::InsertField),
-            13 => ::std::option::Option::Some(GridEvent::DeleteField),
-            14 => ::std::option::Option::Some(GridEvent::SwitchToField),
-            15 => ::std::option::Option::Some(GridEvent::DuplicateField),
-            16 => ::std::option::Option::Some(GridEvent::GetEditFieldContext),
-            17 => ::std::option::Option::Some(GridEvent::MoveItem),
-            18 => ::std::option::Option::Some(GridEvent::GetFieldTypeOption),
+            12 => ::std::option::Option::Some(GridEvent::UpdateFieldTypeOption),
+            13 => ::std::option::Option::Some(GridEvent::InsertField),
+            14 => ::std::option::Option::Some(GridEvent::DeleteField),
+            20 => ::std::option::Option::Some(GridEvent::SwitchToField),
+            21 => ::std::option::Option::Some(GridEvent::DuplicateField),
+            22 => ::std::option::Option::Some(GridEvent::GetEditFieldContext),
+            23 => ::std::option::Option::Some(GridEvent::MoveItem),
+            24 => ::std::option::Option::Some(GridEvent::GetFieldTypeOption),
             30 => ::std::option::Option::Some(GridEvent::NewSelectOption),
             31 => ::std::option::Option::Some(GridEvent::GetSelectOptionContext),
             32 => ::std::option::Option::Some(GridEvent::UpdateSelectOption),
@@ -86,6 +88,7 @@ impl ::protobuf::ProtobufEnum for GridEvent {
             GridEvent::GetGridBlocks,
             GridEvent::GetFields,
             GridEvent::UpdateField,
+            GridEvent::UpdateFieldTypeOption,
             GridEvent::InsertField,
             GridEvent::DeleteField,
             GridEvent::SwitchToField,
@@ -131,17 +134,17 @@ impl ::protobuf::reflect::ProtobufValue for GridEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0fevent_map.proto*\x95\x03\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\
+    \n\x0fevent_map.proto*\xb0\x03\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\
     \0\x12\x11\n\rGetGridBlocks\x10\x01\x12\r\n\tGetFields\x10\n\x12\x0f\n\
-    \x0bUpdateField\x10\x0b\x12\x0f\n\x0bInsertField\x10\x0c\x12\x0f\n\x0bDe\
-    leteField\x10\r\x12\x11\n\rSwitchToField\x10\x0e\x12\x12\n\x0eDuplicateF\
-    ield\x10\x0f\x12\x17\n\x13GetEditFieldContext\x10\x10\x12\x0c\n\x08MoveI\
-    tem\x10\x11\x12\x16\n\x12GetFieldTypeOption\x10\x12\x12\x13\n\x0fNewSele\
-    ctOption\x10\x1e\x12\x1a\n\x16GetSelectOptionContext\x10\x1f\x12\x16\n\
-    \x12UpdateSelectOption\x10\x20\x12\r\n\tCreateRow\x102\x12\n\n\x06GetRow\
-    \x103\x12\r\n\tDeleteRow\x104\x12\x10\n\x0cDuplicateRow\x105\x12\x0b\n\
-    \x07GetCell\x10F\x12\x0e\n\nUpdateCell\x10G\x12\x1a\n\x16UpdateCellSelec\
-    tOption\x10Hb\x06proto3\
+    \x0bUpdateField\x10\x0b\x12\x19\n\x15UpdateFieldTypeOption\x10\x0c\x12\
+    \x0f\n\x0bInsertField\x10\r\x12\x0f\n\x0bDeleteField\x10\x0e\x12\x11\n\r\
+    SwitchToField\x10\x14\x12\x12\n\x0eDuplicateField\x10\x15\x12\x17\n\x13G\
+    etEditFieldContext\x10\x16\x12\x0c\n\x08MoveItem\x10\x17\x12\x16\n\x12Ge\
+    tFieldTypeOption\x10\x18\x12\x13\n\x0fNewSelectOption\x10\x1e\x12\x1a\n\
+    \x16GetSelectOptionContext\x10\x1f\x12\x16\n\x12UpdateSelectOption\x10\
+    \x20\x12\r\n\tCreateRow\x102\x12\n\n\x06GetRow\x103\x12\r\n\tDeleteRow\
+    \x104\x12\x10\n\x0cDuplicateRow\x105\x12\x0b\n\x07GetCell\x10F\x12\x0e\n\
+    \nUpdateCell\x10G\x12\x1a\n\x16UpdateCellSelectOption\x10Hb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto b/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto
index 233de825a2..ab804c2b82 100644
--- a/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto
+++ b/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto
@@ -5,13 +5,14 @@ enum GridEvent {
     GetGridBlocks = 1;
     GetFields = 10;
     UpdateField = 11;
-    InsertField = 12;
-    DeleteField = 13;
-    SwitchToField = 14;
-    DuplicateField = 15;
-    GetEditFieldContext = 16;
-    MoveItem = 17;
-    GetFieldTypeOption = 18;
+    UpdateFieldTypeOption = 12;
+    InsertField = 13;
+    DeleteField = 14;
+    SwitchToField = 20;
+    DuplicateField = 21;
+    GetEditFieldContext = 22;
+    MoveItem = 23;
+    GetFieldTypeOption = 24;
     NewSelectOption = 30;
     GetSelectOptionContext = 31;
     UpdateSelectOption = 32;
diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
index 5962386352..ab41542174 100644
--- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
@@ -93,6 +93,34 @@ impl ClientGridEditor {
         Ok(())
     }
 
+    pub async fn update_field_type_option(
+        &self,
+        grid_id: &str,
+        field_id: &str,
+        type_option_data: Vec<u8>,
+    ) -> FlowyResult<()> {
+        let result = self.get_field_meta(field_id).await;
+        if result.is_none() {
+            tracing::warn!("Can't find the field with id: {}", field_id);
+            return Ok(());
+        }
+        let field_meta = result.unwrap();
+        let _ = self
+            .modify(|grid| {
+                let deserializer = TypeOptionJsonDeserializer(field_meta.field_type.clone());
+                let changeset = FieldChangesetParams {
+                    field_id: field_id.to_owned(),
+                    grid_id: grid_id.to_owned(),
+                    type_option_data: Some(type_option_data),
+                    ..Default::default()
+                };
+                Ok(grid.update_field_meta(changeset, deserializer)?)
+            })
+            .await?;
+        let _ = self.notify_did_update_grid_field(&field_id).await?;
+        Ok(())
+    }
+
     pub async fn create_next_field_meta(&self, field_type: &FieldType) -> FlowyResult<FieldMeta> {
         let name = format!("Property {}", self.grid_pad.read().await.fields().len() + 1);
         let field_meta = FieldBuilder::from_field_type(field_type).name(&name).build();
diff --git a/shared-lib/flowy-grid-data-model/src/entities/grid.rs b/shared-lib/flowy-grid-data-model/src/entities/grid.rs
index 09f6f8f40c..42c280aec5 100644
--- a/shared-lib/flowy-grid-data-model/src/entities/grid.rs
+++ b/shared-lib/flowy-grid-data-model/src/entities/grid.rs
@@ -616,6 +616,40 @@ impl TryInto<InsertFieldParams> for InsertFieldPayload {
     }
 }
 
+#[derive(ProtoBuf, Default)]
+pub struct UpdateFieldTypeOptionPayload {
+    #[pb(index = 1)]
+    pub grid_id: String,
+
+    #[pb(index = 2)]
+    pub field_id: String,
+
+    #[pb(index = 3)]
+    pub type_option_data: Vec<u8>,
+}
+
+#[derive(Clone)]
+pub struct UpdateFieldTypeOptionParams {
+    pub grid_id: String,
+    pub field_id: String,
+    pub type_option_data: Vec<u8>,
+}
+
+impl TryInto<UpdateFieldTypeOptionParams> for UpdateFieldTypeOptionPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<UpdateFieldTypeOptionParams, Self::Error> {
+        let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+        let _ = NotEmptyStr::parse(self.field_id.clone()).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
+
+        Ok(UpdateFieldTypeOptionParams {
+            grid_id: grid_id.0,
+            field_id: self.field_id,
+            type_option_data: self.type_option_data,
+        })
+    }
+}
+
 #[derive(ProtoBuf, Default)]
 pub struct QueryFieldPayload {
     #[pb(index = 1)]
diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
index 5d3d3df43a..8391c13453 100644
--- a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
+++ b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
@@ -6154,6 +6154,249 @@ impl ::protobuf::reflect::ProtobufValue for InsertFieldPayload {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct UpdateFieldTypeOptionPayload {
+    // message fields
+    pub grid_id: ::std::string::String,
+    pub field_id: ::std::string::String,
+    pub type_option_data: ::std::vec::Vec<u8>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a UpdateFieldTypeOptionPayload {
+    fn default() -> &'a UpdateFieldTypeOptionPayload {
+        <UpdateFieldTypeOptionPayload as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl UpdateFieldTypeOptionPayload {
+    pub fn new() -> UpdateFieldTypeOptionPayload {
+        ::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())
+    }
+
+    // bytes type_option_data = 3;
+
+
+    pub fn get_type_option_data(&self) -> &[u8] {
+        &self.type_option_data
+    }
+    pub fn clear_type_option_data(&mut self) {
+        self.type_option_data.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_type_option_data(&mut self, v: ::std::vec::Vec<u8>) {
+        self.type_option_data = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_type_option_data(&mut self) -> &mut ::std::vec::Vec<u8> {
+        &mut self.type_option_data
+    }
+
+    // Take field
+    pub fn take_type_option_data(&mut self) -> ::std::vec::Vec<u8> {
+        ::std::mem::replace(&mut self.type_option_data, ::std::vec::Vec::new())
+    }
+}
+
+impl ::protobuf::Message for UpdateFieldTypeOptionPayload {
+    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_bytes_into(wire_type, is, &mut self.type_option_data)?;
+                },
+                _ => {
+                    ::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.type_option_data.is_empty() {
+            my_size += ::protobuf::rt::bytes_size(3, &self.type_option_data);
+        }
+        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.type_option_data.is_empty() {
+            os.write_bytes(3, &self.type_option_data)?;
+        }
+        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() -> UpdateFieldTypeOptionPayload {
+        UpdateFieldTypeOptionPayload::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: &UpdateFieldTypeOptionPayload| { &m.grid_id },
+                |m: &mut UpdateFieldTypeOptionPayload| { &mut m.grid_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "field_id",
+                |m: &UpdateFieldTypeOptionPayload| { &m.field_id },
+                |m: &mut UpdateFieldTypeOptionPayload| { &mut m.field_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
+                "type_option_data",
+                |m: &UpdateFieldTypeOptionPayload| { &m.type_option_data },
+                |m: &mut UpdateFieldTypeOptionPayload| { &mut m.type_option_data },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<UpdateFieldTypeOptionPayload>(
+                "UpdateFieldTypeOptionPayload",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static UpdateFieldTypeOptionPayload {
+        static instance: ::protobuf::rt::LazyV2<UpdateFieldTypeOptionPayload> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(UpdateFieldTypeOptionPayload::new)
+    }
+}
+
+impl ::protobuf::Clear for UpdateFieldTypeOptionPayload {
+    fn clear(&mut self) {
+        self.grid_id.clear();
+        self.field_id.clear();
+        self.type_option_data.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for UpdateFieldTypeOptionPayload {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for UpdateFieldTypeOptionPayload {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(PartialEq,Clone,Default)]
 pub struct QueryFieldPayload {
     // message fields
@@ -8056,33 +8299,36 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x01\x20\x01(\tR\x06gridId\x12\x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.\
     FieldR\x05field\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etype\
     OptionData\x12&\n\x0estart_field_id\x18\x04\x20\x01(\tH\0R\x0cstartField\
-    IdB\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\x16QueryGridB\
-    locksPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x122\n\
-    \x0cblock_orders\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrder\
-    s\"\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\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\nvisibi\
-    lity\x12\x16\n\x05width\x18\x08\x20\x01(\x05H\x05R\x05width\x12*\n\x10ty\
-    pe_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_fro\
-    zenB\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\x06i\
-    temId\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\x06gridId\x12\x15\n\x06row_id\x18\x02\
-    \x20\x01(\tR\x05rowId\x12\x19\n\x08field_id\x18\x03\x20\x01(\tR\x07field\
-    Id\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\x08DateTime\x10\x02\x12\x10\n\x0cSingleSelect\x10\x03\x12\
-    \x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08Checkbox\x10\x05b\x06proto3\
+    IdB\x17\n\x15one_of_start_field_id\"|\n\x1cUpdateFieldTypeOptionPayload\
+    \x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_i\
+    d\x18\x02\x20\x01(\tR\x07fieldId\x12(\n\x10type_option_data\x18\x03\x20\
+    \x01(\x0cR\x0etypeOptionData\"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\x16QueryGridBlocksPay\
+    load\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(\t\
+    R\x07fieldId\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\
+    \n\x04name\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.FieldTy\
+    peH\x02R\tfieldType\x12\x18\n\x06frozen\x18\x06\x20\x01(\x08H\x03R\x06fr\
+    ozen\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_optio\
+    n_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_opt\
+    ion_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.MoveItemTypeR\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\x0cMoveI\
+    temType\x12\r\n\tMoveField\x10\0\x12\x0b\n\x07MoveRow\x10\x01*d\n\tField\
+    Type\x12\x0c\n\x08RichText\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08\
+    DateTime\x10\x02\x12\x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSel\
+    ect\x10\x04\x12\x0c\n\x08Checkbox\x10\x05b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto
index 77c6943c04..4295dee2bb 100644
--- a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto
+++ b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto
@@ -118,6 +118,11 @@ message InsertFieldPayload {
     bytes type_option_data = 3;
     oneof one_of_start_field_id { string start_field_id = 4; };
 }
+message UpdateFieldTypeOptionPayload {
+    string grid_id = 1;
+    string field_id = 2;
+    bytes type_option_data = 3;
+}
 message QueryFieldPayload {
     string grid_id = 1;
     RepeatedFieldOrder field_orders = 2;