Jelajahi Sumber

Merge pull request #505 from AppFlowy-IO/fix_0.0.4_bugs_2

Fix some UI bugs
Nathan.fooo 3 tahun lalu
induk
melakukan
1ca602fb72
33 mengubah file dengan 755 tambahan dan 528 penghapusan
  1. 1 0
      frontend/app_flowy/assets/translations/en.json
  2. 19 1
      frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart
  3. 1 3
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart
  4. 2 2
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart
  5. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart
  6. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart
  7. 13 6
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart
  8. 3 3
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart
  9. 12 13
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart
  10. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart
  11. 0 44
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart
  12. 25 22
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart
  13. 16 13
      frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart
  14. 6 3
      frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart
  15. 1 1
      frontend/rust-lib/flowy-grid/Flowy.toml
  16. 1 1
      frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs
  17. 0 0
      frontend/rust-lib/flowy-grid/src/entities/field_entities.rs
  18. 0 0
      frontend/rust-lib/flowy-grid/src/entities/mod.rs
  19. 0 0
      frontend/rust-lib/flowy-grid/src/entities/row_entities.rs
  20. 1 1
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  21. 1 0
      frontend/rust-lib/flowy-grid/src/lib.rs
  22. 64 42
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs
  23. 190 115
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs
  24. 85 113
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs
  25. 100 52
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs
  26. 41 33
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs
  27. 1 1
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  28. 0 1
      frontend/rust-lib/flowy-grid/src/services/mod.rs
  29. 125 35
      frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs
  30. 5 3
      frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs
  31. 2 3
      frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs
  32. 28 0
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  33. 9 14
      shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs

+ 1 - 0
frontend/app_flowy/assets/translations/en.json

@@ -168,6 +168,7 @@
       "dateFormatLocal": "Month/Month/Day",
       "dateFormatUS": "Month/Month/Day",
       "timeFormat": " Time format",
+      "invalidTimeFormat": "Invalid format",
       "timeFormatTwelveHour": "12 hour",
       "timeFormatTwentyFourHour": "24 hour",
       "addSelectOption": "Add an option",

+ 19 - 1
frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart

@@ -1,6 +1,9 @@
+import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
+import 'package:easy_localization/easy_localization.dart' show StringTranslateExtension;
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 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';
@@ -91,7 +94,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
           case ErrorCode.InvalidDateTimeFormat:
             emit(state.copyWith(
               dateData: Some(newDateData),
-              timeFormatError: Some(err.toString()),
+              timeFormatError: Some(timeFormatPrompt(err)),
             ));
             break;
           default:
@@ -101,6 +104,21 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
     );
   }
 
+  String timeFormatPrompt(FlowyError error) {
+    String msg = LocaleKeys.grid_field_invalidTimeFormat.tr() + ". ";
+    switch (state.dateTypeOption.timeFormat) {
+      case TimeFormat.TwelveHour:
+        msg = msg + "e.g. 01: 00 AM";
+        break;
+      case TimeFormat.TwentyFourHour:
+        msg = msg + "e.g. 13: 00";
+        break;
+      default:
+        break;
+    }
+    return msg;
+  }
+
   @override
   Future<void> close() async {
     if (_onCellChangedFn != null) {

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

@@ -47,13 +47,11 @@ class BlankCell extends StatelessWidget {
   }
 }
 
-abstract class GridCellWidget extends HoverWidget {
+abstract class GridCellWidget implements FlowyHoverWidget {
   @override
   final ValueNotifier<bool> onFocus = ValueNotifier<bool>(false);
 
   final GridCellRequestFocusNotifier requestFocus = GridCellRequestFocusNotifier();
-
-  GridCellWidget({Key? key}) : super(key: key);
 }
 
 class GridCellRequestFocusNotifier extends ChangeNotifier {

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

@@ -6,7 +6,7 @@ import 'package:flutter/widgets.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'cell_builder.dart';
 
-class CheckboxCell extends GridCellWidget {
+class CheckboxCell extends StatefulWidget with GridCellWidget {
   final GridCellContextBuilder cellContextBuilder;
   CheckboxCell({
     required this.cellContextBuilder,
@@ -41,7 +41,7 @@ class _CheckboxCellState extends State<CheckboxCell> {
               onPressed: () => context.read<CheckboxCellBloc>().add(const CheckboxCellEvent.select()),
               iconPadding: EdgeInsets.zero,
               icon: icon,
-              width: 23,
+              width: 20,
             ),
           );
         },

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

@@ -18,7 +18,7 @@ abstract class GridCellDelegate {
   GridCellDelegate get delegate;
 }
 
-class DateCell extends GridCellWidget {
+class DateCell extends StatefulWidget with GridCellWidget {
   final GridCellContextBuilder cellContextBuilder;
   late final DateCellStyle? cellStyle;
 

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

@@ -7,7 +7,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 
 import 'cell_builder.dart';
 
-class NumberCell extends GridCellWidget {
+class NumberCell extends StatefulWidget with GridCellWidget {
   final GridCellContextBuilder cellContextBuilder;
 
   NumberCell({

+ 13 - 6
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart

@@ -87,7 +87,7 @@ class SelectOptionTag extends StatelessWidget {
   Widget build(BuildContext context) {
     return ChoiceChip(
       pressElevation: 1,
-      label: FlowyText.medium(name, fontSize: 12),
+      label: FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis),
       selectedColor: color,
       backgroundColor: color,
       labelPadding: const EdgeInsets.symmetric(horizontal: 6),
@@ -130,11 +130,18 @@ class SelectOptionTagCell extends StatelessWidget {
           child: InkWell(
             child: Padding(
               padding: const EdgeInsets.symmetric(horizontal: 3),
-              child: Row(children: [
-                SelectOptionTag.fromSelectOption(context: context, option: option),
-                const Spacer(),
-                ...children,
-              ]),
+              child: Row(
+                mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                children: [
+                  Flexible(
+                    fit: FlexFit.loose,
+                    flex: 2,
+                    child: SelectOptionTag.fromSelectOption(context: context, option: option),
+                  ),
+                  const Spacer(),
+                  ...children,
+                ],
+              ),
             ),
             onTap: () => onSelected(option),
           ),

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

@@ -20,7 +20,7 @@ class SelectOptionCellStyle extends GridCellStyle {
   });
 }
 
-class SingleSelectCell extends GridCellWidget {
+class SingleSelectCell extends StatefulWidget with GridCellWidget {
   final GridCellContextBuilder cellContextBuilder;
   late final SelectOptionCellStyle? cellStyle;
 
@@ -74,7 +74,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
 }
 
 //----------------------------------------------------------------
-class MultiSelectCell extends GridCellWidget {
+class MultiSelectCell extends StatefulWidget with GridCellWidget {
   final GridCellContextBuilder cellContextBuilder;
   late final SelectOptionCellStyle? cellStyle;
 
@@ -160,7 +160,7 @@ class _SelectOptionCell extends StatelessWidget {
           .toList();
       child = Align(
         alignment: Alignment.centerLeft,
-        child: Wrap(children: tags, spacing: 4, runSpacing: 4),
+        child: Wrap(children: tags, spacing: 4, runSpacing: 2),
       );
     }
 

+ 12 - 13
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart

@@ -225,7 +225,18 @@ class _SelectOptionCell extends StatelessWidget {
       height: GridSize.typeOptionItemHeight,
       child: Row(
         children: [
-          Expanded(child: _body(theme, context)),
+          Flexible(
+            fit: FlexFit.loose,
+            child: SelectOptionTagCell(
+              option: option,
+              onSelected: (option) {
+                context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.selectOption(option.id));
+              },
+              children: [
+                if (isSelected) svgWidget("grid/checkmark"),
+              ],
+            ),
+          ),
           FlowyIconButton(
             width: 30,
             onPressed: () => _showEditPannel(context),
@@ -237,18 +248,6 @@ class _SelectOptionCell extends StatelessWidget {
     );
   }
 
-  Widget _body(AppTheme theme, BuildContext context) {
-    return SelectOptionTagCell(
-      option: option,
-      onSelected: (option) {
-        context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.selectOption(option.id));
-      },
-      children: [
-        if (isSelected) svgWidget("grid/checkmark"),
-      ],
-    );
-  }
-
   void _showEditPannel(BuildContext context) {
     final pannel = SelectOptionTypeOptionEditor(
       option: option,

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

@@ -13,7 +13,7 @@ class GridTextCellStyle extends GridCellStyle {
   });
 }
 
-class GridTextCell extends GridCellWidget {
+class GridTextCell extends StatefulWidget with GridCellWidget {
   final GridCellContextBuilder cellContextBuilder;
   late final GridTextCellStyle? cellStyle;
   GridTextCell({

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

@@ -1,44 +0,0 @@
-import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/workspace/application/grid/prelude.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-class NumberCell extends StatefulWidget {
-  final GridCell cellData;
-
-  const NumberCell({
-    required this.cellData,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  State<NumberCell> createState() => _NumberCellState();
-}
-
-class _NumberCellState extends State<NumberCell> {
-  late NumberCellBloc _cellBloc;
-
-  @override
-  void initState() {
-    _cellBloc = getIt<NumberCellBloc>(param1: widget.cellData);
-    super.initState();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return BlocProvider.value(
-      value: _cellBloc,
-      child: BlocBuilder<NumberCellBloc, NumberCellState>(
-        builder: (context, state) {
-          return Container();
-        },
-      ),
-    );
-  }
-
-  @override
-  Future<void> dispose() async {
-    _cellBloc.close();
-    super.dispose();
-  }
-}

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

@@ -67,14 +67,15 @@ class _RowDetailPageState extends State<RowDetailPage> {
         return bloc;
       },
       child: Padding(
-        padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 40),
+        padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
         child: Column(
           children: [
             SizedBox(
-                height: 40,
-                child: Row(
-                  children: const [Spacer(), _CloseButton()],
-                )),
+              height: 40,
+              child: Row(
+                children: const [Spacer(), _CloseButton()],
+              ),
+            ),
             Expanded(child: _PropertyList(cellCache: widget.cellCache)),
           ],
         ),
@@ -153,24 +154,26 @@ class _RowDetailCell extends StatelessWidget {
       cellCache,
       style: _buildCellStyle(theme, gridCell.field.fieldType),
     );
-    return SizedBox(
-      height: 36,
-      child: Row(
-        crossAxisAlignment: CrossAxisAlignment.stretch,
-        mainAxisAlignment: MainAxisAlignment.center,
-        children: [
-          SizedBox(
-            width: 150,
-            child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)),
-          ),
-          const HSpace(10),
-          Expanded(
-            child: FlowyHover2(
-              child: cell,
-              contentPadding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
+    return ConstrainedBox(
+      constraints: const BoxConstraints(minHeight: 40),
+      child: IntrinsicHeight(
+        child: Row(
+          crossAxisAlignment: CrossAxisAlignment.stretch,
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            SizedBox(
+              width: 150,
+              child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)),
             ),
-          ),
-        ],
+            const HSpace(10),
+            Expanded(
+              child: FlowyHover2(
+                child: cell,
+                contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
+              ),
+            ),
+          ],
+        ),
       ),
     );
   }

+ 16 - 13
frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart

@@ -102,14 +102,14 @@ class FlowyHoverContainer extends StatelessWidget {
 }
 
 //
-abstract class HoverWidget extends StatefulWidget {
-  const HoverWidget({Key? key}) : super(key: key);
+abstract class FlowyHoverWidget extends Widget {
+  const FlowyHoverWidget({Key? key}) : super(key: key);
 
-  ValueNotifier<bool> get onFocus;
+  ValueNotifier<bool>? get onFocus;
 }
 
 class FlowyHover2 extends StatefulWidget {
-  final Widget child;
+  final FlowyHoverWidget child;
   final EdgeInsets contentPadding;
   const FlowyHover2({
     required this.child,
@@ -123,24 +123,30 @@ class FlowyHover2 extends StatefulWidget {
 
 class _FlowyHover2State extends State<FlowyHover2> {
   late FlowyHoverState _hoverState;
+  VoidCallback? _listenerFn;
 
   @override
   void initState() {
     _hoverState = FlowyHoverState();
 
-    if (widget.child is HoverWidget) {
-      final hoverWidget = widget.child as HoverWidget;
-      hoverWidget.onFocus.addListener(() {
-        _hoverState.onFocus = hoverWidget.onFocus.value;
-      });
+    listener() {
+      _hoverState.onFocus = widget.child.onFocus?.value ?? false;
     }
 
+    _listenerFn = listener;
+    widget.child.onFocus?.addListener(listener);
+
     super.initState();
   }
 
   @override
   void dispose() {
     _hoverState.dispose();
+
+    if (_listenerFn != null) {
+      widget.child.onFocus?.removeListener(_listenerFn!);
+      _listenerFn = null;
+    }
     super.dispose();
   }
 
@@ -179,10 +185,7 @@ class _HoverBackground extends StatelessWidget {
       builder: (context, state, child) {
         if (state.onHover || state.onFocus) {
           return FlowyHoverContainer(
-            style: HoverStyle(
-              borderRadius: Corners.s6Border,
-              hoverColor: theme.shader6,
-            ),
+            style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6),
           );
         } else {
           return const SizedBox();

+ 6 - 3
frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart

@@ -132,9 +132,12 @@ class _RoundedInputFieldState extends State<RoundedInputField> {
       children.add(
         Align(
           alignment: Alignment.centerLeft,
-          child: Text(
-            widget.errorText,
-            style: widget.style,
+          child: Padding(
+            padding: const EdgeInsets.symmetric(vertical: 4),
+            child: Text(
+              widget.errorText,
+              style: widget.style,
+            ),
           ),
         ),
       );

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

@@ -2,7 +2,7 @@
 proto_crates = [
     "src/event_map.rs",
     "src/services/field/type_options",
-    "src/services/entities",
+    "src/entities",
     "src/dart_notification.rs"
 ]
 event_files = ["src/event_map.rs"]

+ 1 - 1
frontend/rust-lib/flowy-grid/src/services/entities/cell_entities.rs → frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs

@@ -1,4 +1,4 @@
-use crate::services::entities::{FieldIdentifier, FieldIdentifierPayload};
+use crate::entities::{FieldIdentifier, FieldIdentifierPayload};
 use flowy_derive::ProtoBuf;
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::parser::NotEmptyStr;

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


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


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


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

@@ -1,5 +1,5 @@
+use crate::entities::*;
 use crate::manager::GridManager;
-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 flowy_error::{ErrorCode, FlowyError, FlowyResult};

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

@@ -6,6 +6,7 @@ pub mod event_map;
 pub mod manager;
 
 mod dart_notification;
+pub mod entities;
 mod protobuf;
 pub mod services;
 pub mod util;

+ 64 - 42
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs

@@ -1,15 +1,14 @@
 use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData};
+use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData};
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
-use flowy_error::FlowyError;
+use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
     CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
 };
 
 use serde::{Deserialize, Serialize};
-use std::str::FromStr;
 
 #[derive(Default)]
 pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption);
@@ -43,32 +42,38 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox);
 const YES: &str = "Yes";
 const NO: &str = "No";
 
-impl CellDataOperation for CheckboxTypeOption {
-    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData {
-        if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
-            if !type_option_cell_data.is_checkbox() {
-                return DecodedCellData::default();
-            }
-            let cell_data = type_option_cell_data.data;
-            if cell_data == YES || cell_data == NO {
-                return DecodedCellData::from_content(cell_data);
-            }
+impl CellDataOperation<String, String> for CheckboxTypeOption {
+    fn decode_cell_data<T>(
+        &self,
+        encoded_data: T,
+        decoded_field_type: &FieldType,
+        _field_meta: &FieldMeta,
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<String>,
+    {
+        if !decoded_field_type.is_checkbox() {
+            return Ok(DecodedCellData::default());
+        }
+
+        let encoded_data = encoded_data.into();
+        if encoded_data == YES || encoded_data == NO {
+            return Ok(DecodedCellData::from_content(encoded_data));
         }
 
-        DecodedCellData::default()
+        Ok(DecodedCellData::default())
     }
 
-    fn apply_changeset<T: Into<CellContentChangeset>>(
-        &self,
-        changeset: T,
-        _cell_meta: Option<CellMeta>,
-    ) -> Result<String, FlowyError> {
+    fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
+    where
+        C: Into<CellContentChangeset>,
+    {
         let changeset = changeset.into();
         let s = match string_to_bool(&changeset) {
             true => YES,
             false => NO,
         };
-        Ok(TypeOptionCellData::new(s, self.field_type()).json())
+        Ok(s.to_string())
     }
 }
 
@@ -88,32 +93,49 @@ fn string_to_bool(bool_str: &str) -> bool {
 #[cfg(test)]
 mod tests {
     use crate::services::field::type_options::checkbox_type_option::{NO, YES};
-    use crate::services::field::CheckboxTypeOption;
+
     use crate::services::field::FieldBuilder;
-    use crate::services::row::CellDataOperation;
+    use crate::services::row::{apply_cell_data_changeset, decode_cell_data_from_type_option_cell_data};
+
     use flowy_grid_data_model::entities::FieldType;
 
     #[test]
     fn checkout_box_description_test() {
-        let type_option = CheckboxTypeOption::default();
-        let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
-
-        let data = type_option.apply_changeset("true", None).unwrap();
-        assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES);
-
-        let data = type_option.apply_changeset("1", None).unwrap();
-        assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES);
-
-        let data = type_option.apply_changeset("yes", None).unwrap();
-        assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES);
-
-        let data = type_option.apply_changeset("false", None).unwrap();
-        assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO);
-
-        let data = type_option.apply_changeset("no", None).unwrap();
-        assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO);
-
-        let data = type_option.apply_changeset("123", None).unwrap();
-        assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO);
+        let field_meta = FieldBuilder::from_field_type(&FieldType::Checkbox).build();
+        let data = apply_cell_data_changeset("true", None, &field_meta).unwrap();
+        assert_eq!(
+            decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
+            YES
+        );
+
+        let data = apply_cell_data_changeset("1", None, &field_meta).unwrap();
+        assert_eq!(
+            decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
+            YES
+        );
+
+        let data = apply_cell_data_changeset("yes", None, &field_meta).unwrap();
+        assert_eq!(
+            decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
+            YES
+        );
+
+        let data = apply_cell_data_changeset("false", None, &field_meta).unwrap();
+        assert_eq!(
+            decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
+            NO
+        );
+
+        let data = apply_cell_data_changeset("no", None, &field_meta).unwrap();
+        assert_eq!(
+            decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
+            NO
+        );
+
+        let data = apply_cell_data_changeset("12", None, &field_meta).unwrap();
+        assert_eq!(
+            decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
+            NO
+        );
     }
 }

+ 190 - 115
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs

@@ -1,7 +1,9 @@
+use crate::entities::{CellIdentifier, CellIdentifierPayload};
 use crate::impl_type_option;
-use crate::services::entities::{CellIdentifier, CellIdentifierPayload};
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData};
+use crate::services::row::{
+    CellContentChangeset, CellDataOperation, DecodedCellData, EncodedCellData, TypeOptionCellData,
+};
 use bytes::Bytes;
 use chrono::format::strftime::StrftimeItems;
 use chrono::NaiveDateTime;
@@ -90,10 +92,10 @@ impl DateTypeOption {
 
         let serde_cell_data = DateCellDataSerde::from_str(&result.unwrap().data)?;
         let date = self.decode_cell_data_from_timestamp(&serde_cell_data).content;
-        let time = serde_cell_data.time.unwrap_or("".to_owned());
+        let time = serde_cell_data.time.unwrap_or_else(|| "".to_owned());
         let timestamp = serde_cell_data.timestamp;
 
-        return Ok(DateCellData { date, time, timestamp });
+        Ok(DateCellData { date, time, timestamp })
     }
 
     fn decode_cell_data_from_timestamp(&self, serde_cell_data: &DateCellDataSerde) -> DecodedCellData {
@@ -102,7 +104,7 @@ impl DateTypeOption {
         }
 
         let cell_content = self.today_desc_from_timestamp(serde_cell_data.timestamp, &serde_cell_data.time);
-        return DecodedCellData::new(serde_cell_data.timestamp.to_string(), cell_content);
+        DecodedCellData::new(serde_cell_data.timestamp.to_string(), cell_content)
     }
 
     fn timestamp_from_utc_with_time(
@@ -131,34 +133,36 @@ impl DateTypeOption {
             }
         }
 
-        return Ok(utc.timestamp());
+        Ok(utc.timestamp())
     }
 }
 
-impl CellDataOperation for DateTypeOption {
-    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData {
-        if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
-            // Return default data if the type_option_cell_data is not FieldType::DateTime.
-            // It happens when switching from one field to another.
-            // For example:
-            // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen.
-            if !type_option_cell_data.is_date() {
-                return DecodedCellData::default();
-            }
-            return match DateCellDataSerde::from_str(&type_option_cell_data.data) {
-                Ok(serde_cell_data) => self.decode_cell_data_from_timestamp(&serde_cell_data),
-                Err(_) => DecodedCellData::default(),
-            };
+impl CellDataOperation<EncodedCellData<DateCellDataSerde>, DateCellDataSerde> for DateTypeOption {
+    fn decode_cell_data<T>(
+        &self,
+        encoded_data: T,
+        decoded_field_type: &FieldType,
+        _field_meta: &FieldMeta,
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<EncodedCellData<DateCellDataSerde>>,
+    {
+        // Return default data if the type_option_cell_data is not FieldType::DateTime.
+        // It happens when switching from one field to another.
+        // For example:
+        // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen.
+        if !decoded_field_type.is_date() {
+            return Ok(DecodedCellData::default());
         }
 
-        DecodedCellData::default()
+        let encoded_data = encoded_data.into().try_into_inner()?;
+        Ok(self.decode_cell_data_from_timestamp(&encoded_data))
     }
 
-    fn apply_changeset<T: Into<CellContentChangeset>>(
-        &self,
-        changeset: T,
-        _cell_meta: Option<CellMeta>,
-    ) -> Result<String, FlowyError> {
+    fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<DateCellDataSerde, FlowyError>
+    where
+        C: Into<CellContentChangeset>,
+    {
         let content_changeset: DateCellContentChangeset = serde_json::from_str(&changeset.into())?;
         let cell_data = match content_changeset.date_timestamp() {
             None => DateCellDataSerde::default(),
@@ -173,7 +177,7 @@ impl CellDataOperation for DateTypeOption {
             },
         };
 
-        Ok(TypeOptionCellData::new(cell_data.to_string(), self.field_type()).json())
+        Ok(cell_data)
     }
 }
 
@@ -307,23 +311,29 @@ impl DateCellDataSerde {
     fn new(timestamp: i64, time: Option<String>, time_format: &TimeFormat) -> Self {
         Self {
             timestamp,
-            time: Some(time.unwrap_or(default_time_str(time_format))),
+            time: Some(time.unwrap_or_else(|| default_time_str(time_format))),
         }
     }
 
     pub(crate) fn from_timestamp(timestamp: i64, time: Option<String>) -> Self {
         Self { timestamp, time }
     }
+}
 
-    fn to_string(self) -> String {
-        serde_json::to_string(&self).unwrap_or("".to_string())
-    }
+impl FromStr for DateCellDataSerde {
+    type Err = FlowyError;
 
-    fn from_str(s: &str) -> FlowyResult<Self> {
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
         serde_json::from_str::<DateCellDataSerde>(s).map_err(internal_error)
     }
 }
 
+impl ToString for DateCellDataSerde {
+    fn to_string(&self) -> String {
+        serde_json::to_string(&self).unwrap_or_else(|_| "".to_string())
+    }
+}
+
 fn default_time_str(time_format: &TimeFormat) -> String {
     match time_format {
         TimeFormat::TwelveHour => "12:00 AM".to_string(),
@@ -407,52 +417,64 @@ impl std::convert::From<DateCellContentChangeset> for CellContentChangeset {
 #[cfg(test)]
 mod tests {
     use crate::services::field::FieldBuilder;
-    use crate::services::field::{
-        DateCellContentChangeset, DateCellData, DateCellDataSerde, DateFormat, DateTypeOption, TimeFormat,
+    use crate::services::field::{DateCellContentChangeset, DateCellDataSerde, DateFormat, DateTypeOption, TimeFormat};
+    use crate::services::row::{
+        apply_cell_data_changeset, decode_cell_data_from_type_option_cell_data, CellDataOperation, EncodedCellData,
     };
-    use crate::services::row::{CellDataOperation, TypeOptionCellData};
-    use flowy_grid_data_model::entities::FieldType;
+    use flowy_grid_data_model::entities::{FieldMeta, FieldType};
     use strum::IntoEnumIterator;
 
     #[test]
     fn date_description_invalid_input_test() {
-        let type_option = DateTypeOption::default();
         let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        let data = apply_cell_data_changeset("1e", None, &field_meta).unwrap();
         assert_eq!(
-            "".to_owned(),
-            type_option.decode_cell_data("1e".to_owned(), &field_meta).content
+            decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
+            "".to_owned()
         );
     }
 
     #[test]
     fn date_description_date_format_test() {
         let mut type_option = DateTypeOption::default();
-        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
         for date_format in DateFormat::iter() {
             type_option.date_format = date_format;
             match date_format {
                 DateFormat::Friendly => {
                     assert_eq!(
                         "Mar 14,2022".to_owned(),
-                        type_option.decode_cell_data(data(1647251762), &field_meta).content
+                        type_option
+                            .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
+                            .unwrap()
+                            .content
                     );
                 }
                 DateFormat::US => {
                     assert_eq!(
                         "2022/03/14".to_owned(),
-                        type_option.decode_cell_data(data(1647251762), &field_meta).content
+                        type_option
+                            .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
+                            .unwrap()
+                            .content
                     );
                 }
                 DateFormat::ISO => {
                     assert_eq!(
                         "2022-03-14".to_owned(),
-                        type_option.decode_cell_data(data(1647251762), &field_meta).content
+                        type_option
+                            .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
+                            .unwrap()
+                            .content
                     );
                 }
                 DateFormat::Local => {
                     assert_eq!(
                         "2022/03/14".to_owned(),
-                        type_option.decode_cell_data(data(1647251762), &field_meta).content
+                        type_option
+                            .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
+                            .unwrap()
+                            .content
                     );
                 }
             }
@@ -462,7 +484,7 @@ mod tests {
     #[test]
     fn date_description_time_format_test() {
         let mut type_option = DateTypeOption::default();
-        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
         for time_format in TimeFormat::iter() {
             type_option.time_format = time_format;
             match time_format {
@@ -473,7 +495,10 @@ mod tests {
                     );
                     assert_eq!(
                         "Mar 14,2022".to_owned(),
-                        type_option.decode_cell_data(data(1647251762), &field_meta).content
+                        type_option
+                            .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
+                            .unwrap()
+                            .content
                     );
                 }
                 TimeFormat::TwelveHour => {
@@ -483,7 +508,10 @@ mod tests {
                     );
                     assert_eq!(
                         "Mar 14,2022".to_owned(),
-                        type_option.decode_cell_data(data(1647251762), &field_meta).content
+                        type_option
+                            .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
+                            .unwrap()
+                            .content
                     );
                 }
             }
@@ -493,53 +521,68 @@ mod tests {
     #[test]
     fn date_description_time_format_test2() {
         let mut type_option = DateTypeOption::default();
-        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        let field_type = FieldType::DateTime;
+        let field_meta = FieldBuilder::from_field_type(&field_type).build();
+
         for time_format in TimeFormat::iter() {
             type_option.time_format = time_format;
             type_option.include_time = true;
             match time_format {
                 TimeFormat::TwentyFourHour => {
-                    let changeset = DateCellContentChangeset {
-                        date: Some(1653609600.to_string()),
-                        time: None,
-                    };
-                    let result = type_option.apply_changeset(changeset, None).unwrap();
-                    let content = type_option.decode_cell_data(result, &field_meta).content;
-                    assert_eq!("May 27,2022 00:00".to_owned(), content);
-
-                    let changeset = DateCellContentChangeset {
-                        date: Some(1653609600.to_string()),
-                        time: Some("23:00".to_owned()),
-                    };
-
-                    let result = type_option.apply_changeset(changeset, None).unwrap();
-                    let content = type_option.decode_cell_data(result, &field_meta).content;
-                    assert_eq!("May 27,2022 23:00".to_owned(), content);
+                    assert_result(
+                        &type_option,
+                        DateCellContentChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: None,
+                        },
+                        &field_type,
+                        &field_meta,
+                        "May 27,2022 00:00",
+                    );
+                    assert_result(
+                        &type_option,
+                        DateCellContentChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: Some("23:00".to_owned()),
+                        },
+                        &field_type,
+                        &field_meta,
+                        "May 27,2022 23:00",
+                    );
                 }
                 TimeFormat::TwelveHour => {
-                    let changeset = DateCellContentChangeset {
-                        date: Some(1653609600.to_string()),
-                        time: None,
-                    };
-                    let result = type_option.apply_changeset(changeset, None).unwrap();
-                    let content = type_option.decode_cell_data(result, &field_meta).content;
-                    assert_eq!("May 27,2022 12:00 AM".to_owned(), content);
-
-                    let changeset = DateCellContentChangeset {
-                        date: Some(1653609600.to_string()),
-                        time: Some("".to_owned()),
-                    };
-                    let result = type_option.apply_changeset(changeset, None).unwrap();
-                    let content = type_option.decode_cell_data(result, &field_meta).content;
-                    assert_eq!("May 27,2022".to_owned(), content);
-
-                    let changeset = DateCellContentChangeset {
-                        date: Some(1653609600.to_string()),
-                        time: Some("11:23 pm".to_owned()),
-                    };
-                    let result = type_option.apply_changeset(changeset, None).unwrap();
-                    let content = type_option.decode_cell_data(result, &field_meta).content;
-                    assert_eq!("May 27,2022 11:23 PM".to_owned(), content);
+                    assert_result(
+                        &type_option,
+                        DateCellContentChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: None,
+                        },
+                        &field_type,
+                        &field_meta,
+                        "May 27,2022 12:00 AM",
+                    );
+
+                    assert_result(
+                        &type_option,
+                        DateCellContentChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: Some("".to_owned()),
+                        },
+                        &field_type,
+                        &field_meta,
+                        "May 27,2022",
+                    );
+
+                    assert_result(
+                        &type_option,
+                        DateCellContentChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: Some("11:23 pm".to_owned()),
+                        },
+                        &field_type,
+                        &field_meta,
+                        "May 27,2022 11:23 PM",
+                    );
                 }
             }
         }
@@ -548,37 +591,55 @@ mod tests {
     #[test]
     fn date_description_apply_changeset_test() {
         let mut type_option = DateTypeOption::default();
-        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        let field_type = FieldType::DateTime;
+        let field_meta = FieldBuilder::from_field_type(&field_type).build();
         let date_timestamp = "1653609600".to_owned();
 
-        let changeset = DateCellContentChangeset {
-            date: Some(date_timestamp.clone()),
-            time: None,
-        };
-        let result = type_option.apply_changeset(changeset, None).unwrap();
-        let content = type_option.decode_cell_data(result.clone(), &field_meta).content;
-        assert_eq!(content, "May 27,2022".to_owned());
+        assert_result(
+            &type_option,
+            DateCellContentChangeset {
+                date: Some(date_timestamp.clone()),
+                time: None,
+            },
+            &field_type,
+            &field_meta,
+            "May 27,2022",
+        );
 
         type_option.include_time = true;
-        let content = type_option.decode_cell_data(result, &field_meta).content;
-        assert_eq!(content, "May 27,2022 00:00".to_owned());
+        assert_result(
+            &type_option,
+            DateCellContentChangeset {
+                date: Some(date_timestamp.clone()),
+                time: None,
+            },
+            &field_type,
+            &field_meta,
+            "May 27,2022 00:00",
+        );
 
-        let changeset = DateCellContentChangeset {
-            date: Some(date_timestamp.clone()),
-            time: Some("1:00".to_owned()),
-        };
-        let result = type_option.apply_changeset(changeset, None).unwrap();
-        let content = type_option.decode_cell_data(result, &field_meta).content;
-        assert_eq!(content, "May 27,2022 01:00".to_owned());
+        assert_result(
+            &type_option,
+            DateCellContentChangeset {
+                date: Some(date_timestamp.clone()),
+                time: Some("1:00".to_owned()),
+            },
+            &field_type,
+            &field_meta,
+            "May 27,2022 01:00",
+        );
 
-        let changeset = DateCellContentChangeset {
-            date: Some(date_timestamp),
-            time: Some("1:00 am".to_owned()),
-        };
         type_option.time_format = TimeFormat::TwelveHour;
-        let result = type_option.apply_changeset(changeset, None).unwrap();
-        let content = type_option.decode_cell_data(result, &field_meta).content;
-        assert_eq!(content, "May 27,2022 01:00 AM".to_owned());
+        assert_result(
+            &type_option,
+            DateCellContentChangeset {
+                date: Some(date_timestamp),
+                time: Some("1:00 am".to_owned()),
+            },
+            &field_type,
+            &field_meta,
+            "May 27,2022 01:00 AM",
+        );
     }
 
     #[test]
@@ -586,7 +647,7 @@ mod tests {
     fn date_description_apply_changeset_error_test() {
         let mut type_option = DateTypeOption::default();
         type_option.include_time = true;
-        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        let _field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
         let date_timestamp = "1653609600".to_owned();
 
         let changeset = DateCellContentChangeset {
@@ -596,7 +657,7 @@ mod tests {
         let _ = type_option.apply_changeset(changeset, None).unwrap();
 
         let changeset = DateCellContentChangeset {
-            date: Some(date_timestamp.clone()),
+            date: Some(date_timestamp),
             time: Some("1:".to_owned()),
         };
         let _ = type_option.apply_changeset(changeset, None).unwrap();
@@ -610,7 +671,21 @@ mod tests {
     }
 
     fn data(s: i64) -> String {
-        let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(s, None)).unwrap();
-        TypeOptionCellData::new(&json, FieldType::DateTime).json()
+        serde_json::to_string(&DateCellDataSerde::from_timestamp(s, None)).unwrap()
+    }
+
+    fn assert_result(
+        type_option: &DateTypeOption,
+        changeset: DateCellContentChangeset,
+        field_type: &FieldType,
+        field_meta: &FieldMeta,
+        expected: &str,
+    ) {
+        let encoded_data = EncodedCellData(Some(type_option.apply_changeset(changeset, None).unwrap()));
+        let content = type_option
+            .decode_cell_data(encoded_data, field_type, field_meta)
+            .unwrap()
+            .content;
+        assert_eq!(expected.to_owned(), content);
     }
 }

+ 85 - 113
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs

@@ -1,9 +1,9 @@
 use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData};
+use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData};
 use bytes::Bytes;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-use flowy_error::FlowyError;
+use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
     CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
 };
@@ -76,45 +76,48 @@ pub struct NumberTypeOption {
 }
 impl_type_option!(NumberTypeOption, FieldType::Number);
 
-impl CellDataOperation for NumberTypeOption {
-    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData {
-        if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
-            if type_option_cell_data.is_date() {
-                return DecodedCellData::default();
-            }
-
-            let cell_data = type_option_cell_data.data;
-            match self.format {
-                NumberFormat::Number => {
-                    if let Ok(v) = cell_data.parse::<f64>() {
-                        return DecodedCellData::from_content(v.to_string());
-                    }
-
-                    if let Ok(v) = cell_data.parse::<i64>() {
-                        return DecodedCellData::from_content(v.to_string());
-                    }
+impl CellDataOperation<String, String> for NumberTypeOption {
+    fn decode_cell_data<T>(
+        &self,
+        encoded_data: T,
+        decoded_field_type: &FieldType,
+        _field_meta: &FieldMeta,
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<String>,
+    {
+        if decoded_field_type.is_date() {
+            return Ok(DecodedCellData::default());
+        }
 
-                    DecodedCellData::default()
-                }
-                NumberFormat::Percent => {
-                    let content = cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string());
-                    DecodedCellData::from_content(content)
+        let cell_data = encoded_data.into();
+        match self.format {
+            NumberFormat::Number => {
+                if let Ok(v) = cell_data.parse::<f64>() {
+                    return Ok(DecodedCellData::from_content(v.to_string()));
                 }
-                _ => {
-                    let content = self.money_from_str(&cell_data);
-                    DecodedCellData::from_content(content)
+
+                if let Ok(v) = cell_data.parse::<i64>() {
+                    return Ok(DecodedCellData::from_content(v.to_string()));
                 }
+
+                Ok(DecodedCellData::default())
+            }
+            NumberFormat::Percent => {
+                let content = cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string());
+                Ok(DecodedCellData::from_content(content))
+            }
+            _ => {
+                let content = self.money_from_str(&cell_data);
+                Ok(DecodedCellData::from_content(content))
             }
-        } else {
-            DecodedCellData::default()
         }
     }
 
-    fn apply_changeset<T: Into<CellContentChangeset>>(
-        &self,
-        changeset: T,
-        _cell_meta: Option<CellMeta>,
-    ) -> Result<String, FlowyError> {
+    fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
+    where
+        C: Into<CellContentChangeset>,
+    {
         let changeset = changeset.into();
         let mut data = changeset.trim().to_string();
 
@@ -125,7 +128,7 @@ impl CellDataOperation for NumberTypeOption {
             }
         }
 
-        Ok(TypeOptionCellData::new(&data, self.field_type()).json())
+        Ok(data)
     }
 }
 
@@ -619,28 +622,24 @@ fn make_strip_symbol() -> Vec<String> {
 mod tests {
     use crate::services::field::FieldBuilder;
     use crate::services::field::{NumberFormat, NumberTypeOption};
-    use crate::services::row::{CellDataOperation, TypeOptionCellData};
-    use flowy_grid_data_model::entities::FieldType;
+    use crate::services::row::CellDataOperation;
+    use flowy_grid_data_model::entities::{FieldMeta, FieldType};
     use strum::IntoEnumIterator;
 
     #[test]
     fn number_description_invalid_input_test() {
         let type_option = NumberTypeOption::default();
-        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
-        assert_eq!(
-            "".to_owned(),
-            type_option.decode_cell_data(data(""), &field_meta).content
-        );
-        assert_eq!(
-            "".to_owned(),
-            type_option.decode_cell_data(data("abc"), &field_meta).content
-        );
+        let field_type = FieldType::Number;
+        let field_meta = FieldBuilder::from_field_type(&field_type).build();
+        assert_equal(&type_option, "", "", &field_type, &field_meta);
+        assert_equal(&type_option, "abc", "", &field_type, &field_meta);
     }
 
     #[test]
     fn number_description_test() {
         let mut type_option = NumberTypeOption::default();
-        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        let field_type = FieldType::Number;
+        let field_meta = FieldBuilder::from_field_type(&field_type).build();
         assert_eq!(type_option.strip_symbol("¥18,443"), "18443".to_owned());
         assert_eq!(type_option.strip_symbol("$18,443"), "18443".to_owned());
         assert_eq!(type_option.strip_symbol("€18.443"), "18443".to_owned());
@@ -649,86 +648,50 @@ mod tests {
             type_option.format = format;
             match format {
                 NumberFormat::Number => {
-                    assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta).content,
-                        "18443".to_owned()
-                    );
+                    assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
                 }
                 NumberFormat::USD => {
-                    assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta).content,
-                        "$18,443".to_owned()
-                    );
-                    assert_eq!(
-                        type_option.decode_cell_data(data(""), &field_meta).content,
-                        "".to_owned()
-                    );
-                    assert_eq!(
-                        type_option.decode_cell_data(data("abc"), &field_meta).content,
-                        "".to_owned()
-                    );
+                    assert_equal(&type_option, "18443", "$18,443", &field_type, &field_meta);
+                    assert_equal(&type_option, "", "", &field_type, &field_meta);
+                    assert_equal(&type_option, "abc", "", &field_type, &field_meta);
                 }
                 NumberFormat::Yen => {
-                    assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta).content,
-                        "¥18,443".to_owned()
-                    );
+                    assert_equal(&type_option, "18443", "¥18,443", &field_type, &field_meta);
                 }
                 NumberFormat::Yuan => {
-                    assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta).content,
-                        "CN¥18,443".to_owned()
-                    );
+                    assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_meta);
                 }
                 NumberFormat::EUR => {
-                    assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta).content,
-                        "€18.443".to_owned()
-                    );
+                    assert_equal(&type_option, "18443", "€18.443", &field_type, &field_meta);
                 }
                 _ => {}
             }
         }
     }
 
-    fn data(s: &str) -> String {
-        TypeOptionCellData::new(s, FieldType::Number).json()
-    }
-
     #[test]
     fn number_description_scale_test() {
         let mut type_option = NumberTypeOption {
             scale: 1,
             ..Default::default()
         };
-        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        let field_type = FieldType::Number;
+        let field_meta = FieldBuilder::from_field_type(&field_type).build();
 
         for format in NumberFormat::iter() {
             type_option.format = format;
             match format {
                 NumberFormat::Number => {
-                    assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta).content,
-                        "18443".to_owned()
-                    );
+                    assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
                 }
                 NumberFormat::USD => {
-                    assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta).content,
-                        "$1,844.3".to_owned()
-                    );
+                    assert_equal(&type_option, "18443", "$1,844.3", &field_type, &field_meta);
                 }
                 NumberFormat::Yen => {
-                    assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta).content,
-                        "¥1,844.3".to_owned()
-                    );
+                    assert_equal(&type_option, "18443", "¥1,844.3", &field_type, &field_meta);
                 }
                 NumberFormat::EUR => {
-                    assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta).content,
-                        "€1.844,3".to_owned()
-                    );
+                    assert_equal(&type_option, "18443", "€1.844,3", &field_type, &field_meta);
                 }
                 _ => {}
             }
@@ -741,37 +704,46 @@ mod tests {
             sign_positive: false,
             ..Default::default()
         };
-        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        let field_type = FieldType::Number;
+        let field_meta = FieldBuilder::from_field_type(&field_type).build();
 
         for format in NumberFormat::iter() {
             type_option.format = format;
             match format {
                 NumberFormat::Number => {
-                    assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta).content,
-                        "18443".to_owned()
-                    );
+                    assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
                 }
                 NumberFormat::USD => {
-                    assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta).content,
-                        "-$18,443".to_owned()
-                    );
+                    assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_meta);
                 }
                 NumberFormat::Yen => {
-                    assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta).content,
-                        "-¥18,443".to_owned()
-                    );
+                    assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_meta);
                 }
                 NumberFormat::EUR => {
-                    assert_eq!(
-                        type_option.decode_cell_data(data("18443"), &field_meta).content,
-                        "-€18.443".to_owned()
-                    );
+                    assert_equal(&type_option, "18443", "-€18.443", &field_type, &field_meta);
                 }
                 _ => {}
             }
         }
     }
+
+    fn assert_equal(
+        type_option: &NumberTypeOption,
+        cell_data: &str,
+        expected_str: &str,
+        field_type: &FieldType,
+        field_meta: &FieldMeta,
+    ) {
+        assert_eq!(
+            type_option
+                .decode_cell_data(data(cell_data), field_type, field_meta)
+                .unwrap()
+                .content,
+            expected_str.to_owned()
+        );
+    }
+
+    fn data(s: &str) -> String {
+        s.to_owned()
+    }
 }

+ 100 - 52
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs

@@ -1,5 +1,5 @@
+use crate::entities::{CellIdentifier, CellIdentifierPayload};
 use crate::impl_type_option;
-use crate::services::entities::{CellIdentifier, CellIdentifierPayload};
 use crate::services::field::type_options::util::get_cell_data;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
 use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData};
@@ -95,29 +95,36 @@ impl SelectOptionOperation for SingleSelectTypeOption {
     }
 }
 
-impl CellDataOperation for SingleSelectTypeOption {
-    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData {
-        if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
-            if !type_option_cell_data.is_single_select() {
-                return DecodedCellData::default();
-            }
-
-            if let Some(option_id) = select_option_ids(type_option_cell_data.data).first() {
-                return match self.options.iter().find(|option| &option.id == option_id) {
-                    None => DecodedCellData::default(),
-                    Some(option) => DecodedCellData::from_content(option.name.clone()),
-                };
-            }
+impl CellDataOperation<String, String> for SingleSelectTypeOption {
+    fn decode_cell_data<T>(
+        &self,
+        encoded_data: T,
+        decoded_field_type: &FieldType,
+        _field_meta: &FieldMeta,
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<String>,
+    {
+        if !decoded_field_type.is_select_option() {
+            return Ok(DecodedCellData::default());
         }
 
-        DecodedCellData::default()
+        let cell_data = encoded_data.into();
+        if let Some(option_id) = select_option_ids(cell_data).first() {
+            let data = match self.options.iter().find(|option| &option.id == option_id) {
+                None => DecodedCellData::default(),
+                Some(option) => DecodedCellData::from_content(option.name.clone()),
+            };
+            Ok(data)
+        } else {
+            Ok(DecodedCellData::default())
+        }
     }
 
-    fn apply_changeset<T: Into<CellContentChangeset>>(
-        &self,
-        changeset: T,
-        _cell_meta: Option<CellMeta>,
-    ) -> Result<String, FlowyError> {
+    fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
+    where
+        C: Into<CellContentChangeset>,
+    {
         let changeset = changeset.into();
         let select_option_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset)?;
         let new_cell_data: String;
@@ -129,7 +136,7 @@ impl CellDataOperation for SingleSelectTypeOption {
             new_cell_data = "".to_string()
         }
 
-        Ok(TypeOptionCellData::new(&new_cell_data, self.field_type()).json())
+        Ok(new_cell_data)
     }
 }
 
@@ -184,31 +191,36 @@ impl SelectOptionOperation for MultiSelectTypeOption {
     }
 }
 
-impl CellDataOperation for MultiSelectTypeOption {
-    fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData {
-        if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
-            if !type_option_cell_data.is_multi_select() {
-                return DecodedCellData::default();
-            }
-            let option_ids = select_option_ids(type_option_cell_data.data);
-            let content = self
-                .options
-                .iter()
-                .filter(|option| option_ids.contains(&option.id))
-                .map(|option| option.name.clone())
-                .collect::<Vec<String>>()
-                .join(SELECTION_IDS_SEPARATOR);
-            DecodedCellData::from_content(content)
-        } else {
-            DecodedCellData::default()
+impl CellDataOperation<String, String> for MultiSelectTypeOption {
+    fn decode_cell_data<T>(
+        &self,
+        encoded_data: T,
+        decoded_field_type: &FieldType,
+        _field_meta: &FieldMeta,
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<String>,
+    {
+        if !decoded_field_type.is_select_option() {
+            return Ok(DecodedCellData::default());
         }
+        let cell_data = encoded_data.into();
+        let option_ids = select_option_ids(cell_data);
+        let content = self
+            .options
+            .iter()
+            .filter(|option| option_ids.contains(&option.id))
+            .map(|option| option.name.clone())
+            .collect::<Vec<String>>()
+            .join(SELECTION_IDS_SEPARATOR);
+
+        Ok(DecodedCellData::from_content(content))
     }
 
-    fn apply_changeset<T: Into<CellContentChangeset>>(
-        &self,
-        changeset: T,
-        cell_meta: Option<CellMeta>,
-    ) -> Result<String, FlowyError> {
+    fn apply_changeset<T>(&self, changeset: T, cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
+    where
+        T: Into<CellContentChangeset>,
+    {
         let content_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset.into())?;
         let new_cell_data: String;
         match cell_meta {
@@ -237,7 +249,7 @@ impl CellDataOperation for MultiSelectTypeOption {
             }
         }
 
-        Ok(TypeOptionCellData::new(&new_cell_data, self.field_type()).json())
+        Ok(new_cell_data)
     }
 }
 
@@ -515,14 +527,20 @@ mod tests {
         let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
         let cell_data = type_option.apply_changeset(data, None).unwrap();
         assert_eq!(
-            type_option.decode_cell_data(cell_data, &field_meta).content,
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
             google_option.name,
         );
 
         let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
         let cell_data = type_option.apply_changeset(data, None).unwrap();
         assert_eq!(
-            type_option.decode_cell_data(cell_data, &field_meta).content,
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
             google_option.name,
         );
 
@@ -530,13 +548,25 @@ mod tests {
         let cell_data = type_option
             .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
             .unwrap();
-        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",);
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
+            "",
+        );
 
         // Invalid option id
         let cell_data = type_option
             .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None)
             .unwrap();
-        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",);
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
+            "",
+        );
 
         // Invalid changeset
         assert!(type_option.apply_changeset("123", None).is_err());
@@ -563,14 +593,20 @@ mod tests {
         let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
         let cell_data = type_option.apply_changeset(data, None).unwrap();
         assert_eq!(
-            type_option.decode_cell_data(cell_data, &field_meta).content,
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
             vec![google_option.name.clone(), facebook_option.name].join(SELECTION_IDS_SEPARATOR),
         );
 
         let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
         let cell_data = type_option.apply_changeset(data, None).unwrap();
         assert_eq!(
-            type_option.decode_cell_data(cell_data, &field_meta).content,
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
             google_option.name,
         );
 
@@ -578,13 +614,25 @@ mod tests {
         let cell_data = type_option
             .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
             .unwrap();
-        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",);
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
+            "",
+        );
 
         // Invalid option id
         let cell_data = type_option
             .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None)
             .unwrap();
-        assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",);
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
+            "",
+        );
 
         // Invalid changeset
         assert!(type_option.apply_changeset("123", None).is_err());

+ 41 - 33
frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs

@@ -1,16 +1,13 @@
 use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{
-    decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData,
-};
+use crate::services::row::{decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData};
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
-use flowy_error::FlowyError;
+use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
     CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
 };
 use serde::{Deserialize, Serialize};
-use std::str::FromStr;
 
 #[derive(Default)]
 pub struct RichTextTypeOptionBuilder(RichTextTypeOption);
@@ -34,33 +31,37 @@ pub struct RichTextTypeOption {
 }
 impl_type_option!(RichTextTypeOption, FieldType::RichText);
 
-impl CellDataOperation for RichTextTypeOption {
-    fn decode_cell_data(&self, data: String, field_meta: &FieldMeta) -> DecodedCellData {
-        if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
-            if type_option_cell_data.is_date()
-                || type_option_cell_data.is_single_select()
-                || type_option_cell_data.is_multi_select()
-                || type_option_cell_data.is_number()
-            {
-                decode_cell_data(data, field_meta, &type_option_cell_data.field_type).unwrap_or_default()
-            } else {
-                DecodedCellData::from_content(type_option_cell_data.data)
-            }
+impl CellDataOperation<String, String> for RichTextTypeOption {
+    fn decode_cell_data<T>(
+        &self,
+        encoded_data: T,
+        decoded_field_type: &FieldType,
+        field_meta: &FieldMeta,
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<String>,
+    {
+        if decoded_field_type.is_date()
+            || decoded_field_type.is_single_select()
+            || decoded_field_type.is_multi_select()
+            || decoded_field_type.is_number()
+        {
+            decode_cell_data(encoded_data, decoded_field_type, decoded_field_type, field_meta)
         } else {
-            DecodedCellData::default()
+            let cell_data = encoded_data.into();
+            Ok(DecodedCellData::from_content(cell_data))
         }
     }
 
-    fn apply_changeset<T: Into<CellContentChangeset>>(
-        &self,
-        changeset: T,
-        _cell_meta: Option<CellMeta>,
-    ) -> Result<String, FlowyError> {
+    fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
+    where
+        C: Into<CellContentChangeset>,
+    {
         let data = changeset.into();
         if data.len() > 10000 {
             Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000"))
         } else {
-            Ok(TypeOptionCellData::new(&data, self.field_type()).json())
+            Ok(data.0)
         }
     }
 }
@@ -69,7 +70,7 @@ impl CellDataOperation for RichTextTypeOption {
 mod tests {
     use crate::services::field::FieldBuilder;
     use crate::services::field::*;
-    use crate::services::row::{CellDataOperation, TypeOptionCellData};
+    use crate::services::row::CellDataOperation;
     use flowy_grid_data_model::entities::FieldType;
 
     #[test]
@@ -77,11 +78,14 @@ mod tests {
         let type_option = RichTextTypeOption::default();
 
         // date
-        let date_time_field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
+        let field_type = FieldType::DateTime;
+        let date_time_field_meta = FieldBuilder::from_field_type(&field_type).build();
         let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(1647251762, None)).unwrap();
-        let data = TypeOptionCellData::new(&json, FieldType::DateTime).json();
         assert_eq!(
-            type_option.decode_cell_data(data, &date_time_field_meta).content,
+            type_option
+                .decode_cell_data(json, &field_type, &date_time_field_meta)
+                .unwrap()
+                .content,
             "Mar 14,2022".to_owned()
         );
 
@@ -90,10 +94,11 @@ mod tests {
         let done_option_id = done_option.id.clone();
         let single_select = SingleSelectTypeOptionBuilder::default().option(done_option);
         let single_select_field_meta = FieldBuilder::new(single_select).build();
-        let cell_data = TypeOptionCellData::new(&done_option_id, FieldType::SingleSelect).json();
+
         assert_eq!(
             type_option
-                .decode_cell_data(cell_data, &single_select_field_meta)
+                .decode_cell_data(done_option_id, &FieldType::SingleSelect, &single_select_field_meta)
+                .unwrap()
                 .content,
             "Done".to_owned()
         );
@@ -111,7 +116,8 @@ mod tests {
         let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap();
         assert_eq!(
             type_option
-                .decode_cell_data(cell_data, &multi_select_field_meta)
+                .decode_cell_data(cell_data, &FieldType::MultiSelect, &multi_select_field_meta)
+                .unwrap()
                 .content,
             "Google,Facebook".to_owned()
         );
@@ -119,9 +125,11 @@ mod tests {
         //Number
         let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
         let number_field_meta = FieldBuilder::new(number).build();
-        let data = TypeOptionCellData::new("18443", FieldType::Number).json();
         assert_eq!(
-            type_option.decode_cell_data(data, &number_field_meta).content,
+            type_option
+                .decode_cell_data("18443".to_owned(), &FieldType::Number, &number_field_meta)
+                .unwrap()
+                .content,
             "$18,443".to_owned()
         );
     }

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

@@ -1,7 +1,7 @@
 use crate::dart_notification::{send_dart_notification, GridNotification};
+use crate::entities::CellIdentifier;
 use crate::manager::GridUser;
 use crate::services::block_meta_manager::GridBlockMetaEditorManager;
-use crate::services::entities::CellIdentifier;
 use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder};
 use crate::services::persistence::block_index::BlockIndexPersistence;
 use crate::services::row::*;

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

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

+ 125 - 35
frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs

@@ -1,20 +1,30 @@
 use crate::services::field::*;
-use flowy_error::FlowyError;
+use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{CellMeta, FieldMeta, FieldType};
 use serde::{Deserialize, Serialize};
 use std::fmt::Formatter;
+use std::str::FromStr;
 
-pub trait CellDataOperation {
-    fn decode_cell_data(&self, data: String, field_meta: &FieldMeta) -> DecodedCellData;
-    fn apply_changeset<T: Into<CellContentChangeset>>(
+pub trait CellDataOperation<D, CO: ToString> {
+    fn decode_cell_data<T>(
         &self,
-        changeset: T,
+        encoded_data: T,
+        decoded_field_type: &FieldType,
+        field_meta: &FieldMeta,
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<D>;
+
+    //
+    fn apply_changeset<C: Into<CellContentChangeset>>(
+        &self,
+        changeset: C,
         cell_meta: Option<CellMeta>,
-    ) -> Result<String, FlowyError>;
+    ) -> FlowyResult<CO>;
 }
 
 #[derive(Debug)]
-pub struct CellContentChangeset(String);
+pub struct CellContentChangeset(pub String);
 
 impl std::fmt::Display for CellContentChangeset {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
@@ -43,6 +53,12 @@ pub struct TypeOptionCellData {
     pub field_type: FieldType,
 }
 
+impl TypeOptionCellData {
+    pub fn split(self) -> (String, FieldType) {
+        (self.data, self.field_type)
+    }
+}
+
 impl std::str::FromStr for TypeOptionCellData {
     type Err = FlowyError;
 
@@ -52,6 +68,14 @@ impl std::str::FromStr for TypeOptionCellData {
     }
 }
 
+impl std::convert::TryInto<TypeOptionCellData> for String {
+    type Error = FlowyError;
+
+    fn try_into(self) -> Result<TypeOptionCellData, Self::Error> {
+        TypeOptionCellData::from_str(&self)
+    }
+}
+
 impl TypeOptionCellData {
     pub fn new<T: ToString>(data: T, field_type: FieldType) -> Self {
         TypeOptionCellData {
@@ -87,6 +111,10 @@ impl TypeOptionCellData {
     pub fn is_multi_select(&self) -> bool {
         self.field_type == FieldType::MultiSelect
     }
+
+    pub fn is_select_option(&self) -> bool {
+        self.field_type == FieldType::MultiSelect || self.field_type == FieldType::SingleSelect
+    }
 }
 
 /// The changeset will be deserialized into specific data base on the FieldType.
@@ -96,47 +124,109 @@ pub fn apply_cell_data_changeset<T: Into<CellContentChangeset>>(
     cell_meta: Option<CellMeta>,
     field_meta: &FieldMeta,
 ) -> Result<String, FlowyError> {
-    match field_meta.field_type {
+    let s = match field_meta.field_type {
         FieldType::RichText => RichTextTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
         FieldType::Number => NumberTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
-        FieldType::DateTime => DateTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
+        FieldType::DateTime => DateTypeOption::from(field_meta)
+            .apply_changeset(changeset, cell_meta)
+            .map(|data| data.to_string()),
         FieldType::SingleSelect => SingleSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
         FieldType::MultiSelect => MultiSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
         FieldType::Checkbox => CheckboxTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
+    }?;
+
+    Ok(TypeOptionCellData::new(s, field_meta.field_type.clone()).json())
+}
+
+pub fn decode_cell_data_from_type_option_cell_data<T: TryInto<TypeOptionCellData>>(
+    data: T,
+    field_meta: &FieldMeta,
+    field_type: &FieldType,
+) -> DecodedCellData {
+    if let Ok(type_option_cell_data) = data.try_into() {
+        let (encoded_data, s_field_type) = type_option_cell_data.split();
+        decode_cell_data(encoded_data, &s_field_type, field_type, field_meta).unwrap_or_default()
+    } else {
+        DecodedCellData::default()
     }
 }
 
-pub fn decode_cell_data(data: String, field_meta: &FieldMeta, field_type: &FieldType) -> Option<DecodedCellData> {
-    let s = match field_type {
-        FieldType::RichText => field_meta
-            .get_type_option_entry::<RichTextTypeOption>(field_type)?
-            .decode_cell_data(data, field_meta),
-        FieldType::Number => field_meta
-            .get_type_option_entry::<NumberTypeOption>(field_type)?
-            .decode_cell_data(data, field_meta),
-        FieldType::DateTime => field_meta
-            .get_type_option_entry::<DateTypeOption>(field_type)?
-            .decode_cell_data(data, field_meta),
-        FieldType::SingleSelect => field_meta
-            .get_type_option_entry::<SingleSelectTypeOption>(field_type)?
-            .decode_cell_data(data, field_meta),
-        FieldType::MultiSelect => field_meta
-            .get_type_option_entry::<MultiSelectTypeOption>(field_type)?
-            .decode_cell_data(data, field_meta),
-        FieldType::Checkbox => field_meta
-            .get_type_option_entry::<CheckboxTypeOption>(field_type)?
-            .decode_cell_data(data, field_meta),
+pub fn decode_cell_data<T: Into<String>>(
+    encoded_data: T,
+    s_field_type: &FieldType,
+    t_field_type: &FieldType,
+    field_meta: &FieldMeta,
+) -> FlowyResult<DecodedCellData> {
+    let encoded_data = encoded_data.into();
+    let get_cell_data = || {
+        let data = match t_field_type {
+            FieldType::RichText => field_meta
+                .get_type_option_entry::<RichTextTypeOption>(t_field_type)?
+                .decode_cell_data(encoded_data, s_field_type, field_meta),
+            FieldType::Number => field_meta
+                .get_type_option_entry::<NumberTypeOption>(t_field_type)?
+                .decode_cell_data(encoded_data, s_field_type, field_meta),
+            FieldType::DateTime => field_meta
+                .get_type_option_entry::<DateTypeOption>(t_field_type)?
+                .decode_cell_data(encoded_data, s_field_type, field_meta),
+            FieldType::SingleSelect => field_meta
+                .get_type_option_entry::<SingleSelectTypeOption>(t_field_type)?
+                .decode_cell_data(encoded_data, s_field_type, field_meta),
+            FieldType::MultiSelect => field_meta
+                .get_type_option_entry::<MultiSelectTypeOption>(t_field_type)?
+                .decode_cell_data(encoded_data, s_field_type, field_meta),
+            FieldType::Checkbox => field_meta
+                .get_type_option_entry::<CheckboxTypeOption>(t_field_type)?
+                .decode_cell_data(encoded_data, s_field_type, field_meta),
+        };
+        Some(data)
     };
-    tracing::Span::current().record(
-        "content",
-        &format!("{:?}: {}", field_meta.field_type, s.content).as_str(),
-    );
-    Some(s)
+
+    match get_cell_data() {
+        Some(Ok(data)) => {
+            tracing::Span::current().record(
+                "content",
+                &format!("{:?}: {}", field_meta.field_type, data.content).as_str(),
+            );
+            Ok(data)
+        }
+        Some(Err(err)) => {
+            tracing::error!("{:?}", err);
+            Ok(DecodedCellData::default())
+        }
+        None => Ok(DecodedCellData::default()),
+    }
+}
+
+pub(crate) struct EncodedCellData<T>(pub Option<T>);
+
+impl<T> EncodedCellData<T> {
+    pub fn try_into_inner(self) -> FlowyResult<T> {
+        match self.0 {
+            None => Err(ErrorCode::InvalidData.into()),
+            Some(data) => Ok(data),
+        }
+    }
+}
+
+impl<T> std::convert::From<String> for EncodedCellData<T>
+where
+    T: FromStr<Err = FlowyError>,
+{
+    fn from(s: String) -> Self {
+        match T::from_str(&s) {
+            Ok(inner) => EncodedCellData(Some(inner)),
+            Err(e) => {
+                tracing::error!("Deserialize Cell Data failed: {}", e);
+                EncodedCellData(None)
+            }
+        }
+    }
 }
 
 #[derive(Default)]
 pub struct DecodedCellData {
-    pub raw: String,
+    raw: String,
     pub content: String,
 }
 

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

@@ -1,4 +1,4 @@
-use crate::services::row::decode_cell_data;
+use crate::services::row::decode_cell_data_from_type_option_cell_data;
 use flowy_error::FlowyResult;
 use flowy_grid_data_model::entities::{
     Cell, CellMeta, FieldMeta, GridBlock, GridBlockOrder, RepeatedGridBlock, Row, RowMeta, RowOrder,
@@ -31,14 +31,16 @@ pub fn make_cell_by_field_id(
     cell_meta: CellMeta,
 ) -> Option<(String, Cell)> {
     let field_meta = field_map.get(&field_id)?;
-    let (raw, content) = decode_cell_data(cell_meta.data, field_meta, &field_meta.field_type)?.split();
+    let (raw, content) =
+        decode_cell_data_from_type_option_cell_data(cell_meta.data, field_meta, &field_meta.field_type).split();
     let cell = Cell::new(&field_id, content, raw);
     Some((field_id, cell))
 }
 
 pub fn make_cell(field_id: &str, field_meta: &FieldMeta, row_meta: &RowMeta) -> Option<Cell> {
     let cell_meta = row_meta.cells.get(field_id)?.clone();
-    let (raw, content) = decode_cell_data(cell_meta.data, field_meta, &field_meta.field_type)?.split();
+    let (raw, content) =
+        decode_cell_data_from_type_option_cell_data(cell_meta.data, field_meta, &field_meta.field_type).split();
     Some(Cell::new(field_id, content, raw))
 }
 

+ 2 - 3
frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs

@@ -5,7 +5,7 @@ use flowy_grid::services::field::{
     DateCellContentChangeset, MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset,
     SingleSelectTypeOption, SELECTION_IDS_SEPARATOR,
 };
-use flowy_grid::services::row::{decode_cell_data, CreateRowMetaBuilder};
+use flowy_grid::services::row::{decode_cell_data_from_type_option_cell_data, CreateRowMetaBuilder};
 use flowy_grid_data_model::entities::{
     CellChangeset, FieldChangesetParams, FieldType, GridBlockMeta, GridBlockMetaChangeset, RowMetaChangeset,
     TypeOptionDataEntry,
@@ -291,8 +291,7 @@ async fn grid_row_add_date_cell_test() {
     let date_field = date_field.unwrap();
     let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone();
     assert_eq!(
-        decode_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type)
-            .unwrap()
+        decode_cell_data_from_type_option_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type)
             .split()
             .1,
         "2022/03/16",

+ 28 - 0
shared-lib/flowy-grid-data-model/src/entities/grid.rs

@@ -912,6 +912,34 @@ impl FieldType {
             _ => 150,
         }
     }
+
+    pub fn is_number(&self) -> bool {
+        self == &FieldType::Number
+    }
+
+    pub fn is_text(&self) -> bool {
+        self == &FieldType::RichText
+    }
+
+    pub fn is_checkbox(&self) -> bool {
+        self == &FieldType::Checkbox
+    }
+
+    pub fn is_date(&self) -> bool {
+        self == &FieldType::DateTime
+    }
+
+    pub fn is_single_select(&self) -> bool {
+        self == &FieldType::SingleSelect
+    }
+
+    pub fn is_multi_select(&self) -> bool {
+        self == &FieldType::MultiSelect
+    }
+
+    pub fn is_select_option(&self) -> bool {
+        self == &FieldType::MultiSelect || self == &FieldType::SingleSelect
+    }
 }
 
 #[derive(Debug, Clone, Default, ProtoBuf)]

+ 9 - 14
shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs

@@ -153,27 +153,22 @@ pub fn check_pb_dart_plugin() {
             let output = Command::new("sh").arg("-c").arg("echo $PATH").output();
             let paths = String::from_utf8(output.unwrap().stdout)
                 .unwrap()
-                .split(":")
+                .split(':')
                 .map(|s| s.to_string())
                 .collect::<Vec<String>>();
 
             paths.iter().for_each(|s| msg.push_str(&format!("{}\n", s)));
 
-            match Command::new("sh").arg("-c").arg("which protoc-gen-dart").output() {
-                Ok(output) => {
-                    msg.push_str(&format!(
-                        "Installed protoc-gen-dart path: {:?}\n",
-                        String::from_utf8(output.stdout).unwrap()
-                    ));
-                }
-                Err(_) => {}
+            if let Ok(output) = Command::new("sh").arg("-c").arg("which protoc-gen-dart").output() {
+                msg.push_str(&format!(
+                    "Installed protoc-gen-dart path: {:?}\n",
+                    String::from_utf8(output.stdout).unwrap()
+                ));
             }
 
-            msg.push_str(&format!("✅ You can fix that by adding:"));
-            msg.push_str(&format!("\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n",));
-            msg.push_str(&format!(
-                "to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)"
-            ));
+            msg.push_str(&"✅ You can fix that by adding:".to_string());
+            msg.push_str(&"\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n".to_string());
+            msg.push_str(&"to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)".to_string());
             panic!("{}", msg)
         }
     }