瀏覽代碼

Merge branch 'main' into refactor/add_pb_suffix

appflowy 2 年之前
父節點
當前提交
3a0b8bbd74
共有 60 個文件被更改,包括 2233 次插入1696 次删除
  1. 2 2
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart
  2. 1 0
      frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart
  3. 1 1
      frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart
  4. 1 1
      frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart
  5. 1 1
      frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart
  6. 1 0
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart
  7. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart
  8. 0 1
      frontend/rust-lib/flowy-grid/Flowy.toml
  9. 1 1
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs
  10. 12 7
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  11. 41 13
      frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs
  12. 47 29
      frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs
  13. 0 1
      frontend/rust-lib/flowy-grid/src/services/field/mod.rs
  14. 0 133
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs
  15. 30 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs
  16. 76 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs
  17. 69 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs
  18. 7 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/mod.rs
  19. 0 661
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs
  20. 272 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_tests.rs
  21. 195 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs
  22. 210 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs
  23. 7 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/mod.rs
  24. 7 10
      frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs
  25. 3 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs
  26. 139 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_tests.rs
  27. 8 237
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs
  28. 105 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option_entities.rs
  29. 7 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs
  30. 13 28
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs
  31. 40 15
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs
  32. 12 21
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs
  33. 3 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/mod.rs
  34. 36 12
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs
  35. 0 194
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs
  36. 7 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs
  37. 67 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_tests.rs
  38. 95 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs
  39. 42 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option_entities.rs
  40. 0 1
      frontend/rust-lib/flowy-grid/src/services/field/type_options/util/mod.rs
  41. 6 4
      frontend/rust-lib/flowy-grid/src/services/filter/impls/checkbox_filter.rs
  42. 3 2
      frontend/rust-lib/flowy-grid/src/services/filter/impls/date_filter.rs
  43. 3 5
      frontend/rust-lib/flowy-grid/src/services/filter/impls/number_filter.rs
  44. 4 4
      frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs
  45. 3 2
      frontend/rust-lib/flowy-grid/src/services/filter/impls/text_filter.rs
  46. 3 2
      frontend/rust-lib/flowy-grid/src/services/filter/impls/url_filter.rs
  47. 18 27
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  48. 17 21
      frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs
  49. 8 22
      frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs
  50. 8 8
      frontend/rust-lib/flowy-grid/src/util.rs
  51. 74 89
      frontend/rust-lib/flowy-grid/tests/grid/block_test/row_test.rs
  52. 235 12
      frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs
  53. 98 41
      frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs
  54. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/cell_test/test.rs
  55. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs
  56. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs
  57. 5 5
      frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs
  58. 164 70
      frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs
  59. 2 2
      shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs
  60. 20 7
      shared-lib/flowy-sync/src/client_grid/grid_builder.rs

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

@@ -8,10 +8,10 @@ import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart';
 import 'package:flutter/foundation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';

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

@@ -5,6 +5,7 @@ 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:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:table_calendar/table_calendar.dart';

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

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';

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

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

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

@@ -1,4 +1,4 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 1 - 0
frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart

@@ -1,5 +1,6 @@
 import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart

@@ -9,7 +9,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 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:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'builder.dart';

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

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

+ 1 - 1
frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs

@@ -1,4 +1,4 @@
-use crate::services::field::select_option::SelectOptionIds;
+use crate::services::field::SelectOptionIds;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::revision::GridFilterRevision;

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

@@ -1,10 +1,13 @@
 use crate::entities::*;
 use crate::manager::GridManager;
 use crate::services::cell::AnyCellData;
-use crate::services::field::select_option::*;
 use crate::services::field::{
-    default_type_option_builder_from_type, type_option_builder_from_json_str, DateChangesetParams, DateChangesetPayload,
+    default_type_option_builder_from_type, select_option_operation, type_option_builder_from_json_str,
+    DateChangesetParams, DateChangesetPayload, SelectOption, SelectOptionCellChangeset,
+    SelectOptionCellChangesetParams, SelectOptionCellChangesetPayload, SelectOptionCellData, SelectOptionChangeset,
+    SelectOptionChangesetPayload,
 };
+use crate::services::row::make_row_from_row_rev;
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_grid_data_model::revision::FieldRevision;
 use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams};
@@ -229,10 +232,12 @@ pub(crate) async fn get_row_handler(
 ) -> DataResult<OptionalRow, FlowyError> {
     let params: GridRowId = data.into_inner().try_into()?;
     let editor = manager.get_grid_editor(&params.grid_id)?;
-    let row = OptionalRow {
-        row: editor.get_row(&params.row_id).await?,
-    };
-    data_result(row)
+    let row = editor
+        .get_row_rev(&params.row_id)
+        .await?
+        .and_then(make_row_from_row_rev);
+
+    data_result(OptionalRow { row })
 }
 
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
@@ -373,7 +378,7 @@ pub(crate) async fn get_select_option_handler(
                 },
                 Some(cell_rev) => cell_rev.try_into()?,
             };
-            let option_context = type_option.selected_select_option(any_cell_data);
+            let option_context = type_option.selected_select_option(any_cell_data.into());
             data_result(option_context)
         }
     }

+ 41 - 13
frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs

@@ -1,9 +1,11 @@
 use crate::entities::FieldType;
+use crate::services::cell::{CellData, FromCellString};
 use bytes::Bytes;
 use flowy_error::{internal_error, FlowyError, FlowyResult};
 use flowy_grid_data_model::revision::CellRevision;
 use serde::{Deserialize, Serialize};
 use std::str::FromStr;
+
 /// AnyCellData is a generic CellData, you can parse the cell_data according to the field_type.
 /// When the type of field is changed, it's different from the field_type of AnyCellData.
 /// So it will return an empty data. You could check the CellDataOperation trait for more information.
@@ -46,6 +48,15 @@ impl std::convert::TryFrom<CellRevision> for AnyCellData {
     }
 }
 
+impl<T> std::convert::From<AnyCellData> for CellData<T>
+where
+    T: FromCellString,
+{
+    fn from(any_call_data: AnyCellData) -> Self {
+        CellData::from(any_call_data.data)
+    }
+}
+
 impl AnyCellData {
     pub fn new(content: String, field_type: FieldType) -> Self {
         AnyCellData {
@@ -100,36 +111,45 @@ impl AnyCellData {
 /// * Use String to parse the data when the FieldType is RichText, Number, or Checkbox.
 /// * Check out the implementation of CellDataOperation trait for more information.
 #[derive(Default)]
-pub struct DecodedCellData {
-    pub data: Vec<u8>,
+pub struct CellBytes(pub Bytes);
+
+pub trait CellBytesParser {
+    type Object;
+    fn parse(&self, bytes: &Bytes) -> FlowyResult<Self::Object>;
 }
 
-impl DecodedCellData {
+impl CellBytes {
     pub fn new<T: AsRef<[u8]>>(data: T) -> Self {
-        Self {
-            data: data.as_ref().to_vec(),
-        }
+        let bytes = Bytes::from(data.as_ref().to_vec());
+        Self(bytes)
     }
 
-    pub fn try_from_bytes<T: TryInto<Bytes>>(bytes: T) -> FlowyResult<Self>
+    pub fn from<T: TryInto<Bytes>>(bytes: T) -> FlowyResult<Self>
     where
         <T as TryInto<Bytes>>::Error: std::fmt::Debug,
     {
         let bytes = bytes.try_into().map_err(internal_error)?;
-        Ok(Self { data: bytes.to_vec() })
+        Ok(Self(bytes))
     }
 
-    pub fn parse<'a, T: TryFrom<&'a [u8]>>(&'a self) -> FlowyResult<T>
+    pub fn with_parser<P>(&self, parser: P) -> FlowyResult<P::Object>
     where
-        <T as TryFrom<&'a [u8]>>::Error: std::fmt::Debug,
+        P: CellBytesParser,
     {
-        T::try_from(self.data.as_ref()).map_err(internal_error)
+        parser.parse(&self.0)
     }
+
+    // pub fn parse<'a, T: TryFrom<&'a [u8]>>(&'a self) -> FlowyResult<T>
+    // where
+    //     <T as TryFrom<&'a [u8]>>::Error: std::fmt::Debug,
+    // {
+    //     T::try_from(self.0.as_ref()).map_err(internal_error)
+    // }
 }
 
-impl ToString for DecodedCellData {
+impl ToString for CellBytes {
     fn to_string(&self) -> String {
-        match String::from_utf8(self.data.clone()) {
+        match String::from_utf8(self.0.to_vec()) {
             Ok(s) => s,
             Err(e) => {
                 tracing::error!("DecodedCellData to string failed: {:?}", e);
@@ -138,3 +158,11 @@ impl ToString for DecodedCellData {
         }
     }
 }
+
+impl std::ops::Deref for CellBytes {
+    type Target = Bytes;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}

+ 47 - 29
frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs

@@ -1,33 +1,51 @@
-use flowy_error::{ErrorCode, FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, FieldTypeRevision};
-
 use crate::entities::FieldType;
-use crate::services::cell::{AnyCellData, DecodedCellData};
+use crate::services::cell::{AnyCellData, CellBytes};
 use crate::services::field::*;
 
+use flowy_error::{ErrorCode, FlowyError, FlowyResult};
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, FieldTypeRevision};
+
+/// This trait is used when doing filter/search on the grid.
 pub trait CellFilterOperation<T> {
     /// Return true if any_cell_data match the filter condition.
     fn apply_filter(&self, any_cell_data: AnyCellData, filter: &T) -> FlowyResult<bool>;
 }
 
-pub trait CellDataOperation<D, C> {
-    /// The cell_data is able to parse into the specific data that was impl the FromCellData trait.
+/// Return object that describes the cell.
+pub trait CellDisplayable<CD> {
+    fn display_data(
+        &self,
+        cell_data: CellData<CD>,
+        decoded_field_type: &FieldType,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes>;
+}
+
+// CD: Short for CellData. This type is the type return by apply_changeset function.
+// CS: Short for Changeset. Parse the string into specific Changeset type.
+pub trait CellDataOperation<CD, CS> {
+    /// The cell_data is able to parse into the specific data if CD impl the FromCellData trait.
     /// For example:
     /// URLCellData, DateCellData. etc.
     fn decode_cell_data(
         &self,
-        cell_data: CellData<D>,
+        cell_data: CellData<CD>,
         decoded_field_type: &FieldType,
         field_rev: &FieldRevision,
-    ) -> FlowyResult<DecodedCellData>;
+    ) -> FlowyResult<CellBytes>;
 
-    /// The changeset is able to parse into the specific data that was impl the FromCellChangeset trait.
+    /// The changeset is able to parse into the specific data if CS impl the FromCellChangeset trait.
     /// For example:
     /// SelectOptionCellChangeset,DateCellChangeset. etc.  
-    fn apply_changeset(&self, changeset: CellDataChangeset<C>, cell_rev: Option<CellRevision>) -> FlowyResult<String>;
+    fn apply_changeset(&self, changeset: CellDataChangeset<CS>, cell_rev: Option<CellRevision>) -> FlowyResult<String>;
 }
-/// The changeset will be deserialized into specific data base on the FieldType.
-/// For example, it's String on FieldType::RichText, and SelectOptionChangeset on FieldType::SingleSelect
+
+/// changeset: It will be deserialized into specific data base on the FieldType.
+///     For example,
+///         FieldType::RichText => String
+///         FieldType::SingleSelect => SelectOptionChangeset
+///
+/// cell_rev: It will be None if the cell does not contain any data.
 pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
     changeset: C,
     cell_rev: Option<CellRevision>,
@@ -49,23 +67,20 @@ pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
     Ok(AnyCellData::new(s, field_type).json())
 }
 
-pub fn decode_any_cell_data<T: TryInto<AnyCellData>>(data: T, field_rev: &FieldRevision) -> DecodedCellData {
+pub fn decode_any_cell_data<T: TryInto<AnyCellData>>(data: T, field_rev: &FieldRevision) -> CellBytes {
     if let Ok(any_cell_data) = data.try_into() {
-        let AnyCellData {
-            data: cell_data,
-            field_type,
-        } = any_cell_data;
+        let AnyCellData { data, field_type } = any_cell_data;
         let to_field_type = field_rev.field_type_rev.into();
-        match try_decode_cell_data(CellData(Some(cell_data)), field_rev, &field_type, &to_field_type) {
-            Ok(cell_data) => cell_data,
+        match try_decode_cell_data(data.into(), field_rev, &field_type, &to_field_type) {
+            Ok(cell_bytes) => cell_bytes,
             Err(e) => {
                 tracing::error!("Decode cell data failed, {:?}", e);
-                DecodedCellData::default()
+                CellBytes::default()
             }
         }
     } else {
         tracing::error!("Decode type option data failed");
-        DecodedCellData::default()
+        CellBytes::default()
     }
 }
 
@@ -74,7 +89,7 @@ pub fn try_decode_cell_data(
     field_rev: &FieldRevision,
     s_field_type: &FieldType,
     t_field_type: &FieldType,
-) -> FlowyResult<DecodedCellData> {
+) -> FlowyResult<CellBytes> {
     let cell_data = cell_data.try_into_inner()?;
     let get_cell_data = || {
         let field_type: FieldTypeRevision = t_field_type.into();
@@ -108,20 +123,22 @@ pub fn try_decode_cell_data(
         Some(Ok(data)) => Ok(data),
         Some(Err(err)) => {
             tracing::error!("{:?}", err);
-            Ok(DecodedCellData::default())
+            Ok(CellBytes::default())
         }
-        None => Ok(DecodedCellData::default()),
+        None => Ok(CellBytes::default()),
     }
 }
 
+/// If the cell data is not String type, it should impl this trait.
+/// Deserialize the String into cell specific data type.  
 pub trait FromCellString {
     fn from_cell_str(s: &str) -> FlowyResult<Self>
     where
         Self: Sized;
 }
 
+/// CellData is a helper struct. String will be parser into Option<T> only if the T impl the FromCellString trait.
 pub struct CellData<T>(pub Option<T>);
-
 impl<T> CellData<T> {
     pub fn try_into_inner(self) -> FlowyResult<T> {
         match self.0 {
@@ -146,9 +163,9 @@ where
     }
 }
 
-impl std::convert::From<String> for CellData<String> {
-    fn from(s: String) -> Self {
-        CellData(Some(s))
+impl<T> std::convert::From<T> for CellData<T> {
+    fn from(val: T) -> Self {
+        CellData(Some(val))
     }
 }
 
@@ -158,7 +175,8 @@ impl std::convert::From<CellData<String>> for String {
     }
 }
 
-// CellChangeset
+/// If the changeset applying to the cell is not String type, it should impl this trait.
+/// Deserialize the string into cell specific changeset.
 pub trait FromCellChangeset {
     fn from_changeset(changeset: String) -> FlowyResult<Self>
     where

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

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

+ 0 - 133
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs

@@ -1,133 +0,0 @@
-use crate::entities::FieldType;
-use crate::impl_type_option;
-use crate::services::cell::{AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData};
-use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use bytes::Bytes;
-use flowy_derive::ProtoBuf;
-use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
-use serde::{Deserialize, Serialize};
-
-#[derive(Default)]
-pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption);
-impl_into_box_type_option_builder!(CheckboxTypeOptionBuilder);
-impl_builder_from_json_str_and_from_bytes!(CheckboxTypeOptionBuilder, CheckboxTypeOption);
-
-impl CheckboxTypeOptionBuilder {
-    pub fn set_selected(mut self, is_selected: bool) -> Self {
-        self.0.is_selected = is_selected;
-        self
-    }
-}
-
-impl TypeOptionBuilder for CheckboxTypeOptionBuilder {
-    fn field_type(&self) -> FieldType {
-        FieldType::Checkbox
-    }
-
-    fn entry(&self) -> &dyn TypeOptionDataEntry {
-        &self.0
-    }
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
-pub struct CheckboxTypeOption {
-    #[pb(index = 1)]
-    pub is_selected: bool,
-}
-impl_type_option!(CheckboxTypeOption, FieldType::Checkbox);
-
-const YES: &str = "Yes";
-const NO: &str = "No";
-
-impl CellDataOperation<String, String> for CheckboxTypeOption {
-    fn decode_cell_data(
-        &self,
-        cell_data: CellData<String>,
-        decoded_field_type: &FieldType,
-        _field_rev: &FieldRevision,
-    ) -> FlowyResult<DecodedCellData> {
-        if !decoded_field_type.is_checkbox() {
-            return Ok(DecodedCellData::default());
-        }
-
-        let s: String = cell_data.try_into_inner()?;
-        if s == YES || s == NO {
-            return Ok(DecodedCellData::new(s));
-        }
-
-        Ok(DecodedCellData::default())
-    }
-
-    fn apply_changeset(
-        &self,
-        changeset: CellDataChangeset<String>,
-        _cell_rev: Option<CellRevision>,
-    ) -> Result<String, FlowyError> {
-        let changeset = changeset.try_into_inner()?;
-        let s = match string_to_bool(&changeset) {
-            true => YES,
-            false => NO,
-        };
-        Ok(s.to_string())
-    }
-}
-
-fn string_to_bool(bool_str: &str) -> bool {
-    let lower_case_str: &str = &bool_str.to_lowercase();
-    match lower_case_str {
-        "1" => true,
-        "true" => true,
-        "yes" => true,
-        "0" => false,
-        "false" => false,
-        "no" => false,
-        _ => false,
-    }
-}
-
-pub struct CheckboxCellData(pub String);
-
-impl CheckboxCellData {
-    pub fn is_check(&self) -> bool {
-        string_to_bool(&self.0)
-    }
-}
-impl std::convert::TryFrom<AnyCellData> for CheckboxCellData {
-    type Error = FlowyError;
-
-    fn try_from(_value: AnyCellData) -> Result<Self, Self::Error> {
-        todo!()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data};
-    use crate::services::field::type_options::checkbox_type_option::{NO, YES};
-    use crate::services::field::FieldBuilder;
-
-    use crate::entities::FieldType;
-
-    #[test]
-    fn checkout_box_description_test() {
-        let field_rev = FieldBuilder::from_field_type(&FieldType::Checkbox).build();
-        let data = apply_cell_data_changeset("true", None, &field_rev).unwrap();
-        assert_eq!(decode_any_cell_data(data, &field_rev).to_string(), YES);
-
-        let data = apply_cell_data_changeset("1", None, &field_rev).unwrap();
-        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), YES);
-
-        let data = apply_cell_data_changeset("yes", None, &field_rev).unwrap();
-        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), YES);
-
-        let data = apply_cell_data_changeset("false", None, &field_rev).unwrap();
-        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), NO);
-
-        let data = apply_cell_data_changeset("no", None, &field_rev).unwrap();
-        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), NO);
-
-        let data = apply_cell_data_changeset("12", None, &field_rev).unwrap();
-        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), NO);
-    }
-}

+ 30 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs

@@ -0,0 +1,30 @@
+#[cfg(test)]
+mod tests {
+    use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data};
+    use crate::services::field::type_options::checkbox_type_option::{NO, YES};
+    use crate::services::field::FieldBuilder;
+
+    use crate::entities::FieldType;
+
+    #[test]
+    fn checkout_box_description_test() {
+        let field_rev = FieldBuilder::from_field_type(&FieldType::Checkbox).build();
+        let data = apply_cell_data_changeset("true", None, &field_rev).unwrap();
+        assert_eq!(decode_any_cell_data(data, &field_rev).to_string(), YES);
+
+        let data = apply_cell_data_changeset("1", None, &field_rev).unwrap();
+        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), YES);
+
+        let data = apply_cell_data_changeset("yes", None, &field_rev).unwrap();
+        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), YES);
+
+        let data = apply_cell_data_changeset("false", None, &field_rev).unwrap();
+        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), NO);
+
+        let data = apply_cell_data_changeset("no", None, &field_rev).unwrap();
+        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), NO);
+
+        let data = apply_cell_data_changeset("12", None, &field_rev).unwrap();
+        assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), "");
+    }
+}

+ 76 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs

@@ -0,0 +1,76 @@
+use crate::entities::FieldType;
+use crate::impl_type_option;
+use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
+use crate::services::field::{BoxTypeOptionBuilder, CheckboxCellData, TypeOptionBuilder};
+use bytes::Bytes;
+use flowy_derive::ProtoBuf;
+use flowy_error::{FlowyError, FlowyResult};
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
+use serde::{Deserialize, Serialize};
+use std::str::FromStr;
+
+#[derive(Default)]
+pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption);
+impl_into_box_type_option_builder!(CheckboxTypeOptionBuilder);
+impl_builder_from_json_str_and_from_bytes!(CheckboxTypeOptionBuilder, CheckboxTypeOption);
+
+impl CheckboxTypeOptionBuilder {
+    pub fn set_selected(mut self, is_selected: bool) -> Self {
+        self.0.is_selected = is_selected;
+        self
+    }
+}
+
+impl TypeOptionBuilder for CheckboxTypeOptionBuilder {
+    fn field_type(&self) -> FieldType {
+        FieldType::Checkbox
+    }
+
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
+pub struct CheckboxTypeOption {
+    #[pb(index = 1)]
+    pub is_selected: bool,
+}
+impl_type_option!(CheckboxTypeOption, FieldType::Checkbox);
+
+impl CellDisplayable<CheckboxCellData> for CheckboxTypeOption {
+    fn display_data(
+        &self,
+        cell_data: CellData<CheckboxCellData>,
+        _decoded_field_type: &FieldType,
+        _field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        let cell_data = cell_data.try_into_inner()?;
+        Ok(CellBytes::new(cell_data))
+    }
+}
+
+impl CellDataOperation<CheckboxCellData, String> for CheckboxTypeOption {
+    fn decode_cell_data(
+        &self,
+        cell_data: CellData<CheckboxCellData>,
+        decoded_field_type: &FieldType,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        if !decoded_field_type.is_checkbox() {
+            return Ok(CellBytes::default());
+        }
+
+        self.display_data(cell_data, decoded_field_type, field_rev)
+    }
+
+    fn apply_changeset(
+        &self,
+        changeset: CellDataChangeset<String>,
+        _cell_rev: Option<CellRevision>,
+    ) -> Result<String, FlowyError> {
+        let changeset = changeset.try_into_inner()?;
+        let cell_data = CheckboxCellData::from_str(&changeset)?;
+        Ok(cell_data.to_string())
+    }
+}

+ 69 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs

@@ -0,0 +1,69 @@
+use crate::services::cell::{CellBytesParser, FromCellString};
+use bytes::Bytes;
+use flowy_error::{FlowyError, FlowyResult};
+use std::str::FromStr;
+
+pub const YES: &str = "Yes";
+pub const NO: &str = "No";
+
+pub struct CheckboxCellData(String);
+
+impl CheckboxCellData {
+    pub fn is_check(&self) -> bool {
+        self.0 == YES
+    }
+}
+
+impl AsRef<[u8]> for CheckboxCellData {
+    fn as_ref(&self) -> &[u8] {
+        self.0.as_ref()
+    }
+}
+
+impl FromStr for CheckboxCellData {
+    type Err = FlowyError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let lower_case_str: &str = &s.to_lowercase();
+        let val = match lower_case_str {
+            "1" => Some(true),
+            "true" => Some(true),
+            "yes" => Some(true),
+            "0" => Some(false),
+            "false" => Some(false),
+            "no" => Some(false),
+            _ => None,
+        };
+
+        match val {
+            Some(true) => Ok(Self(YES.to_string())),
+            Some(false) => Ok(Self(NO.to_string())),
+            None => Ok(Self("".to_string())),
+        }
+    }
+}
+
+impl FromCellString for CheckboxCellData {
+    fn from_cell_str(s: &str) -> FlowyResult<Self>
+    where
+        Self: Sized,
+    {
+        Self::from_str(s)
+    }
+}
+
+impl ToString for CheckboxCellData {
+    fn to_string(&self) -> String {
+        self.0.clone()
+    }
+}
+pub struct CheckboxCellDataParser();
+impl CellBytesParser for CheckboxCellDataParser {
+    type Object = CheckboxCellData;
+    fn parse(&self, bytes: &Bytes) -> FlowyResult<Self::Object> {
+        match String::from_utf8(bytes.to_vec()) {
+            Ok(s) => CheckboxCellData::from_str(&s),
+            Err(_) => Ok(CheckboxCellData("".to_string())),
+        }
+    }
+}

+ 7 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/mod.rs

@@ -0,0 +1,7 @@
+#![allow(clippy::module_inception)]
+mod checkbox_tests;
+mod checkbox_type_option;
+mod checkbox_type_option_entities;
+
+pub use checkbox_type_option::*;
+pub use checkbox_type_option_entities::*;

+ 0 - 661
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs

@@ -1,661 +0,0 @@
-use crate::entities::{CellChangeset, FieldType};
-use crate::entities::{CellIdentifier, CellIdentifierPayload};
-use crate::impl_type_option;
-use crate::services::cell::{
-    AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData, FromCellChangeset, FromCellString,
-};
-use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use bytes::Bytes;
-use chrono::format::strftime::StrftimeItems;
-use chrono::{NaiveDateTime, Timelike};
-use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
-use serde::{Deserialize, Serialize};
-use strum_macros::EnumIter;
-
-// Date
-#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct DateTypeOption {
-    #[pb(index = 1)]
-    pub date_format: DateFormat,
-
-    #[pb(index = 2)]
-    pub time_format: TimeFormat,
-
-    #[pb(index = 3)]
-    pub include_time: bool,
-}
-impl_type_option!(DateTypeOption, FieldType::DateTime);
-
-impl DateTypeOption {
-    #[allow(dead_code)]
-    pub fn new() -> Self {
-        Self::default()
-    }
-
-    fn today_desc_from_timestamp(&self, timestamp: i64) -> DateCellData {
-        let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0);
-        self.date_from_native(native)
-    }
-
-    fn date_from_native(&self, native: chrono::NaiveDateTime) -> DateCellData {
-        if native.timestamp() == 0 {
-            return DateCellData::default();
-        }
-
-        let time = native.time();
-        let has_time = time.hour() != 0 || time.second() != 0;
-
-        let utc = self.utc_date_time_from_native(native);
-        let fmt = self.date_format.format_str();
-        let date = format!("{}", utc.format_with_items(StrftimeItems::new(fmt)));
-
-        let mut time = "".to_string();
-        if has_time {
-            let fmt = format!("{} {}", self.date_format.format_str(), self.time_format.format_str());
-            time = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt))).replace(&date, "");
-        }
-
-        let timestamp = native.timestamp();
-        DateCellData { date, time, timestamp }
-    }
-
-    fn date_fmt(&self, time: &Option<String>) -> String {
-        if self.include_time {
-            match time.as_ref() {
-                None => self.date_format.format_str().to_string(),
-                Some(time_str) => {
-                    if time_str.is_empty() {
-                        self.date_format.format_str().to_string()
-                    } else {
-                        format!("{} {}", self.date_format.format_str(), self.time_format.format_str())
-                    }
-                }
-            }
-        } else {
-            self.date_format.format_str().to_string()
-        }
-    }
-
-    fn timestamp_from_utc_with_time(
-        &self,
-        utc: &chrono::DateTime<chrono::Utc>,
-        time: &Option<String>,
-    ) -> FlowyResult<i64> {
-        if let Some(time_str) = time.as_ref() {
-            if !time_str.is_empty() {
-                let date_str = format!(
-                    "{}{}",
-                    utc.format_with_items(StrftimeItems::new(self.date_format.format_str())),
-                    &time_str
-                );
-
-                return match NaiveDateTime::parse_from_str(&date_str, &self.date_fmt(time)) {
-                    Ok(native) => {
-                        let utc = self.utc_date_time_from_native(native);
-                        Ok(utc.timestamp())
-                    }
-                    Err(_e) => {
-                        let msg = format!("Parse {} failed", date_str);
-                        Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg))
-                    }
-                };
-            }
-        }
-
-        Ok(utc.timestamp())
-    }
-
-    fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime<chrono::Utc> {
-        let native = NaiveDateTime::from_timestamp(timestamp, 0);
-        let native2 = NaiveDateTime::from_timestamp(timestamp, 0);
-
-        if native > native2 {}
-
-        self.utc_date_time_from_native(native)
-    }
-
-    fn utc_date_time_from_native(&self, naive: chrono::NaiveDateTime) -> chrono::DateTime<chrono::Utc> {
-        chrono::DateTime::<chrono::Utc>::from_utc(naive, chrono::Utc)
-    }
-}
-
-impl CellDataOperation<DateTimestamp, DateCellChangeset> for DateTypeOption {
-    fn decode_cell_data(
-        &self,
-        cell_data: CellData<DateTimestamp>,
-        decoded_field_type: &FieldType,
-        _field_rev: &FieldRevision,
-    ) -> FlowyResult<DecodedCellData> {
-        // 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());
-        }
-        let timestamp = cell_data.try_into_inner()?;
-        let date = self.today_desc_from_timestamp(timestamp.0);
-        DecodedCellData::try_from_bytes(date)
-    }
-
-    fn apply_changeset(
-        &self,
-        changeset: CellDataChangeset<DateCellChangeset>,
-        _cell_rev: Option<CellRevision>,
-    ) -> Result<String, FlowyError> {
-        let changeset = changeset.try_into_inner()?;
-        let cell_data = match changeset.date_timestamp() {
-            None => 0,
-            Some(date_timestamp) => match (self.include_time, changeset.time) {
-                (true, Some(time)) => {
-                    let time = Some(time.trim().to_uppercase());
-                    let utc = self.utc_date_time_from_timestamp(date_timestamp);
-                    self.timestamp_from_utc_with_time(&utc, &time)?
-                }
-                _ => date_timestamp,
-            },
-        };
-
-        Ok(cell_data.to_string())
-    }
-}
-
-pub struct DateTimestamp(i64);
-impl AsRef<i64> for DateTimestamp {
-    fn as_ref(&self) -> &i64 {
-        &self.0
-    }
-}
-
-impl std::convert::From<DateTimestamp> for i64 {
-    fn from(timestamp: DateTimestamp) -> Self {
-        timestamp.0
-    }
-}
-
-impl FromCellString for DateTimestamp {
-    fn from_cell_str(s: &str) -> FlowyResult<Self>
-    where
-        Self: Sized,
-    {
-        let num = s.parse::<i64>().unwrap_or(0);
-        Ok(DateTimestamp(num))
-    }
-}
-
-impl std::convert::From<AnyCellData> for DateTimestamp {
-    fn from(data: AnyCellData) -> Self {
-        let num = data.data.parse::<i64>().unwrap_or(0);
-        DateTimestamp(num)
-    }
-}
-
-#[derive(Default)]
-pub struct DateTypeOptionBuilder(DateTypeOption);
-impl_into_box_type_option_builder!(DateTypeOptionBuilder);
-impl_builder_from_json_str_and_from_bytes!(DateTypeOptionBuilder, DateTypeOption);
-
-impl DateTypeOptionBuilder {
-    pub fn date_format(mut self, date_format: DateFormat) -> Self {
-        self.0.date_format = date_format;
-        self
-    }
-
-    pub fn time_format(mut self, time_format: TimeFormat) -> Self {
-        self.0.time_format = time_format;
-        self
-    }
-}
-impl TypeOptionBuilder for DateTypeOptionBuilder {
-    fn field_type(&self) -> FieldType {
-        FieldType::DateTime
-    }
-
-    fn entry(&self) -> &dyn TypeOptionDataEntry {
-        &self.0
-    }
-}
-
-#[derive(Clone, Debug, Copy, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
-pub enum DateFormat {
-    Local = 0,
-    US = 1,
-    ISO = 2,
-    Friendly = 3,
-}
-impl std::default::Default for DateFormat {
-    fn default() -> Self {
-        DateFormat::Friendly
-    }
-}
-
-impl std::convert::From<i32> for DateFormat {
-    fn from(value: i32) -> Self {
-        match value {
-            0 => DateFormat::Local,
-            1 => DateFormat::US,
-            2 => DateFormat::ISO,
-            3 => DateFormat::Friendly,
-            _ => {
-                tracing::error!("Unsupported date format, fallback to friendly");
-                DateFormat::Friendly
-            }
-        }
-    }
-}
-
-impl DateFormat {
-    pub fn value(&self) -> i32 {
-        *self as i32
-    }
-    // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
-    pub fn format_str(&self) -> &'static str {
-        match self {
-            DateFormat::Local => "%Y/%m/%d",
-            DateFormat::US => "%Y/%m/%d",
-            DateFormat::ISO => "%Y-%m-%d",
-            DateFormat::Friendly => "%b %d,%Y",
-        }
-    }
-}
-
-#[derive(Clone, Copy, PartialEq, Eq, EnumIter, Debug, Hash, Serialize, Deserialize, ProtoBuf_Enum)]
-pub enum TimeFormat {
-    TwelveHour = 0,
-    TwentyFourHour = 1,
-}
-
-impl std::convert::From<i32> for TimeFormat {
-    fn from(value: i32) -> Self {
-        match value {
-            0 => TimeFormat::TwelveHour,
-            1 => TimeFormat::TwentyFourHour,
-            _ => {
-                tracing::error!("Unsupported time format, fallback to TwentyFourHour");
-                TimeFormat::TwentyFourHour
-            }
-        }
-    }
-}
-
-impl TimeFormat {
-    pub fn value(&self) -> i32 {
-        *self as i32
-    }
-
-    // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
-    pub fn format_str(&self) -> &'static str {
-        match self {
-            TimeFormat::TwelveHour => "%I:%M %p",
-            TimeFormat::TwentyFourHour => "%R",
-        }
-    }
-}
-
-impl std::default::Default for TimeFormat {
-    fn default() -> Self {
-        TimeFormat::TwentyFourHour
-    }
-}
-
-#[derive(Clone, Debug, Default, ProtoBuf)]
-pub struct DateCellData {
-    #[pb(index = 1)]
-    pub date: String,
-
-    #[pb(index = 2)]
-    pub time: String,
-
-    #[pb(index = 3)]
-    pub timestamp: i64,
-}
-
-#[derive(Clone, Debug, Default, ProtoBuf)]
-pub struct DateChangesetPayload {
-    #[pb(index = 1)]
-    pub cell_identifier: CellIdentifierPayload,
-
-    #[pb(index = 2, one_of)]
-    pub date: Option<String>,
-
-    #[pb(index = 3, one_of)]
-    pub time: Option<String>,
-}
-
-pub struct DateChangesetParams {
-    pub cell_identifier: CellIdentifier,
-    pub date: Option<String>,
-    pub time: Option<String>,
-}
-
-impl TryInto<DateChangesetParams> for DateChangesetPayload {
-    type Error = ErrorCode;
-
-    fn try_into(self) -> Result<DateChangesetParams, Self::Error> {
-        let cell_identifier: CellIdentifier = self.cell_identifier.try_into()?;
-        Ok(DateChangesetParams {
-            cell_identifier,
-            date: self.date,
-            time: self.time,
-        })
-    }
-}
-
-impl std::convert::From<DateChangesetParams> for CellChangeset {
-    fn from(params: DateChangesetParams) -> Self {
-        let changeset = DateCellChangeset {
-            date: params.date,
-            time: params.time,
-        };
-        let s = serde_json::to_string(&changeset).unwrap();
-        CellChangeset {
-            grid_id: params.cell_identifier.grid_id,
-            row_id: params.cell_identifier.row_id,
-            field_id: params.cell_identifier.field_id,
-            content: Some(s),
-        }
-    }
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-pub struct DateCellChangeset {
-    pub date: Option<String>,
-    pub time: Option<String>,
-}
-
-impl DateCellChangeset {
-    pub fn date_timestamp(&self) -> Option<i64> {
-        if let Some(date) = &self.date {
-            match date.parse::<i64>() {
-                Ok(date_timestamp) => Some(date_timestamp),
-                Err(_) => None,
-            }
-        } else {
-            None
-        }
-    }
-}
-
-impl FromCellChangeset for DateCellChangeset {
-    fn from_changeset(changeset: String) -> FlowyResult<Self>
-    where
-        Self: Sized,
-    {
-        serde_json::from_str::<DateCellChangeset>(&changeset).map_err(internal_error)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::entities::FieldType;
-    use crate::services::cell::{CellDataChangeset, CellDataOperation};
-    use crate::services::field::FieldBuilder;
-    use crate::services::field::{DateCellChangeset, DateCellData, DateFormat, DateTypeOption, TimeFormat};
-    use flowy_grid_data_model::revision::FieldRevision;
-    use strum::IntoEnumIterator;
-
-    #[test]
-    fn date_type_option_invalid_input_test() {
-        let type_option = DateTypeOption::default();
-        let field_type = FieldType::DateTime;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some("1e".to_string()),
-                time: Some("23:00".to_owned()),
-            },
-            &field_type,
-            &field_rev,
-            "",
-        );
-    }
-
-    #[test]
-    fn date_type_option_date_format_test() {
-        let mut type_option = DateTypeOption::default();
-        let field_rev = 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_decode_timestamp(1647251762, &type_option, &field_rev, "Mar 14,2022");
-                }
-                DateFormat::US => {
-                    assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022/03/14");
-                }
-                DateFormat::ISO => {
-                    assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022-03-14");
-                }
-                DateFormat::Local => {
-                    assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022/03/14");
-                }
-            }
-        }
-    }
-
-    #[test]
-    fn date_type_option_time_format_test() {
-        let mut type_option = DateTypeOption::default();
-        let field_type = FieldType::DateTime;
-        let field_rev = 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 => {
-                    assert_changeset_result(
-                        &type_option,
-                        DateCellChangeset {
-                            date: Some(1653609600.to_string()),
-                            time: None,
-                        },
-                        &field_type,
-                        &field_rev,
-                        "May 27,2022",
-                    );
-                    assert_changeset_result(
-                        &type_option,
-                        DateCellChangeset {
-                            date: Some(1653609600.to_string()),
-                            time: Some("23:00".to_owned()),
-                        },
-                        &field_type,
-                        &field_rev,
-                        "May 27,2022 23:00",
-                    );
-                }
-                TimeFormat::TwelveHour => {
-                    assert_changeset_result(
-                        &type_option,
-                        DateCellChangeset {
-                            date: Some(1653609600.to_string()),
-                            time: None,
-                        },
-                        &field_type,
-                        &field_rev,
-                        "May 27,2022",
-                    );
-                    //
-                    assert_changeset_result(
-                        &type_option,
-                        DateCellChangeset {
-                            date: Some(1653609600.to_string()),
-                            time: Some("".to_owned()),
-                        },
-                        &field_type,
-                        &field_rev,
-                        "May 27,2022",
-                    );
-
-                    assert_changeset_result(
-                        &type_option,
-                        DateCellChangeset {
-                            date: Some(1653609600.to_string()),
-                            time: Some("11:23 pm".to_owned()),
-                        },
-                        &field_type,
-                        &field_rev,
-                        "May 27,2022 11:23 PM",
-                    );
-                }
-            }
-        }
-    }
-
-    #[test]
-    fn date_type_option_apply_changeset_test() {
-        let mut type_option = DateTypeOption::new();
-        let field_type = FieldType::DateTime;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-        let date_timestamp = "1653609600".to_owned();
-
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some(date_timestamp.clone()),
-                time: None,
-            },
-            &field_type,
-            &field_rev,
-            "May 27,2022",
-        );
-
-        type_option.include_time = true;
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some(date_timestamp.clone()),
-                time: None,
-            },
-            &field_type,
-            &field_rev,
-            "May 27,2022",
-        );
-
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some(date_timestamp.clone()),
-                time: Some("1:00".to_owned()),
-            },
-            &field_type,
-            &field_rev,
-            "May 27,2022 01:00",
-        );
-
-        type_option.time_format = TimeFormat::TwelveHour;
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some(date_timestamp),
-                time: Some("1:00 am".to_owned()),
-            },
-            &field_type,
-            &field_rev,
-            "May 27,2022 01:00 AM",
-        );
-    }
-
-    #[test]
-    #[should_panic]
-    fn date_type_option_apply_changeset_error_test() {
-        let mut type_option = DateTypeOption::new();
-        type_option.include_time = true;
-        let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build();
-        let date_timestamp = "1653609600".to_owned();
-
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some(date_timestamp.clone()),
-                time: Some("1:".to_owned()),
-            },
-            &FieldType::DateTime,
-            &field_rev,
-            "May 27,2022 01:00",
-        );
-
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some(date_timestamp),
-                time: Some("1:00".to_owned()),
-            },
-            &FieldType::DateTime,
-            &field_rev,
-            "May 27,2022 01:00",
-        );
-    }
-
-    #[test]
-    #[should_panic]
-    fn date_type_option_twelve_hours_to_twenty_four_hours() {
-        let mut type_option = DateTypeOption::new();
-        type_option.include_time = true;
-        let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build();
-        let date_timestamp = "1653609600".to_owned();
-
-        assert_changeset_result(
-            &type_option,
-            DateCellChangeset {
-                date: Some(date_timestamp),
-                time: Some("1:00 am".to_owned()),
-            },
-            &FieldType::DateTime,
-            &field_rev,
-            "May 27,2022 01:00",
-        );
-    }
-
-    fn assert_changeset_result(
-        type_option: &DateTypeOption,
-        changeset: DateCellChangeset,
-        _field_type: &FieldType,
-        field_rev: &FieldRevision,
-        expected: &str,
-    ) {
-        let changeset = CellDataChangeset(Some(changeset));
-        let encoded_data = type_option.apply_changeset(changeset, None).unwrap();
-        assert_eq!(
-            expected.to_owned(),
-            decode_cell_data(encoded_data, type_option, field_rev)
-        );
-    }
-
-    fn assert_decode_timestamp(
-        timestamp: i64,
-        type_option: &DateTypeOption,
-        field_rev: &FieldRevision,
-        expected: &str,
-    ) {
-        let s = serde_json::to_string(&DateCellChangeset {
-            date: Some(timestamp.to_string()),
-            time: None,
-        })
-        .unwrap();
-        let encoded_data = type_option.apply_changeset(s.into(), None).unwrap();
-
-        assert_eq!(
-            expected.to_owned(),
-            decode_cell_data(encoded_data, type_option, field_rev)
-        );
-    }
-
-    fn decode_cell_data(encoded_data: String, type_option: &DateTypeOption, field_rev: &FieldRevision) -> String {
-        let decoded_data = type_option
-            .decode_cell_data(encoded_data.into(), &FieldType::DateTime, field_rev)
-            .unwrap()
-            .parse::<DateCellData>()
-            .unwrap();
-
-        if type_option.include_time {
-            format!("{}{}", decoded_data.date, decoded_data.time)
-        } else {
-            decoded_data.date
-        }
-    }
-}

+ 272 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_tests.rs

@@ -0,0 +1,272 @@
+#[cfg(test)]
+mod tests {
+    use crate::entities::FieldType;
+    use crate::services::cell::{CellDataChangeset, CellDataOperation};
+    use crate::services::field::*;
+    // use crate::services::field::{DateCellChangeset, DateCellData, DateFormat, DateTypeOption, TimeFormat};
+    use flowy_grid_data_model::revision::FieldRevision;
+    use strum::IntoEnumIterator;
+
+    #[test]
+    fn date_type_option_invalid_input_test() {
+        let type_option = DateTypeOption::default();
+        let field_type = FieldType::DateTime;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some("1e".to_string()),
+                time: Some("23:00".to_owned()),
+            },
+            &field_type,
+            &field_rev,
+            "",
+        );
+    }
+
+    #[test]
+    fn date_type_option_date_format_test() {
+        let mut type_option = DateTypeOption::default();
+        let field_rev = 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_decode_timestamp(1647251762, &type_option, &field_rev, "Mar 14,2022");
+                }
+                DateFormat::US => {
+                    assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022/03/14");
+                }
+                DateFormat::ISO => {
+                    assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022-03-14");
+                }
+                DateFormat::Local => {
+                    assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022/03/14");
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn date_type_option_time_format_test() {
+        let mut type_option = DateTypeOption::default();
+        let field_type = FieldType::DateTime;
+        let field_rev = 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 => {
+                    assert_changeset_result(
+                        &type_option,
+                        DateCellChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: None,
+                        },
+                        &field_type,
+                        &field_rev,
+                        "May 27,2022",
+                    );
+                    assert_changeset_result(
+                        &type_option,
+                        DateCellChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: Some("23:00".to_owned()),
+                        },
+                        &field_type,
+                        &field_rev,
+                        "May 27,2022 23:00",
+                    );
+                }
+                TimeFormat::TwelveHour => {
+                    assert_changeset_result(
+                        &type_option,
+                        DateCellChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: None,
+                        },
+                        &field_type,
+                        &field_rev,
+                        "May 27,2022",
+                    );
+                    //
+                    assert_changeset_result(
+                        &type_option,
+                        DateCellChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: Some("".to_owned()),
+                        },
+                        &field_type,
+                        &field_rev,
+                        "May 27,2022",
+                    );
+
+                    assert_changeset_result(
+                        &type_option,
+                        DateCellChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: Some("11:23 pm".to_owned()),
+                        },
+                        &field_type,
+                        &field_rev,
+                        "May 27,2022 11:23 PM",
+                    );
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn date_type_option_apply_changeset_test() {
+        let mut type_option = DateTypeOption::new();
+        let field_type = FieldType::DateTime;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+        let date_timestamp = "1653609600".to_owned();
+
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some(date_timestamp.clone()),
+                time: None,
+            },
+            &field_type,
+            &field_rev,
+            "May 27,2022",
+        );
+
+        type_option.include_time = true;
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some(date_timestamp.clone()),
+                time: None,
+            },
+            &field_type,
+            &field_rev,
+            "May 27,2022",
+        );
+
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some(date_timestamp.clone()),
+                time: Some("1:00".to_owned()),
+            },
+            &field_type,
+            &field_rev,
+            "May 27,2022 01:00",
+        );
+
+        type_option.time_format = TimeFormat::TwelveHour;
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some(date_timestamp),
+                time: Some("1:00 am".to_owned()),
+            },
+            &field_type,
+            &field_rev,
+            "May 27,2022 01:00 AM",
+        );
+    }
+
+    #[test]
+    #[should_panic]
+    fn date_type_option_apply_changeset_error_test() {
+        let mut type_option = DateTypeOption::new();
+        type_option.include_time = true;
+        let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build();
+        let date_timestamp = "1653609600".to_owned();
+
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some(date_timestamp.clone()),
+                time: Some("1:".to_owned()),
+            },
+            &FieldType::DateTime,
+            &field_rev,
+            "May 27,2022 01:00",
+        );
+
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some(date_timestamp),
+                time: Some("1:00".to_owned()),
+            },
+            &FieldType::DateTime,
+            &field_rev,
+            "May 27,2022 01:00",
+        );
+    }
+
+    #[test]
+    #[should_panic]
+    fn date_type_option_twelve_hours_to_twenty_four_hours() {
+        let mut type_option = DateTypeOption::new();
+        type_option.include_time = true;
+        let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build();
+        let date_timestamp = "1653609600".to_owned();
+
+        assert_changeset_result(
+            &type_option,
+            DateCellChangeset {
+                date: Some(date_timestamp),
+                time: Some("1:00 am".to_owned()),
+            },
+            &FieldType::DateTime,
+            &field_rev,
+            "May 27,2022 01:00",
+        );
+    }
+
+    fn assert_changeset_result(
+        type_option: &DateTypeOption,
+        changeset: DateCellChangeset,
+        _field_type: &FieldType,
+        field_rev: &FieldRevision,
+        expected: &str,
+    ) {
+        let changeset = CellDataChangeset(Some(changeset));
+        let encoded_data = type_option.apply_changeset(changeset, None).unwrap();
+        assert_eq!(
+            expected.to_owned(),
+            decode_cell_data(encoded_data, type_option, field_rev)
+        );
+    }
+
+    fn assert_decode_timestamp(
+        timestamp: i64,
+        type_option: &DateTypeOption,
+        field_rev: &FieldRevision,
+        expected: &str,
+    ) {
+        let s = serde_json::to_string(&DateCellChangeset {
+            date: Some(timestamp.to_string()),
+            time: None,
+        })
+        .unwrap();
+        let encoded_data = type_option.apply_changeset(s.into(), None).unwrap();
+
+        assert_eq!(
+            expected.to_owned(),
+            decode_cell_data(encoded_data, type_option, field_rev)
+        );
+    }
+
+    fn decode_cell_data(encoded_data: String, type_option: &DateTypeOption, field_rev: &FieldRevision) -> String {
+        let decoded_data = type_option
+            .decode_cell_data(encoded_data.into(), &FieldType::DateTime, field_rev)
+            .unwrap()
+            .with_parser(DateCellDataParser())
+            .unwrap();
+
+        if type_option.include_time {
+            format!("{}{}", decoded_data.date, decoded_data.time)
+        } else {
+            decoded_data.date
+        }
+    }
+}

+ 195 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs

@@ -0,0 +1,195 @@
+use crate::entities::FieldType;
+use crate::impl_type_option;
+use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
+use crate::services::field::{
+    BoxTypeOptionBuilder, DateCellChangeset, DateCellData, DateFormat, DateTimestamp, TimeFormat, TypeOptionBuilder,
+};
+use bytes::Bytes;
+use chrono::format::strftime::StrftimeItems;
+use chrono::{NaiveDateTime, Timelike};
+use flowy_derive::ProtoBuf;
+use flowy_error::{ErrorCode, FlowyError, FlowyResult};
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
+use serde::{Deserialize, Serialize};
+
+// Date
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
+pub struct DateTypeOption {
+    #[pb(index = 1)]
+    pub date_format: DateFormat,
+
+    #[pb(index = 2)]
+    pub time_format: TimeFormat,
+
+    #[pb(index = 3)]
+    pub include_time: bool,
+}
+impl_type_option!(DateTypeOption, FieldType::DateTime);
+
+impl DateTypeOption {
+    #[allow(dead_code)]
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    fn today_desc_from_timestamp<T: AsRef<i64>>(&self, timestamp: T) -> DateCellData {
+        let timestamp = *timestamp.as_ref();
+        let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0);
+        self.date_from_native(native)
+    }
+
+    fn date_from_native(&self, native: chrono::NaiveDateTime) -> DateCellData {
+        if native.timestamp() == 0 {
+            return DateCellData::default();
+        }
+
+        let time = native.time();
+        let has_time = time.hour() != 0 || time.second() != 0;
+
+        let utc = self.utc_date_time_from_native(native);
+        let fmt = self.date_format.format_str();
+        let date = format!("{}", utc.format_with_items(StrftimeItems::new(fmt)));
+
+        let mut time = "".to_string();
+        if has_time {
+            let fmt = format!("{} {}", self.date_format.format_str(), self.time_format.format_str());
+            time = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt))).replace(&date, "");
+        }
+
+        let timestamp = native.timestamp();
+        DateCellData { date, time, timestamp }
+    }
+
+    fn date_fmt(&self, time: &Option<String>) -> String {
+        if self.include_time {
+            match time.as_ref() {
+                None => self.date_format.format_str().to_string(),
+                Some(time_str) => {
+                    if time_str.is_empty() {
+                        self.date_format.format_str().to_string()
+                    } else {
+                        format!("{} {}", self.date_format.format_str(), self.time_format.format_str())
+                    }
+                }
+            }
+        } else {
+            self.date_format.format_str().to_string()
+        }
+    }
+
+    fn timestamp_from_utc_with_time(
+        &self,
+        utc: &chrono::DateTime<chrono::Utc>,
+        time: &Option<String>,
+    ) -> FlowyResult<i64> {
+        if let Some(time_str) = time.as_ref() {
+            if !time_str.is_empty() {
+                let date_str = format!(
+                    "{}{}",
+                    utc.format_with_items(StrftimeItems::new(self.date_format.format_str())),
+                    &time_str
+                );
+
+                return match NaiveDateTime::parse_from_str(&date_str, &self.date_fmt(time)) {
+                    Ok(native) => {
+                        let utc = self.utc_date_time_from_native(native);
+                        Ok(utc.timestamp())
+                    }
+                    Err(_e) => {
+                        let msg = format!("Parse {} failed", date_str);
+                        Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg))
+                    }
+                };
+            }
+        }
+
+        Ok(utc.timestamp())
+    }
+
+    fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime<chrono::Utc> {
+        let native = NaiveDateTime::from_timestamp(timestamp, 0);
+        self.utc_date_time_from_native(native)
+    }
+
+    fn utc_date_time_from_native(&self, naive: chrono::NaiveDateTime) -> chrono::DateTime<chrono::Utc> {
+        chrono::DateTime::<chrono::Utc>::from_utc(naive, chrono::Utc)
+    }
+}
+
+impl CellDisplayable<DateTimestamp> for DateTypeOption {
+    fn display_data(
+        &self,
+        cell_data: CellData<DateTimestamp>,
+        _decoded_field_type: &FieldType,
+        _field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        let timestamp = cell_data.try_into_inner()?;
+        let date_cell_data = self.today_desc_from_timestamp(timestamp);
+        CellBytes::from(date_cell_data)
+    }
+}
+
+impl CellDataOperation<DateTimestamp, DateCellChangeset> for DateTypeOption {
+    fn decode_cell_data(
+        &self,
+        cell_data: CellData<DateTimestamp>,
+        decoded_field_type: &FieldType,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        // 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(CellBytes::default());
+        }
+        self.display_data(cell_data, decoded_field_type, field_rev)
+    }
+
+    fn apply_changeset(
+        &self,
+        changeset: CellDataChangeset<DateCellChangeset>,
+        _cell_rev: Option<CellRevision>,
+    ) -> Result<String, FlowyError> {
+        let changeset = changeset.try_into_inner()?;
+        let cell_data = match changeset.date_timestamp() {
+            None => 0,
+            Some(date_timestamp) => match (self.include_time, changeset.time) {
+                (true, Some(time)) => {
+                    let time = Some(time.trim().to_uppercase());
+                    let utc = self.utc_date_time_from_timestamp(date_timestamp);
+                    self.timestamp_from_utc_with_time(&utc, &time)?
+                }
+                _ => date_timestamp,
+            },
+        };
+
+        Ok(cell_data.to_string())
+    }
+}
+
+#[derive(Default)]
+pub struct DateTypeOptionBuilder(DateTypeOption);
+impl_into_box_type_option_builder!(DateTypeOptionBuilder);
+impl_builder_from_json_str_and_from_bytes!(DateTypeOptionBuilder, DateTypeOption);
+
+impl DateTypeOptionBuilder {
+    pub fn date_format(mut self, date_format: DateFormat) -> Self {
+        self.0.date_format = date_format;
+        self
+    }
+
+    pub fn time_format(mut self, time_format: TimeFormat) -> Self {
+        self.0.time_format = time_format;
+        self
+    }
+}
+impl TypeOptionBuilder for DateTypeOptionBuilder {
+    fn field_type(&self) -> FieldType {
+        FieldType::DateTime
+    }
+
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
+    }
+}

+ 210 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs

@@ -0,0 +1,210 @@
+use crate::entities::CellChangeset;
+use crate::entities::{CellIdentifier, CellIdentifierPayload};
+use crate::services::cell::{CellBytesParser, FromCellChangeset, FromCellString};
+use bytes::Bytes;
+
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::{internal_error, ErrorCode, FlowyResult};
+
+use serde::{Deserialize, Serialize};
+use strum_macros::EnumIter;
+
+#[derive(Clone, Debug, Default, ProtoBuf)]
+pub struct DateCellData {
+    #[pb(index = 1)]
+    pub date: String,
+
+    #[pb(index = 2)]
+    pub time: String,
+
+    #[pb(index = 3)]
+    pub timestamp: i64,
+}
+
+#[derive(Clone, Debug, Default, ProtoBuf)]
+pub struct DateChangesetPayload {
+    #[pb(index = 1)]
+    pub cell_identifier: CellIdentifierPayload,
+
+    #[pb(index = 2, one_of)]
+    pub date: Option<String>,
+
+    #[pb(index = 3, one_of)]
+    pub time: Option<String>,
+}
+
+pub struct DateChangesetParams {
+    pub cell_identifier: CellIdentifier,
+    pub date: Option<String>,
+    pub time: Option<String>,
+}
+
+impl TryInto<DateChangesetParams> for DateChangesetPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<DateChangesetParams, Self::Error> {
+        let cell_identifier: CellIdentifier = self.cell_identifier.try_into()?;
+        Ok(DateChangesetParams {
+            cell_identifier,
+            date: self.date,
+            time: self.time,
+        })
+    }
+}
+
+impl std::convert::From<DateChangesetParams> for CellChangeset {
+    fn from(params: DateChangesetParams) -> Self {
+        let changeset = DateCellChangeset {
+            date: params.date,
+            time: params.time,
+        };
+        let s = serde_json::to_string(&changeset).unwrap();
+        CellChangeset {
+            grid_id: params.cell_identifier.grid_id,
+            row_id: params.cell_identifier.row_id,
+            field_id: params.cell_identifier.field_id,
+            content: Some(s),
+        }
+    }
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+pub struct DateCellChangeset {
+    pub date: Option<String>,
+    pub time: Option<String>,
+}
+
+impl DateCellChangeset {
+    pub fn date_timestamp(&self) -> Option<i64> {
+        if let Some(date) = &self.date {
+            match date.parse::<i64>() {
+                Ok(date_timestamp) => Some(date_timestamp),
+                Err(_) => None,
+            }
+        } else {
+            None
+        }
+    }
+}
+
+impl FromCellChangeset for DateCellChangeset {
+    fn from_changeset(changeset: String) -> FlowyResult<Self>
+    where
+        Self: Sized,
+    {
+        serde_json::from_str::<DateCellChangeset>(&changeset).map_err(internal_error)
+    }
+}
+pub struct DateTimestamp(i64);
+impl AsRef<i64> for DateTimestamp {
+    fn as_ref(&self) -> &i64 {
+        &self.0
+    }
+}
+
+impl std::convert::From<DateTimestamp> for i64 {
+    fn from(timestamp: DateTimestamp) -> Self {
+        timestamp.0
+    }
+}
+
+impl FromCellString for DateTimestamp {
+    fn from_cell_str(s: &str) -> FlowyResult<Self>
+    where
+        Self: Sized,
+    {
+        let num = s.parse::<i64>().unwrap_or(0);
+        Ok(DateTimestamp(num))
+    }
+}
+
+#[derive(Clone, Debug, Copy, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
+pub enum DateFormat {
+    Local = 0,
+    US = 1,
+    ISO = 2,
+    Friendly = 3,
+}
+impl std::default::Default for DateFormat {
+    fn default() -> Self {
+        DateFormat::Friendly
+    }
+}
+
+impl std::convert::From<i32> for DateFormat {
+    fn from(value: i32) -> Self {
+        match value {
+            0 => DateFormat::Local,
+            1 => DateFormat::US,
+            2 => DateFormat::ISO,
+            3 => DateFormat::Friendly,
+            _ => {
+                tracing::error!("Unsupported date format, fallback to friendly");
+                DateFormat::Friendly
+            }
+        }
+    }
+}
+
+impl DateFormat {
+    pub fn value(&self) -> i32 {
+        *self as i32
+    }
+    // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
+    pub fn format_str(&self) -> &'static str {
+        match self {
+            DateFormat::Local => "%Y/%m/%d",
+            DateFormat::US => "%Y/%m/%d",
+            DateFormat::ISO => "%Y-%m-%d",
+            DateFormat::Friendly => "%b %d,%Y",
+        }
+    }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, EnumIter, Debug, Hash, Serialize, Deserialize, ProtoBuf_Enum)]
+pub enum TimeFormat {
+    TwelveHour = 0,
+    TwentyFourHour = 1,
+}
+
+impl std::convert::From<i32> for TimeFormat {
+    fn from(value: i32) -> Self {
+        match value {
+            0 => TimeFormat::TwelveHour,
+            1 => TimeFormat::TwentyFourHour,
+            _ => {
+                tracing::error!("Unsupported time format, fallback to TwentyFourHour");
+                TimeFormat::TwentyFourHour
+            }
+        }
+    }
+}
+
+impl TimeFormat {
+    pub fn value(&self) -> i32 {
+        *self as i32
+    }
+
+    // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
+    pub fn format_str(&self) -> &'static str {
+        match self {
+            TimeFormat::TwelveHour => "%I:%M %p",
+            TimeFormat::TwentyFourHour => "%R",
+        }
+    }
+}
+
+impl std::default::Default for TimeFormat {
+    fn default() -> Self {
+        TimeFormat::TwentyFourHour
+    }
+}
+
+pub struct DateCellDataParser();
+impl CellBytesParser for DateCellDataParser {
+    type Object = DateCellData;
+
+    fn parse(&self, bytes: &Bytes) -> FlowyResult<Self::Object> {
+        DateCellData::try_from(bytes.as_ref()).map_err(internal_error)
+    }
+}

+ 7 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/mod.rs

@@ -0,0 +1,7 @@
+#![allow(clippy::module_inception)]
+mod date_tests;
+mod date_type_option;
+mod date_type_option_entities;
+
+pub use date_type_option::*;
+pub use date_type_option_entities::*;

+ 7 - 10
frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs

@@ -1,17 +1,14 @@
-mod checkbox_type_option;
-mod date_type_option;
-mod multi_select_type_option;
-mod number_type_option;
-mod single_select_type_option;
-mod text_type_option;
-mod url_type_option;
+pub mod checkbox_type_option;
+pub mod date_type_option;
+pub mod number_type_option;
+pub mod selection_type_option;
+pub mod text_type_option;
+pub mod url_type_option;
 mod util;
 
 pub use checkbox_type_option::*;
 pub use date_type_option::*;
-pub use multi_select_type_option::*;
-pub use multi_select_type_option::*;
 pub use number_type_option::*;
-pub use single_select_type_option::*;
+pub use selection_type_option::*;
 pub use text_type_option::*;
 pub use url_type_option::*;

+ 3 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs

@@ -1,6 +1,9 @@
 #![allow(clippy::module_inception)]
 mod format;
+mod number_tests;
 mod number_type_option;
+mod number_type_option_entities;
 
 pub use format::*;
 pub use number_type_option::*;
+pub use number_type_option_entities::*;

+ 139 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_tests.rs

@@ -0,0 +1,139 @@
+#[cfg(test)]
+mod tests {
+    use crate::entities::FieldType;
+    use crate::services::cell::CellDataOperation;
+    use crate::services::field::FieldBuilder;
+    use crate::services::field::{strip_currency_symbol, NumberFormat, NumberTypeOption};
+    use flowy_grid_data_model::revision::FieldRevision;
+    use strum::IntoEnumIterator;
+
+    #[test]
+    fn number_type_option_invalid_input_test() {
+        let type_option = NumberTypeOption::default();
+        let field_type = FieldType::Number;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+        assert_equal(&type_option, "", "", &field_type, &field_rev);
+        assert_equal(&type_option, "abc", "", &field_type, &field_rev);
+    }
+
+    #[test]
+    fn number_type_option_strip_symbol_test() {
+        let mut type_option = NumberTypeOption::new();
+        type_option.format = NumberFormat::USD;
+        assert_eq!(strip_currency_symbol("$18,443"), "18,443".to_owned());
+
+        type_option.format = NumberFormat::Yuan;
+        assert_eq!(strip_currency_symbol("$0.2"), "0.2".to_owned());
+    }
+
+    #[test]
+    fn number_type_option_format_number_test() {
+        let mut type_option = NumberTypeOption::default();
+        let field_type = FieldType::Number;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+
+        for format in NumberFormat::iter() {
+            type_option.format = format;
+            match format {
+                NumberFormat::Num => {
+                    assert_equal(&type_option, "18443", "18443", &field_type, &field_rev);
+                }
+                NumberFormat::USD => {
+                    assert_equal(&type_option, "18443", "$18,443", &field_type, &field_rev);
+                }
+                NumberFormat::Yen => {
+                    assert_equal(&type_option, "18443", "¥18,443", &field_type, &field_rev);
+                }
+                NumberFormat::Yuan => {
+                    assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_rev);
+                }
+                NumberFormat::EUR => {
+                    assert_equal(&type_option, "18443", "€18.443", &field_type, &field_rev);
+                }
+                _ => {}
+            }
+        }
+    }
+
+    #[test]
+    fn number_type_option_format_str_test() {
+        let mut type_option = NumberTypeOption::default();
+        let field_type = FieldType::Number;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+
+        for format in NumberFormat::iter() {
+            type_option.format = format;
+            match format {
+                NumberFormat::Num => {
+                    assert_equal(&type_option, "18443", "18443", &field_type, &field_rev);
+                    assert_equal(&type_option, "0.2", "0.2", &field_type, &field_rev);
+                }
+                NumberFormat::USD => {
+                    assert_equal(&type_option, "$18,44", "$1,844", &field_type, &field_rev);
+                    assert_equal(&type_option, "$0.2", "$0.2", &field_type, &field_rev);
+                    assert_equal(&type_option, "", "", &field_type, &field_rev);
+                    assert_equal(&type_option, "abc", "", &field_type, &field_rev);
+                }
+                NumberFormat::Yen => {
+                    assert_equal(&type_option, "¥18,44", "¥1,844", &field_type, &field_rev);
+                    assert_equal(&type_option, "¥1844", "¥1,844", &field_type, &field_rev);
+                }
+                NumberFormat::Yuan => {
+                    assert_equal(&type_option, "CN¥18,44", "CN¥1,844", &field_type, &field_rev);
+                    assert_equal(&type_option, "CN¥1844", "CN¥1,844", &field_type, &field_rev);
+                }
+                NumberFormat::EUR => {
+                    assert_equal(&type_option, "€18.44", "€18,44", &field_type, &field_rev);
+                    assert_equal(&type_option, "€0.5", "€0,5", &field_type, &field_rev);
+                    assert_equal(&type_option, "€1844", "€1.844", &field_type, &field_rev);
+                }
+                _ => {}
+            }
+        }
+    }
+
+    #[test]
+    fn number_description_sign_test() {
+        let mut type_option = NumberTypeOption {
+            sign_positive: false,
+            ..Default::default()
+        };
+        let field_type = FieldType::Number;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+
+        for format in NumberFormat::iter() {
+            type_option.format = format;
+            match format {
+                NumberFormat::Num => {
+                    assert_equal(&type_option, "18443", "18443", &field_type, &field_rev);
+                }
+                NumberFormat::USD => {
+                    assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_rev);
+                }
+                NumberFormat::Yen => {
+                    assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_rev);
+                }
+                NumberFormat::EUR => {
+                    assert_equal(&type_option, "18443", "-€18.443", &field_type, &field_rev);
+                }
+                _ => {}
+            }
+        }
+    }
+
+    fn assert_equal(
+        type_option: &NumberTypeOption,
+        cell_data: &str,
+        expected_str: &str,
+        field_type: &FieldType,
+        field_rev: &FieldRevision,
+    ) {
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data.to_owned().into(), field_type, field_rev)
+                .unwrap()
+                .to_string(),
+            expected_str.to_owned()
+        );
+    }
+}

+ 8 - 237
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs

@@ -1,17 +1,15 @@
-use crate::impl_type_option;
-
 use crate::entities::FieldType;
-use crate::services::cell::{CellData, CellDataChangeset, CellDataOperation, DecodedCellData};
-use crate::services::field::number_currency::Currency;
+use crate::impl_type_option;
+use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation};
 use crate::services::field::type_options::number_type_option::format::*;
-use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
+use crate::services::field::{BoxTypeOptionBuilder, NumberCellData, TypeOptionBuilder};
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
 
 use rust_decimal::Decimal;
-use rusty_money::Money;
+
 use serde::{Deserialize, Serialize};
 use std::str::FromStr;
 
@@ -110,15 +108,15 @@ impl CellDataOperation<String, String> for NumberTypeOption {
         cell_data: CellData<String>,
         decoded_field_type: &FieldType,
         _field_rev: &FieldRevision,
-    ) -> FlowyResult<DecodedCellData> {
+    ) -> FlowyResult<CellBytes> {
         if decoded_field_type.is_date() {
-            return Ok(DecodedCellData::default());
+            return Ok(CellBytes::default());
         }
 
         let cell_data: String = cell_data.try_into_inner()?;
         match self.format_cell_data(&cell_data) {
-            Ok(num) => Ok(DecodedCellData::new(num.to_string())),
-            Err(_) => Ok(DecodedCellData::default()),
+            Ok(num) => Ok(CellBytes::new(num.to_string())),
+            Err(_) => Ok(CellBytes::default()),
         }
     }
 
@@ -147,230 +145,3 @@ impl std::default::Default for NumberTypeOption {
         }
     }
 }
-
-#[derive(Default)]
-pub struct NumberCellData {
-    decimal: Option<Decimal>,
-    money: Option<String>,
-}
-
-impl NumberCellData {
-    pub fn new() -> Self {
-        Self {
-            decimal: Default::default(),
-            money: None,
-        }
-    }
-
-    pub fn from_format_str(s: &str, sign_positive: bool, format: &NumberFormat) -> FlowyResult<Self> {
-        let mut num_str = strip_currency_symbol(s);
-        let currency = format.currency();
-        if num_str.is_empty() {
-            return Ok(Self::default());
-        }
-        match Decimal::from_str(&num_str) {
-            Ok(mut decimal) => {
-                decimal.set_sign_positive(sign_positive);
-                let money = Money::from_decimal(decimal, currency);
-                Ok(Self::from_money(money))
-            }
-            Err(_) => match Money::from_str(&num_str, currency) {
-                Ok(money) => Ok(NumberCellData::from_money(money)),
-                Err(_) => {
-                    num_str.retain(|c| !STRIP_SYMBOL.contains(&c.to_string()));
-                    if num_str.chars().all(char::is_numeric) {
-                        Self::from_format_str(&num_str, sign_positive, format)
-                    } else {
-                        Err(FlowyError::invalid_data().context("Should only contain numbers"))
-                    }
-                }
-            },
-        }
-    }
-
-    pub fn from_decimal(decimal: Decimal) -> Self {
-        Self {
-            decimal: Some(decimal),
-            money: None,
-        }
-    }
-
-    pub fn from_money(money: Money<Currency>) -> Self {
-        Self {
-            decimal: Some(*money.amount()),
-            money: Some(money.to_string()),
-        }
-    }
-
-    pub fn decimal(&self) -> &Option<Decimal> {
-        &self.decimal
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.decimal.is_none()
-    }
-}
-
-impl FromStr for NumberCellData {
-    type Err = rust_decimal::Error;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        if s.is_empty() {
-            return Ok(Self::default());
-        }
-        let decimal = Decimal::from_str(s)?;
-        Ok(Self::from_decimal(decimal))
-    }
-}
-
-impl ToString for NumberCellData {
-    fn to_string(&self) -> String {
-        match &self.money {
-            None => match self.decimal {
-                None => String::default(),
-                Some(decimal) => decimal.to_string(),
-            },
-            Some(money) => money.to_string(),
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::entities::FieldType;
-    use crate::services::cell::CellDataOperation;
-    use crate::services::field::FieldBuilder;
-    use crate::services::field::{strip_currency_symbol, NumberFormat, NumberTypeOption};
-    use flowy_grid_data_model::revision::FieldRevision;
-    use strum::IntoEnumIterator;
-
-    #[test]
-    fn number_type_option_invalid_input_test() {
-        let type_option = NumberTypeOption::default();
-        let field_type = FieldType::Number;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-        assert_equal(&type_option, "", "", &field_type, &field_rev);
-        assert_equal(&type_option, "abc", "", &field_type, &field_rev);
-    }
-
-    #[test]
-    fn number_type_option_strip_symbol_test() {
-        let mut type_option = NumberTypeOption::new();
-        type_option.format = NumberFormat::USD;
-        assert_eq!(strip_currency_symbol("$18,443"), "18,443".to_owned());
-
-        type_option.format = NumberFormat::Yuan;
-        assert_eq!(strip_currency_symbol("$0.2"), "0.2".to_owned());
-    }
-
-    #[test]
-    fn number_type_option_format_number_test() {
-        let mut type_option = NumberTypeOption::default();
-        let field_type = FieldType::Number;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-
-        for format in NumberFormat::iter() {
-            type_option.format = format;
-            match format {
-                NumberFormat::Num => {
-                    assert_equal(&type_option, "18443", "18443", &field_type, &field_rev);
-                }
-                NumberFormat::USD => {
-                    assert_equal(&type_option, "18443", "$18,443", &field_type, &field_rev);
-                }
-                NumberFormat::Yen => {
-                    assert_equal(&type_option, "18443", "¥18,443", &field_type, &field_rev);
-                }
-                NumberFormat::Yuan => {
-                    assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_rev);
-                }
-                NumberFormat::EUR => {
-                    assert_equal(&type_option, "18443", "€18.443", &field_type, &field_rev);
-                }
-                _ => {}
-            }
-        }
-    }
-
-    #[test]
-    fn number_type_option_format_str_test() {
-        let mut type_option = NumberTypeOption::default();
-        let field_type = FieldType::Number;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-
-        for format in NumberFormat::iter() {
-            type_option.format = format;
-            match format {
-                NumberFormat::Num => {
-                    assert_equal(&type_option, "18443", "18443", &field_type, &field_rev);
-                    assert_equal(&type_option, "0.2", "0.2", &field_type, &field_rev);
-                }
-                NumberFormat::USD => {
-                    assert_equal(&type_option, "$18,44", "$1,844", &field_type, &field_rev);
-                    assert_equal(&type_option, "$0.2", "$0.2", &field_type, &field_rev);
-                    assert_equal(&type_option, "", "", &field_type, &field_rev);
-                    assert_equal(&type_option, "abc", "", &field_type, &field_rev);
-                }
-                NumberFormat::Yen => {
-                    assert_equal(&type_option, "¥18,44", "¥1,844", &field_type, &field_rev);
-                    assert_equal(&type_option, "¥1844", "¥1,844", &field_type, &field_rev);
-                }
-                NumberFormat::Yuan => {
-                    assert_equal(&type_option, "CN¥18,44", "CN¥1,844", &field_type, &field_rev);
-                    assert_equal(&type_option, "CN¥1844", "CN¥1,844", &field_type, &field_rev);
-                }
-                NumberFormat::EUR => {
-                    assert_equal(&type_option, "€18.44", "€18,44", &field_type, &field_rev);
-                    assert_equal(&type_option, "€0.5", "€0,5", &field_type, &field_rev);
-                    assert_equal(&type_option, "€1844", "€1.844", &field_type, &field_rev);
-                }
-                _ => {}
-            }
-        }
-    }
-
-    #[test]
-    fn number_description_sign_test() {
-        let mut type_option = NumberTypeOption {
-            sign_positive: false,
-            ..Default::default()
-        };
-        let field_type = FieldType::Number;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-
-        for format in NumberFormat::iter() {
-            type_option.format = format;
-            match format {
-                NumberFormat::Num => {
-                    assert_equal(&type_option, "18443", "18443", &field_type, &field_rev);
-                }
-                NumberFormat::USD => {
-                    assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_rev);
-                }
-                NumberFormat::Yen => {
-                    assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_rev);
-                }
-                NumberFormat::EUR => {
-                    assert_equal(&type_option, "18443", "-€18.443", &field_type, &field_rev);
-                }
-                _ => {}
-            }
-        }
-    }
-
-    fn assert_equal(
-        type_option: &NumberTypeOption,
-        cell_data: &str,
-        expected_str: &str,
-        field_type: &FieldType,
-        field_rev: &FieldRevision,
-    ) {
-        assert_eq!(
-            type_option
-                .decode_cell_data(cell_data.to_owned().into(), field_type, field_rev)
-                .unwrap()
-                .to_string(),
-            expected_str.to_owned()
-        );
-    }
-}

+ 105 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option_entities.rs

@@ -0,0 +1,105 @@
+use crate::services::cell::CellBytesParser;
+use crate::services::field::number_currency::Currency;
+use crate::services::field::{strip_currency_symbol, NumberFormat, STRIP_SYMBOL};
+use bytes::Bytes;
+use flowy_error::{FlowyError, FlowyResult};
+use rust_decimal::Decimal;
+use rusty_money::Money;
+use std::str::FromStr;
+
+#[derive(Default)]
+pub struct NumberCellData {
+    decimal: Option<Decimal>,
+    money: Option<String>,
+}
+
+impl NumberCellData {
+    pub fn new() -> Self {
+        Self {
+            decimal: Default::default(),
+            money: None,
+        }
+    }
+
+    pub fn from_format_str(s: &str, sign_positive: bool, format: &NumberFormat) -> FlowyResult<Self> {
+        let mut num_str = strip_currency_symbol(s);
+        let currency = format.currency();
+        if num_str.is_empty() {
+            return Ok(Self::default());
+        }
+        match Decimal::from_str(&num_str) {
+            Ok(mut decimal) => {
+                decimal.set_sign_positive(sign_positive);
+                let money = Money::from_decimal(decimal, currency);
+                Ok(Self::from_money(money))
+            }
+            Err(_) => match Money::from_str(&num_str, currency) {
+                Ok(money) => Ok(NumberCellData::from_money(money)),
+                Err(_) => {
+                    num_str.retain(|c| !STRIP_SYMBOL.contains(&c.to_string()));
+                    if num_str.chars().all(char::is_numeric) {
+                        Self::from_format_str(&num_str, sign_positive, format)
+                    } else {
+                        Err(FlowyError::invalid_data().context("Should only contain numbers"))
+                    }
+                }
+            },
+        }
+    }
+
+    pub fn from_decimal(decimal: Decimal) -> Self {
+        Self {
+            decimal: Some(decimal),
+            money: None,
+        }
+    }
+
+    pub fn from_money(money: Money<Currency>) -> Self {
+        Self {
+            decimal: Some(*money.amount()),
+            money: Some(money.to_string()),
+        }
+    }
+
+    pub fn decimal(&self) -> &Option<Decimal> {
+        &self.decimal
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.decimal.is_none()
+    }
+}
+
+// impl FromStr for NumberCellData {
+//     type Err = FlowyError;
+//
+//     fn from_str(s: &str) -> Result<Self, Self::Err> {
+//         if s.is_empty() {
+//             return Ok(Self::default());
+//         }
+//         let decimal = Decimal::from_str(s).map_err(internal_error)?;
+//         Ok(Self::from_decimal(decimal))
+//     }
+// }
+
+impl ToString for NumberCellData {
+    fn to_string(&self) -> String {
+        match &self.money {
+            None => match self.decimal {
+                None => String::default(),
+                Some(decimal) => decimal.to_string(),
+            },
+            Some(money) => money.to_string(),
+        }
+    }
+}
+pub struct NumberCellDataParser(pub NumberFormat);
+impl CellBytesParser for NumberCellDataParser {
+    type Object = NumberCellData;
+    fn parse(&self, bytes: &Bytes) -> FlowyResult<Self::Object> {
+        match String::from_utf8(bytes.to_vec()) {
+            Ok(s) => NumberCellData::from_format_str(&s, true, &self.0),
+            Err(_) => Ok(NumberCellData::default()),
+        }
+    }
+}

+ 7 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs

@@ -0,0 +1,7 @@
+mod multi_select_type_option;
+mod select_option;
+mod single_select_type_option;
+
+pub use multi_select_type_option::*;
+pub use select_option::*;
+pub use single_select_type_option::*;

+ 13 - 28
frontend/rust-lib/flowy-grid/src/services/field/type_options/multi_select_type_option.rs → frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs

@@ -1,19 +1,15 @@
 use crate::entities::FieldType;
-
 use crate::impl_type_option;
-use crate::services::cell::{AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData};
-use crate::services::field::select_option::{
-    make_selected_select_options, SelectOption, SelectOptionCellChangeset, SelectOptionCellData, SelectOptionIds,
-    SelectOptionOperation, SELECTION_IDS_SEPARATOR,
-};
+use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
 use crate::services::field::type_options::util::get_cell_data;
-use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
+use crate::services::field::{
+    make_selected_select_options, BoxTypeOptionBuilder, SelectOption, SelectOptionCellChangeset, SelectOptionCellData,
+    SelectOptionIds, SelectOptionOperation, TypeOptionBuilder, SELECTION_IDS_SEPARATOR,
+};
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::{FlowyError, FlowyResult};
-
 use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
-
 use serde::{Deserialize, Serialize};
 
 // Multiple select
@@ -28,8 +24,8 @@ pub struct MultiSelectTypeOption {
 impl_type_option!(MultiSelectTypeOption, FieldType::MultiSelect);
 
 impl SelectOptionOperation for MultiSelectTypeOption {
-    fn selected_select_option(&self, any_cell_data: AnyCellData) -> SelectOptionCellData {
-        let select_options = make_selected_select_options(any_cell_data, &self.options);
+    fn selected_select_option(&self, cell_data: CellData<SelectOptionIds>) -> SelectOptionCellData {
+        let select_options = make_selected_select_options(cell_data, &self.options);
         SelectOptionCellData {
             options: self.options.clone(),
             select_options,
@@ -50,24 +46,13 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for MultiSele
         &self,
         cell_data: CellData<SelectOptionIds>,
         decoded_field_type: &FieldType,
-        _field_rev: &FieldRevision,
-    ) -> FlowyResult<DecodedCellData> {
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
         if !decoded_field_type.is_select_option() {
-            return Ok(DecodedCellData::default());
+            return Ok(CellBytes::default());
         }
 
-        let ids: SelectOptionIds = cell_data.try_into_inner()?;
-        let select_options = ids
-            .iter()
-            .flat_map(|option_id| self.options.iter().find(|option| &option.id == option_id).cloned())
-            .collect::<Vec<SelectOption>>();
-
-        let cell_data = SelectOptionCellData {
-            options: self.options.clone(),
-            select_options,
-        };
-
-        DecodedCellData::try_from_bytes(cell_data)
+        self.display_data(cell_data, decoded_field_type, field_rev)
     }
 
     fn apply_changeset(
@@ -131,7 +116,7 @@ impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
 mod tests {
     use crate::entities::FieldType;
     use crate::services::cell::CellDataOperation;
-    use crate::services::field::select_option::*;
+    use crate::services::field::type_options::selection_type_option::*;
     use crate::services::field::FieldBuilder;
     use crate::services::field::{MultiSelectTypeOption, MultiSelectTypeOptionBuilder};
     use flowy_grid_data_model::revision::FieldRevision;
@@ -195,7 +180,7 @@ mod tests {
             type_option
                 .decode_cell_data(cell_data.into(), &field_type, field_rev)
                 .unwrap()
-                .parse::<SelectOptionCellData>()
+                .with_parser(SelectOptionCellDataParser())
                 .unwrap()
                 .select_options,
         );

+ 40 - 15
frontend/rust-lib/flowy-grid/src/services/field/select_option.rs → frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs

@@ -1,8 +1,9 @@
 use crate::entities::{CellChangeset, CellIdentifier, CellIdentifierPayload, FieldType};
-use crate::services::cell::{AnyCellData, FromCellChangeset, FromCellString};
+use crate::services::cell::{CellBytes, CellBytesParser, CellData, CellDisplayable, FromCellChangeset, FromCellString};
 use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
+use bytes::Bytes;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
+use flowy_error::{internal_error, ErrorCode, FlowyResult};
 use flowy_grid_data_model::parser::NotEmptyStr;
 use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataEntry};
 use nanoid::nanoid;
@@ -60,12 +61,11 @@ impl std::default::Default for SelectOptionColor {
     }
 }
 
-pub fn make_selected_select_options<T: TryInto<AnyCellData>>(
-    any_cell_data: T,
+pub fn make_selected_select_options(
+    cell_data: CellData<SelectOptionIds>,
     options: &[SelectOption],
 ) -> Vec<SelectOption> {
-    if let Ok(type_option_cell_data) = any_cell_data.try_into() {
-        let ids = SelectOptionIds::from(type_option_cell_data.data);
+    if let Ok(ids) = cell_data.try_into_inner() {
         ids.iter()
             .flat_map(|option_id| options.iter().find(|option| &option.id == option_id).cloned())
             .collect()
@@ -100,13 +100,27 @@ pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync {
         SelectOption::with_color(name, color)
     }
 
-    fn selected_select_option(&self, any_cell_data: AnyCellData) -> SelectOptionCellData;
+    fn selected_select_option(&self, cell_data: CellData<SelectOptionIds>) -> SelectOptionCellData;
 
     fn options(&self) -> &Vec<SelectOption>;
 
     fn mut_options(&mut self) -> &mut Vec<SelectOption>;
 }
 
+impl<T> CellDisplayable<SelectOptionIds> for T
+where
+    T: SelectOptionOperation,
+{
+    fn display_data(
+        &self,
+        cell_data: CellData<SelectOptionIds>,
+        _decoded_field_type: &FieldType,
+        _field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        CellBytes::from(self.selected_select_option(cell_data))
+    }
+}
+
 pub fn select_option_operation(field_rev: &FieldRevision) -> FlowyResult<Box<dyn SelectOptionOperation>> {
     let field_type: FieldType = field_rev.field_type_rev.into();
     match &field_type {
@@ -147,14 +161,6 @@ impl SelectOptionIds {
     }
 }
 
-impl std::convert::TryFrom<AnyCellData> for SelectOptionIds {
-    type Error = FlowyError;
-
-    fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
-        Ok(Self::from(value.data))
-    }
-}
-
 impl FromCellString for SelectOptionIds {
     fn from_cell_str(s: &str) -> FlowyResult<Self>
     where
@@ -196,6 +202,25 @@ impl std::ops::DerefMut for SelectOptionIds {
         &mut self.0
     }
 }
+pub struct SelectOptionIdsParser();
+impl CellBytesParser for SelectOptionIdsParser {
+    type Object = SelectOptionIds;
+    fn parse(&self, bytes: &Bytes) -> FlowyResult<Self::Object> {
+        match String::from_utf8(bytes.to_vec()) {
+            Ok(s) => Ok(SelectOptionIds::from(s)),
+            Err(_) => Ok(SelectOptionIds::from("".to_owned())),
+        }
+    }
+}
+
+pub struct SelectOptionCellDataParser();
+impl CellBytesParser for SelectOptionCellDataParser {
+    type Object = SelectOptionCellData;
+
+    fn parse(&self, bytes: &Bytes) -> FlowyResult<Self::Object> {
+        SelectOptionCellData::try_from(bytes.as_ref()).map_err(internal_error)
+    }
+}
 
 #[derive(Clone, Debug, Default, ProtoBuf)]
 pub struct SelectOptionCellChangesetPayload {

+ 12 - 21
frontend/rust-lib/flowy-grid/src/services/field/type_options/single_select_type_option.rs → frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs

@@ -1,7 +1,7 @@
 use crate::entities::FieldType;
 use crate::impl_type_option;
-use crate::services::cell::{AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData};
-use crate::services::field::select_option::{
+use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
+use crate::services::field::{
     make_selected_select_options, SelectOption, SelectOptionCellChangeset, SelectOptionCellData, SelectOptionIds,
     SelectOptionOperation,
 };
@@ -24,8 +24,10 @@ pub struct SingleSelectTypeOption {
 impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect);
 
 impl SelectOptionOperation for SingleSelectTypeOption {
-    fn selected_select_option(&self, any_cell_data: AnyCellData) -> SelectOptionCellData {
-        let select_options = make_selected_select_options(any_cell_data, &self.options);
+    fn selected_select_option(&self, cell_data: CellData<SelectOptionIds>) -> SelectOptionCellData {
+        let mut select_options = make_selected_select_options(cell_data, &self.options);
+        // only keep option in single select
+        select_options.truncate(1);
         SelectOptionCellData {
             options: self.options.clone(),
             select_options,
@@ -46,24 +48,13 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for SingleSel
         &self,
         cell_data: CellData<SelectOptionIds>,
         decoded_field_type: &FieldType,
-        _field_rev: &FieldRevision,
-    ) -> FlowyResult<DecodedCellData> {
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
         if !decoded_field_type.is_select_option() {
-            return Ok(DecodedCellData::default());
+            return Ok(CellBytes::default());
         }
 
-        let ids: SelectOptionIds = cell_data.try_into_inner()?;
-        let mut cell_data = SelectOptionCellData {
-            options: self.options.clone(),
-            select_options: vec![],
-        };
-        if let Some(option_id) = ids.first() {
-            if let Some(option) = self.options.iter().find(|option| &option.id == option_id) {
-                cell_data.select_options.push(option.clone());
-            }
-        }
-
-        DecodedCellData::try_from_bytes(cell_data)
+        self.display_data(cell_data, decoded_field_type, field_rev)
     }
 
     fn apply_changeset(
@@ -111,7 +102,7 @@ impl TypeOptionBuilder for SingleSelectTypeOptionBuilder {
 mod tests {
     use crate::entities::FieldType;
     use crate::services::cell::CellDataOperation;
-    use crate::services::field::select_option::*;
+
     use crate::services::field::type_options::*;
     use crate::services::field::FieldBuilder;
     use flowy_grid_data_model::revision::FieldRevision;
@@ -171,7 +162,7 @@ mod tests {
             type_option
                 .decode_cell_data(cell_data.into(), &field_type, field_rev)
                 .unwrap()
-                .parse::<SelectOptionCellData>()
+                .with_parser(SelectOptionCellDataParser())
                 .unwrap()
                 .select_options,
         );

+ 3 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/mod.rs

@@ -0,0 +1,3 @@
+#![allow(clippy::module_inception)]
+mod text_type_option;
+pub use text_type_option::*;

+ 36 - 12
frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs → frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs

@@ -1,7 +1,8 @@
 use crate::entities::FieldType;
 use crate::impl_type_option;
 use crate::services::cell::{
-    try_decode_cell_data, AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData,
+    try_decode_cell_data, CellBytes, CellBytesParser, CellData, CellDataChangeset, CellDataOperation, CellDisplayable,
+    FromCellString,
 };
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
 use bytes::Bytes;
@@ -32,13 +33,25 @@ pub struct RichTextTypeOption {
 }
 impl_type_option!(RichTextTypeOption, FieldType::RichText);
 
+impl CellDisplayable<String> for RichTextTypeOption {
+    fn display_data(
+        &self,
+        cell_data: CellData<String>,
+        _decoded_field_type: &FieldType,
+        _field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        let cell_str: String = cell_data.try_into_inner()?;
+        Ok(CellBytes::new(cell_str))
+    }
+}
+
 impl CellDataOperation<String, String> for RichTextTypeOption {
     fn decode_cell_data(
         &self,
         cell_data: CellData<String>,
         decoded_field_type: &FieldType,
         field_rev: &FieldRevision,
-    ) -> FlowyResult<DecodedCellData> {
+    ) -> FlowyResult<CellBytes> {
         if decoded_field_type.is_date()
             || decoded_field_type.is_single_select()
             || decoded_field_type.is_multi_select()
@@ -46,8 +59,7 @@ impl CellDataOperation<String, String> for RichTextTypeOption {
         {
             try_decode_cell_data(cell_data, field_rev, decoded_field_type, decoded_field_type)
         } else {
-            let cell_data: String = cell_data.try_into_inner()?;
-            Ok(DecodedCellData::new(cell_data))
+            self.display_data(cell_data, decoded_field_type, field_rev)
         }
     }
 
@@ -72,11 +84,23 @@ impl AsRef<str> for TextCellData {
     }
 }
 
-impl std::convert::TryFrom<AnyCellData> for TextCellData {
-    type Error = FlowyError;
+impl FromCellString for TextCellData {
+    fn from_cell_str(s: &str) -> FlowyResult<Self>
+    where
+        Self: Sized,
+    {
+        Ok(TextCellData(s.to_owned()))
+    }
+}
 
-    fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
-        Ok(TextCellData(value.data))
+pub struct TextCellDataParser();
+impl CellBytesParser for TextCellDataParser {
+    type Object = TextCellData;
+    fn parse(&self, bytes: &Bytes) -> FlowyResult<Self::Object> {
+        match String::from_utf8(bytes.to_vec()) {
+            Ok(s) => Ok(TextCellData(s)),
+            Err(_) => Ok(TextCellData("".to_owned())),
+        }
     }
 }
 
@@ -84,7 +108,7 @@ impl std::convert::TryFrom<AnyCellData> for TextCellData {
 mod tests {
     use crate::entities::FieldType;
     use crate::services::cell::CellDataOperation;
-    use crate::services::field::select_option::*;
+
     use crate::services::field::FieldBuilder;
     use crate::services::field::*;
 
@@ -100,7 +124,7 @@ mod tests {
             type_option
                 .decode_cell_data(1647251762.to_string().into(), &field_type, &date_time_field_rev)
                 .unwrap()
-                .parse::<DateCellData>()
+                .with_parser(DateCellDataParser())
                 .unwrap()
                 .date,
             "Mar 14,2022".to_owned()
@@ -120,7 +144,7 @@ mod tests {
                     &single_select_field_rev
                 )
                 .unwrap()
-                .parse::<SelectOptionCellData>()
+                .with_parser(SelectOptionCellDataParser())
                 .unwrap()
                 .select_options,
             vec![done_option],
@@ -143,7 +167,7 @@ mod tests {
             type_option
                 .decode_cell_data(cell_data.into(), &FieldType::MultiSelect, &multi_select_field_rev)
                 .unwrap()
-                .parse::<SelectOptionCellData>()
+                .with_parser(SelectOptionCellDataParser())
                 .unwrap()
                 .select_options,
             vec![google_option, facebook_option]

+ 0 - 194
frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs

@@ -1,194 +0,0 @@
-use crate::entities::FieldType;
-use crate::impl_type_option;
-use crate::services::cell::{
-    AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData, FromCellString,
-};
-use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use bytes::Bytes;
-use fancy_regex::Regex;
-use flowy_derive::ProtoBuf;
-use flowy_error::{internal_error, FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
-use lazy_static::lazy_static;
-use serde::{Deserialize, Serialize};
-
-#[derive(Default)]
-pub struct URLTypeOptionBuilder(URLTypeOption);
-impl_into_box_type_option_builder!(URLTypeOptionBuilder);
-impl_builder_from_json_str_and_from_bytes!(URLTypeOptionBuilder, URLTypeOption);
-
-impl TypeOptionBuilder for URLTypeOptionBuilder {
-    fn field_type(&self) -> FieldType {
-        FieldType::URL
-    }
-
-    fn entry(&self) -> &dyn TypeOptionDataEntry {
-        &self.0
-    }
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
-pub struct URLTypeOption {
-    #[pb(index = 1)]
-    data: String, //It's not used yet.
-}
-impl_type_option!(URLTypeOption, FieldType::URL);
-
-impl CellDataOperation<URLCellData, String> for URLTypeOption {
-    fn decode_cell_data(
-        &self,
-        cell_data: CellData<URLCellData>,
-        decoded_field_type: &FieldType,
-        _field_rev: &FieldRevision,
-    ) -> FlowyResult<DecodedCellData> {
-        if !decoded_field_type.is_url() {
-            return Ok(DecodedCellData::default());
-        }
-        let cell_data: URLCellData = cell_data.try_into_inner()?;
-        DecodedCellData::try_from_bytes(cell_data)
-    }
-
-    fn apply_changeset(
-        &self,
-        changeset: CellDataChangeset<String>,
-        _cell_rev: Option<CellRevision>,
-    ) -> Result<String, FlowyError> {
-        let changeset = changeset.try_into_inner()?;
-        let mut url = "".to_string();
-        if let Ok(Some(m)) = URL_REGEX.find(&changeset) {
-            url = auto_append_scheme(m.as_str());
-        }
-        URLCellData {
-            url,
-            content: changeset,
-        }
-        .to_json()
-    }
-}
-
-fn auto_append_scheme(s: &str) -> String {
-    // Only support https scheme by now
-    match url::Url::parse(s) {
-        Ok(url) => {
-            if url.scheme() == "https" {
-                url.into()
-            } else {
-                format!("https://{}", s)
-            }
-        }
-        Err(_) => {
-            format!("https://{}", s)
-        }
-    }
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct URLCellData {
-    #[pb(index = 1)]
-    pub url: String,
-
-    #[pb(index = 2)]
-    pub content: String,
-}
-
-impl URLCellData {
-    pub fn new(s: &str) -> Self {
-        Self {
-            url: "".to_string(),
-            content: s.to_string(),
-        }
-    }
-
-    fn to_json(&self) -> FlowyResult<String> {
-        serde_json::to_string(self).map_err(internal_error)
-    }
-}
-
-impl FromCellString for URLCellData {
-    fn from_cell_str(s: &str) -> FlowyResult<Self> {
-        serde_json::from_str::<URLCellData>(s).map_err(internal_error)
-    }
-}
-
-impl std::convert::TryFrom<AnyCellData> for URLCellData {
-    type Error = FlowyError;
-
-    fn try_from(data: AnyCellData) -> Result<Self, Self::Error> {
-        serde_json::from_str::<URLCellData>(&data.data).map_err(internal_error)
-    }
-}
-
-lazy_static! {
-    static ref URL_REGEX: Regex = Regex::new(
-        "[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)"
-    )
-    .unwrap();
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::entities::FieldType;
-    use crate::services::cell::{CellData, CellDataOperation};
-    use crate::services::field::FieldBuilder;
-    use crate::services::field::{URLCellData, URLTypeOption};
-    use flowy_grid_data_model::revision::FieldRevision;
-
-    #[test]
-    fn url_type_option_test_no_url() {
-        let type_option = URLTypeOption::default();
-        let field_type = FieldType::URL;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-        assert_changeset(&type_option, "123", &field_type, &field_rev, "123", "");
-    }
-
-    #[test]
-    fn url_type_option_test_contains_url() {
-        let type_option = URLTypeOption::default();
-        let field_type = FieldType::URL;
-        let field_rev = FieldBuilder::from_field_type(&field_type).build();
-        assert_changeset(
-            &type_option,
-            "AppFlowy website - https://www.appflowy.io",
-            &field_type,
-            &field_rev,
-            "AppFlowy website - https://www.appflowy.io",
-            "https://www.appflowy.io/",
-        );
-
-        assert_changeset(
-            &type_option,
-            "AppFlowy website appflowy.io",
-            &field_type,
-            &field_rev,
-            "AppFlowy website appflowy.io",
-            "https://appflowy.io",
-        );
-    }
-
-    fn assert_changeset(
-        type_option: &URLTypeOption,
-        cell_data: &str,
-        field_type: &FieldType,
-        field_rev: &FieldRevision,
-        expected: &str,
-        expected_url: &str,
-    ) {
-        let encoded_data = type_option.apply_changeset(cell_data.to_owned().into(), None).unwrap();
-        let decode_cell_data = decode_cell_data(encoded_data, type_option, field_rev, field_type);
-        assert_eq!(expected.to_owned(), decode_cell_data.content);
-        assert_eq!(expected_url.to_owned(), decode_cell_data.url);
-    }
-
-    fn decode_cell_data<T: Into<CellData<URLCellData>>>(
-        encoded_data: T,
-        type_option: &URLTypeOption,
-        field_rev: &FieldRevision,
-        field_type: &FieldType,
-    ) -> URLCellData {
-        type_option
-            .decode_cell_data(encoded_data.into(), field_type, field_rev)
-            .unwrap()
-            .parse::<URLCellData>()
-            .unwrap()
-    }
-}

+ 7 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs

@@ -0,0 +1,7 @@
+#![allow(clippy::module_inception)]
+mod url_tests;
+mod url_type_option;
+mod url_type_option_entities;
+
+pub use url_type_option::*;
+pub use url_type_option_entities::*;

+ 67 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_tests.rs

@@ -0,0 +1,67 @@
+#[cfg(test)]
+mod tests {
+    use crate::entities::FieldType;
+    use crate::services::cell::{CellData, CellDataOperation};
+    use crate::services::field::{FieldBuilder, URLCellDataParser};
+    use crate::services::field::{URLCellData, URLTypeOption};
+    use flowy_grid_data_model::revision::FieldRevision;
+
+    #[test]
+    fn url_type_option_test_no_url() {
+        let type_option = URLTypeOption::default();
+        let field_type = FieldType::URL;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+        assert_changeset(&type_option, "123", &field_type, &field_rev, "123", "");
+    }
+
+    #[test]
+    fn url_type_option_test_contains_url() {
+        let type_option = URLTypeOption::default();
+        let field_type = FieldType::URL;
+        let field_rev = FieldBuilder::from_field_type(&field_type).build();
+        assert_changeset(
+            &type_option,
+            "AppFlowy website - https://www.appflowy.io",
+            &field_type,
+            &field_rev,
+            "AppFlowy website - https://www.appflowy.io",
+            "https://www.appflowy.io/",
+        );
+
+        assert_changeset(
+            &type_option,
+            "AppFlowy website appflowy.io",
+            &field_type,
+            &field_rev,
+            "AppFlowy website appflowy.io",
+            "https://appflowy.io",
+        );
+    }
+
+    fn assert_changeset(
+        type_option: &URLTypeOption,
+        cell_data: &str,
+        field_type: &FieldType,
+        field_rev: &FieldRevision,
+        expected: &str,
+        expected_url: &str,
+    ) {
+        let encoded_data = type_option.apply_changeset(cell_data.to_owned().into(), None).unwrap();
+        let decode_cell_data = decode_cell_data(encoded_data, type_option, field_rev, field_type);
+        assert_eq!(expected.to_owned(), decode_cell_data.content);
+        assert_eq!(expected_url.to_owned(), decode_cell_data.url);
+    }
+
+    fn decode_cell_data<T: Into<CellData<URLCellData>>>(
+        encoded_data: T,
+        type_option: &URLTypeOption,
+        field_rev: &FieldRevision,
+        field_type: &FieldType,
+    ) -> URLCellData {
+        type_option
+            .decode_cell_data(encoded_data.into(), field_type, field_rev)
+            .unwrap()
+            .with_parser(URLCellDataParser())
+            .unwrap()
+    }
+}

+ 95 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs

@@ -0,0 +1,95 @@
+use crate::entities::FieldType;
+use crate::impl_type_option;
+use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
+use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder, URLCellData};
+use bytes::Bytes;
+use fancy_regex::Regex;
+use flowy_derive::ProtoBuf;
+use flowy_error::{FlowyError, FlowyResult};
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
+use lazy_static::lazy_static;
+use serde::{Deserialize, Serialize};
+
+#[derive(Default)]
+pub struct URLTypeOptionBuilder(URLTypeOption);
+impl_into_box_type_option_builder!(URLTypeOptionBuilder);
+impl_builder_from_json_str_and_from_bytes!(URLTypeOptionBuilder, URLTypeOption);
+
+impl TypeOptionBuilder for URLTypeOptionBuilder {
+    fn field_type(&self) -> FieldType {
+        FieldType::URL
+    }
+
+    fn entry(&self) -> &dyn TypeOptionDataEntry {
+        &self.0
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
+pub struct URLTypeOption {
+    #[pb(index = 1)]
+    data: String, //It's not used yet.
+}
+impl_type_option!(URLTypeOption, FieldType::URL);
+
+impl CellDisplayable<URLCellData> for URLTypeOption {
+    fn display_data(
+        &self,
+        cell_data: CellData<URLCellData>,
+        _decoded_field_type: &FieldType,
+        _field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        let cell_data: URLCellData = cell_data.try_into_inner()?;
+        CellBytes::from(cell_data)
+    }
+}
+
+impl CellDataOperation<URLCellData, String> for URLTypeOption {
+    fn decode_cell_data(
+        &self,
+        cell_data: CellData<URLCellData>,
+        decoded_field_type: &FieldType,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        if !decoded_field_type.is_url() {
+            return Ok(CellBytes::default());
+        }
+        self.display_data(cell_data, decoded_field_type, field_rev)
+    }
+
+    fn apply_changeset(
+        &self,
+        changeset: CellDataChangeset<String>,
+        _cell_rev: Option<CellRevision>,
+    ) -> Result<String, FlowyError> {
+        let content = changeset.try_into_inner()?;
+        let mut url = "".to_string();
+        if let Ok(Some(m)) = URL_REGEX.find(&content) {
+            url = auto_append_scheme(m.as_str());
+        }
+        URLCellData { url, content }.to_json()
+    }
+}
+
+fn auto_append_scheme(s: &str) -> String {
+    // Only support https scheme by now
+    match url::Url::parse(s) {
+        Ok(url) => {
+            if url.scheme() == "https" {
+                url.into()
+            } else {
+                format!("https://{}", s)
+            }
+        }
+        Err(_) => {
+            format!("https://{}", s)
+        }
+    }
+}
+
+lazy_static! {
+    static ref URL_REGEX: Regex = Regex::new(
+        "[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)"
+    )
+    .unwrap();
+}

+ 42 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option_entities.rs

@@ -0,0 +1,42 @@
+use crate::services::cell::{CellBytesParser, FromCellString};
+use bytes::Bytes;
+use flowy_derive::ProtoBuf;
+use flowy_error::{internal_error, FlowyResult};
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
+pub struct URLCellData {
+    #[pb(index = 1)]
+    pub url: String,
+
+    #[pb(index = 2)]
+    pub content: String,
+}
+
+impl URLCellData {
+    pub fn new(s: &str) -> Self {
+        Self {
+            url: "".to_string(),
+            content: s.to_string(),
+        }
+    }
+
+    pub(crate) fn to_json(&self) -> FlowyResult<String> {
+        serde_json::to_string(self).map_err(internal_error)
+    }
+}
+
+pub struct URLCellDataParser();
+impl CellBytesParser for URLCellDataParser {
+    type Object = URLCellData;
+
+    fn parse(&self, bytes: &Bytes) -> FlowyResult<Self::Object> {
+        URLCellData::try_from(bytes.as_ref()).map_err(internal_error)
+    }
+}
+
+impl FromCellString for URLCellData {
+    fn from_cell_str(s: &str) -> FlowyResult<Self> {
+        serde_json::from_str::<URLCellData>(s).map_err(internal_error)
+    }
+}

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

@@ -1,4 +1,3 @@
 mod cell_data_util;
 
-pub use crate::services::field::select_option::*;
 pub use cell_data_util::*;

+ 6 - 4
frontend/rust-lib/flowy-grid/src/services/filter/impls/checkbox_filter.rs

@@ -1,5 +1,5 @@
 use crate::entities::{CheckboxCondition, GridCheckboxFilter};
-use crate::services::cell::{AnyCellData, CellFilterOperation};
+use crate::services::cell::{AnyCellData, CellData, CellFilterOperation};
 use crate::services::field::{CheckboxCellData, CheckboxTypeOption};
 use flowy_error::FlowyResult;
 
@@ -18,7 +18,8 @@ impl CellFilterOperation<GridCheckboxFilter> for CheckboxTypeOption {
         if !any_cell_data.is_checkbox() {
             return Ok(true);
         }
-        let checkbox_cell_data: CheckboxCellData = any_cell_data.try_into()?;
+        let cell_data: CellData<CheckboxCellData> = any_cell_data.into();
+        let checkbox_cell_data = cell_data.try_into_inner()?;
         Ok(filter.is_visible(&checkbox_cell_data))
     }
 }
@@ -27,6 +28,7 @@ impl CellFilterOperation<GridCheckboxFilter> for CheckboxTypeOption {
 mod tests {
     use crate::entities::{CheckboxCondition, GridCheckboxFilter};
     use crate::services::field::CheckboxCellData;
+    use std::str::FromStr;
 
     #[test]
     fn checkbox_filter_is_check_test() {
@@ -34,7 +36,7 @@ mod tests {
             condition: CheckboxCondition::IsChecked,
         };
         for (value, visible) in [("true", true), ("yes", true), ("false", false), ("no", false)] {
-            let data = CheckboxCellData(value.to_owned());
+            let data = CheckboxCellData::from_str(value).unwrap();
             assert_eq!(checkbox_filter.is_visible(&data), visible);
         }
     }
@@ -45,7 +47,7 @@ mod tests {
             condition: CheckboxCondition::IsUnChecked,
         };
         for (value, visible) in [("false", true), ("no", true), ("true", false), ("yes", false)] {
-            let data = CheckboxCellData(value.to_owned());
+            let data = CheckboxCellData::from_str(value).unwrap();
             assert_eq!(checkbox_filter.is_visible(&data), visible);
         }
     }

+ 3 - 2
frontend/rust-lib/flowy-grid/src/services/filter/impls/date_filter.rs

@@ -1,5 +1,5 @@
 use crate::entities::{DateFilterCondition, GridDateFilter};
-use crate::services::cell::{AnyCellData, CellFilterOperation};
+use crate::services::cell::{AnyCellData, CellData, CellFilterOperation};
 use crate::services::field::{DateTimestamp, DateTypeOption};
 use flowy_error::FlowyResult;
 
@@ -34,7 +34,8 @@ impl CellFilterOperation<GridDateFilter> for DateTypeOption {
         if !any_cell_data.is_date() {
             return Ok(true);
         }
-        let timestamp: DateTimestamp = any_cell_data.into();
+        let cell_data: CellData<DateTimestamp> = any_cell_data.into();
+        let timestamp = cell_data.try_into_inner()?;
         Ok(filter.is_visible(timestamp))
     }
 }

+ 3 - 5
frontend/rust-lib/flowy-grid/src/services/filter/impls/number_filter.rs

@@ -47,9 +47,7 @@ impl CellFilterOperation<GridNumberFilter> for NumberTypeOption {
 #[cfg(test)]
 mod tests {
     use crate::entities::{GridNumberFilter, NumberFilterCondition};
-
     use crate::services::field::{NumberCellData, NumberFormat};
-    use std::str::FromStr;
     #[test]
     fn number_filter_equal_test() {
         let number_filter = GridNumberFilter {
@@ -58,7 +56,7 @@ mod tests {
         };
 
         for (num_str, visible) in [("123", true), ("1234", false), ("", false)] {
-            let data = NumberCellData::from_str(num_str).unwrap();
+            let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap();
             assert_eq!(number_filter.is_visible(&data), visible);
         }
 
@@ -75,7 +73,7 @@ mod tests {
             content: Some("12".to_owned()),
         };
         for (num_str, visible) in [("123", true), ("10", false), ("30", true), ("", false)] {
-            let data = NumberCellData::from_str(num_str).unwrap();
+            let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap();
             assert_eq!(number_filter.is_visible(&data), visible);
         }
     }
@@ -87,7 +85,7 @@ mod tests {
             content: Some("100".to_owned()),
         };
         for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", true)] {
-            let data = NumberCellData::from_str(num_str).unwrap();
+            let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap();
             assert_eq!(number_filter.is_visible(&data), visible);
         }
     }

+ 4 - 4
frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs

@@ -2,8 +2,8 @@
 
 use crate::entities::{GridSelectOptionFilter, SelectOptionCondition};
 use crate::services::cell::{AnyCellData, CellFilterOperation};
-use crate::services::field::select_option::{SelectOptionOperation, SelectedSelectOptions};
 use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
+use crate::services::field::{SelectOptionOperation, SelectedSelectOptions};
 use flowy_error::FlowyResult;
 
 impl GridSelectOptionFilter {
@@ -45,7 +45,7 @@ impl CellFilterOperation<GridSelectOptionFilter> for MultiSelectTypeOption {
             return Ok(true);
         }
 
-        let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data));
+        let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data.into()));
         Ok(filter.is_visible(&selected_options))
     }
 }
@@ -55,7 +55,7 @@ impl CellFilterOperation<GridSelectOptionFilter> for SingleSelectTypeOption {
         if !any_cell_data.is_single_select() {
             return Ok(true);
         }
-        let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data));
+        let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data.into()));
         Ok(filter.is_visible(&selected_options))
     }
 }
@@ -64,7 +64,7 @@ impl CellFilterOperation<GridSelectOptionFilter> for SingleSelectTypeOption {
 mod tests {
     #![allow(clippy::all)]
     use crate::entities::{GridSelectOptionFilter, SelectOptionCondition};
-    use crate::services::field::select_option::{SelectOption, SelectedSelectOptions};
+    use crate::services::field::selection_type_option::{SelectOption, SelectedSelectOptions};
 
     #[test]
     fn select_option_filter_is_test() {

+ 3 - 2
frontend/rust-lib/flowy-grid/src/services/filter/impls/text_filter.rs

@@ -1,5 +1,5 @@
 use crate::entities::{GridTextFilter, TextFilterCondition};
-use crate::services::cell::{AnyCellData, CellFilterOperation};
+use crate::services::cell::{AnyCellData, CellData, CellFilterOperation};
 use crate::services::field::{RichTextTypeOption, TextCellData};
 use flowy_error::FlowyResult;
 
@@ -30,7 +30,8 @@ impl CellFilterOperation<GridTextFilter> for RichTextTypeOption {
             return Ok(true);
         }
 
-        let text_cell_data: TextCellData = any_cell_data.try_into()?;
+        let cell_data: CellData<TextCellData> = any_cell_data.into();
+        let text_cell_data = cell_data.try_into_inner()?;
         Ok(filter.is_visible(text_cell_data))
     }
 }

+ 3 - 2
frontend/rust-lib/flowy-grid/src/services/filter/impls/url_filter.rs

@@ -1,5 +1,5 @@
 use crate::entities::GridTextFilter;
-use crate::services::cell::{AnyCellData, CellFilterOperation};
+use crate::services::cell::{AnyCellData, CellData, CellFilterOperation};
 use crate::services::field::{TextCellData, URLTypeOption};
 use flowy_error::FlowyResult;
 
@@ -9,7 +9,8 @@ impl CellFilterOperation<GridTextFilter> for URLTypeOption {
             return Ok(true);
         }
 
-        let text_cell_data: TextCellData = any_cell_data.try_into()?;
+        let cell_data: CellData<TextCellData> = any_cell_data.into();
+        let text_cell_data = cell_data.try_into_inner()?;
         Ok(filter.is_visible(&text_cell_data))
     }
 }

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

@@ -3,13 +3,12 @@ use crate::entities::CellIdentifier;
 use crate::entities::*;
 use crate::manager::{GridTaskSchedulerRwLock, GridUser};
 use crate::services::block_manager::GridBlockManager;
-use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data};
+use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data, CellBytes};
 use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder};
 use crate::services::filter::{GridFilterChangeset, GridFilterService};
 use crate::services::persistence::block_index::BlockIndexCache;
 use crate::services::row::{
-    make_grid_blocks, make_row_from_row_rev, make_row_rev_from_context, make_rows_from_row_revs,
-    CreateRowRevisionBuilder, CreateRowRevisionPayload, GridBlockSnapshot,
+    make_grid_blocks, make_row_from_row_rev, make_rows_from_row_revs, GridBlockSnapshot, RowRevisionBuilder,
 };
 use crate::services::setting::make_grid_setting;
 use bytes::Bytes;
@@ -274,8 +273,7 @@ impl GridRevisionEditor {
         let block_id = self.block_id().await?;
 
         // insert empty row below the row whose id is upper_row_id
-        let row_rev_ctx = CreateRowRevisionBuilder::new(&field_revs).build();
-        let row_rev = make_row_rev_from_context(&block_id, row_rev_ctx);
+        let row_rev = RowRevisionBuilder::new(&field_revs).build(&block_id);
         let row_order = Row::from(&row_rev);
 
         // insert the row
@@ -287,12 +285,11 @@ impl GridRevisionEditor {
         Ok(row_order)
     }
 
-    pub async fn insert_rows(&self, contexts: Vec<CreateRowRevisionPayload>) -> FlowyResult<Vec<Row>> {
+    pub async fn insert_rows(&self, row_revs: Vec<RowRevision>) -> FlowyResult<Vec<Row>> {
         let block_id = self.block_id().await?;
         let mut rows_by_block_id: HashMap<String, Vec<RowRevision>> = HashMap::new();
         let mut row_orders = vec![];
-        for ctx in contexts {
-            let row_rev = make_row_rev_from_context(&block_id, ctx);
+        for row_rev in row_revs {
             row_orders.push(Row::from(&row_rev));
             rows_by_block_id
                 .entry(block_id.clone())
@@ -307,10 +304,7 @@ impl GridRevisionEditor {
     }
 
     pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
-        let field_revs = self.get_field_revs(None).await?;
-        self.block_manager
-            .update_row(changeset, |row_rev| make_row_from_row_rev(&field_revs, row_rev))
-            .await
+        self.block_manager.update_row(changeset, make_row_from_row_rev).await
     }
 
     pub async fn get_rows(&self, block_id: &str) -> FlowyResult<RepeatedRow> {
@@ -322,26 +316,20 @@ impl GridRevisionEditor {
         debug_assert_eq!(grid_block_snapshot.len(), 1);
         if grid_block_snapshot.len() == 1 {
             let snapshot = grid_block_snapshot.pop().unwrap();
-            let field_revs = self.get_field_revs(None).await?;
-            let rows = make_rows_from_row_revs(&field_revs, &snapshot.row_revs);
+            let rows = make_rows_from_row_revs(&snapshot.row_revs);
             Ok(rows.into())
         } else {
             Ok(vec![].into())
         }
     }
 
-    pub async fn get_row(&self, row_id: &str) -> FlowyResult<Option<Row>> {
+    pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult<Option<Arc<RowRevision>>> {
         match self.block_manager.get_row_rev(row_id).await? {
             None => Ok(None),
-            Some(row_rev) => {
-                let field_revs = self.get_field_revs(None).await?;
-                let row_revs = vec![row_rev];
-                let mut rows = make_rows_from_row_revs(&field_revs, &row_revs);
-                debug_assert!(rows.len() == 1);
-                Ok(rows.pop())
-            }
+            Some(row_rev) => Ok(Some(row_rev)),
         }
     }
+
     pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> {
         let _ = self.block_manager.delete_row(row_id).await?;
         Ok(())
@@ -352,12 +340,16 @@ impl GridRevisionEditor {
     }
 
     pub async fn get_cell(&self, params: &CellIdentifier) -> Option<Cell> {
+        let cell_bytes = self.get_cell_bytes(params).await?;
+        Some(Cell::new(&params.field_id, cell_bytes.to_vec()))
+    }
+
+    pub async fn get_cell_bytes(&self, params: &CellIdentifier) -> Option<CellBytes> {
         let field_rev = self.get_field_rev(&params.field_id).await?;
         let row_rev = self.block_manager.get_row_rev(&params.row_id).await.ok()??;
 
         let cell_rev = row_rev.cells.get(&params.field_id)?.clone();
-        let data = decode_any_cell_data(cell_rev.data, &field_rev).data;
-        Some(Cell::new(&params.field_id, data))
+        Some(decode_any_cell_data(cell_rev.data, &field_rev))
     }
 
     pub async fn get_cell_rev(&self, row_id: &str, field_id: &str) -> FlowyResult<Option<CellRevision>> {
@@ -395,7 +387,6 @@ impl GridRevisionEditor {
                 let cell_rev = self.get_cell_rev(&row_id, &field_id).await?;
                 // Update the changeset.data property with the return value.
                 content = Some(apply_cell_data_changeset(content.unwrap(), cell_rev, field_rev)?);
-                let field_revs = self.get_field_revs(None).await?;
                 let cell_changeset = CellChangeset {
                     grid_id,
                     row_id,
@@ -404,7 +395,7 @@ impl GridRevisionEditor {
                 };
                 let _ = self
                     .block_manager
-                    .update_cell(cell_changeset, |row_rev| make_row_from_row_rev(&field_revs, row_rev))
+                    .update_cell(cell_changeset, make_row_from_row_rev)
                     .await?;
                 Ok(())
             }
@@ -561,7 +552,7 @@ impl GridRevisionEditor {
         drop(grid_pad);
 
         Ok(BuildGridContext {
-            field_revs: duplicated_fields,
+            field_revs: duplicated_fields.into_iter().map(Arc::new).collect(),
             blocks: duplicated_blocks,
             blocks_meta_data,
         })

+ 17 - 21
frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs

@@ -1,22 +1,22 @@
 use crate::services::cell::apply_cell_data_changeset;
-use crate::services::field::select_option::SelectOptionCellChangeset;
+use crate::services::field::SelectOptionCellChangeset;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::revision::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT};
 use indexmap::IndexMap;
 use std::collections::HashMap;
 use std::sync::Arc;
 
-pub struct CreateRowRevisionBuilder<'a> {
-    field_rev_map: HashMap<&'a String, &'a Arc<FieldRevision>>,
+pub struct RowRevisionBuilder<'a> {
+    field_rev_map: HashMap<&'a String, Arc<FieldRevision>>,
     payload: CreateRowRevisionPayload,
 }
 
-impl<'a> CreateRowRevisionBuilder<'a> {
+impl<'a> RowRevisionBuilder<'a> {
     pub fn new(fields: &'a [Arc<FieldRevision>]) -> Self {
         let field_rev_map = fields
             .iter()
-            .map(|field| (&field.id, field))
-            .collect::<HashMap<&String, &Arc<FieldRevision>>>();
+            .map(|field| (&field.id, field.clone()))
+            .collect::<HashMap<&String, Arc<FieldRevision>>>();
 
         let payload = CreateRowRevisionPayload {
             row_id: gen_row_id(),
@@ -28,10 +28,10 @@ impl<'a> CreateRowRevisionBuilder<'a> {
         Self { field_rev_map, payload }
     }
 
-    pub fn add_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> {
+    pub fn insert_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> {
         match self.field_rev_map.get(&field_id.to_owned()) {
             None => {
-                let msg = format!("Invalid field_id: {}", field_id);
+                let msg = format!("Can't find the field with id: {}", field_id);
                 Err(FlowyError::internal().context(msg))
             }
             Some(field_rev) => {
@@ -43,7 +43,7 @@ impl<'a> CreateRowRevisionBuilder<'a> {
         }
     }
 
-    pub fn add_select_option_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> {
+    pub fn insert_select_option_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> {
         match self.field_rev_map.get(&field_id.to_owned()) {
             None => {
                 let msg = format!("Invalid field_id: {}", field_id);
@@ -71,18 +71,14 @@ impl<'a> CreateRowRevisionBuilder<'a> {
         self
     }
 
-    pub fn build(self) -> CreateRowRevisionPayload {
-        self.payload
-    }
-}
-
-pub fn make_row_rev_from_context(block_id: &str, payload: CreateRowRevisionPayload) -> RowRevision {
-    RowRevision {
-        id: payload.row_id,
-        block_id: block_id.to_owned(),
-        cells: payload.cell_by_field_id,
-        height: payload.height,
-        visibility: payload.visibility,
+    pub fn build(self, block_id: &str) -> RowRevision {
+        RowRevision {
+            id: self.payload.row_id,
+            block_id: block_id.to_owned(),
+            cells: self.payload.cell_by_field_id,
+            height: self.payload.height,
+            visibility: self.payload.visibility,
+        }
     }
 }
 

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

@@ -1,6 +1,6 @@
 use crate::entities::{GridBlock, RepeatedGridBlock, Row};
 use flowy_error::FlowyResult;
-use flowy_grid_data_model::revision::{FieldRevision, RowRevision};
+use flowy_grid_data_model::revision::RowRevision;
 use std::collections::HashMap;
 use std::sync::Arc;
 
@@ -39,29 +39,15 @@ pub(crate) fn make_row_orders_from_row_revs(row_revs: &[Arc<RowRevision>]) -> Ve
     row_revs.iter().map(Row::from).collect::<Vec<_>>()
 }
 
-pub(crate) fn make_row_from_row_rev(fields: &[Arc<FieldRevision>], row_rev: Arc<RowRevision>) -> Option<Row> {
-    make_rows_from_row_revs(fields, &[row_rev]).pop()
+pub(crate) fn make_row_from_row_rev(row_rev: Arc<RowRevision>) -> Option<Row> {
+    make_rows_from_row_revs(&[row_rev]).pop()
 }
 
-pub(crate) fn make_rows_from_row_revs(_fields: &[Arc<FieldRevision>], row_revs: &[Arc<RowRevision>]) -> Vec<Row> {
-    // let field_rev_map = fields
-    //     .iter()
-    //     .map(|field_rev| (&field_rev.id, field_rev))
-    //     .collect::<HashMap<&String, &FieldRevision>>();
-
-    let make_row = |row_rev: &Arc<RowRevision>| {
-        // let cell_by_field_id = row_rev
-        //     .cells
-        //     .clone()
-        //     .into_iter()
-        //     .flat_map(|(field_id, cell_rev)| make_cell_by_field_id(&field_rev_map, field_id, cell_rev))
-        //     .collect::<HashMap<String, Cell>>();
-
-        Row {
-            block_id: row_rev.block_id.clone(),
-            id: row_rev.id.clone(),
-            height: row_rev.height,
-        }
+pub(crate) fn make_rows_from_row_revs(row_revs: &[Arc<RowRevision>]) -> Vec<Row> {
+    let make_row = |row_rev: &Arc<RowRevision>| Row {
+        block_id: row_rev.block_id.clone(),
+        id: row_rev.id.clone(),
+        height: row_rev.height,
     };
 
     row_revs.iter().map(make_row).collect::<Vec<_>>()

+ 8 - 8
frontend/rust-lib/flowy-grid/src/util.rs

@@ -4,29 +4,29 @@ use flowy_grid_data_model::revision::BuildGridContext;
 use flowy_sync::client_grid::GridBuilder;
 
 pub fn make_default_grid() -> BuildGridContext {
+    let mut grid_builder = GridBuilder::new();
     // text
     let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
         .name("Name")
         .visibility(true)
         .primary(true)
         .build();
+    grid_builder.add_field(text_field);
 
     // single select
     let single_select = SingleSelectTypeOptionBuilder::default();
     let single_select_field = FieldBuilder::new(single_select).name("Type").visibility(true).build();
+    grid_builder.add_field(single_select_field);
 
     // checkbox
     let checkbox_field = FieldBuilder::from_field_type(&FieldType::Checkbox)
         .name("Done")
         .visibility(true)
         .build();
+    grid_builder.add_field(checkbox_field);
 
-    GridBuilder::default()
-        .add_field(text_field)
-        .add_field(single_select_field)
-        .add_field(checkbox_field)
-        .add_empty_row()
-        .add_empty_row()
-        .add_empty_row()
-        .build()
+    grid_builder.add_empty_row();
+    grid_builder.add_empty_row();
+    grid_builder.add_empty_row();
+    grid_builder.build()
 }

+ 74 - 89
frontend/rust-lib/flowy-grid/tests/grid/block_test/row_test.rs

@@ -1,26 +1,21 @@
-use crate::grid::block_test::script::GridRowTest;
 use crate::grid::block_test::script::RowScript::*;
-use crate::grid::block_test::util::GridRowTestBuilder;
-use chrono::NaiveDateTime;
+use crate::grid::block_test::script::{CreateRowScriptBuilder, GridRowTest};
+use crate::grid::grid_editor::{COMPLETED, FACEBOOK, GOOGLE, PAUSED, TWITTER};
 use flowy_grid::entities::FieldType;
-use flowy_grid::services::cell::decode_any_cell_data;
-use flowy_grid::services::field::select_option::SELECTION_IDS_SEPARATOR;
-use flowy_grid::services::field::{DateCellData, MultiSelectTypeOption, SingleSelectTypeOption};
-
-use crate::grid::field_test::util::make_date_cell_string;
+use flowy_grid::services::field::{NO, SELECTION_IDS_SEPARATOR};
 use flowy_grid_data_model::revision::RowMetaChangeset;
 
 #[tokio::test]
 async fn grid_create_row_count_test() {
     let mut test = GridRowTest::new().await;
     let scripts = vec![
-        AssertRowCount(3),
+        AssertRowCount(5),
         CreateEmptyRow,
         CreateEmptyRow,
         CreateRow {
-            payload: GridRowTestBuilder::new(&test).build(),
+            row_rev: test.row_builder().build(),
         },
-        AssertRowCount(6),
+        AssertRowCount(8),
     ];
     test.run_scripts(scripts).await;
 }
@@ -28,42 +23,42 @@ async fn grid_create_row_count_test() {
 #[tokio::test]
 async fn grid_update_row() {
     let mut test = GridRowTest::new().await;
-    let payload = GridRowTestBuilder::new(&test).build();
+    let row_rev = test.row_builder().build();
     let changeset = RowMetaChangeset {
-        row_id: payload.row_id.clone(),
+        row_id: row_rev.id.clone(),
         height: None,
         visibility: None,
         cell_by_field_id: Default::default(),
     };
 
-    let scripts = vec![AssertRowCount(3), CreateRow { payload }, UpdateRow { changeset }];
+    let scripts = vec![AssertRowCount(5), CreateRow { row_rev }, UpdateRow { changeset }];
     test.run_scripts(scripts).await;
 
     let expected_row = test.last_row().unwrap();
-    let scripts = vec![AssertRow { expected_row }, AssertRowCount(4)];
+    let scripts = vec![AssertRow { expected_row }, AssertRowCount(6)];
     test.run_scripts(scripts).await;
 }
 
 #[tokio::test]
 async fn grid_delete_row() {
     let mut test = GridRowTest::new().await;
-    let payload1 = GridRowTestBuilder::new(&test).build();
-    let payload2 = GridRowTestBuilder::new(&test).build();
-    let row_ids = vec![payload1.row_id.clone(), payload2.row_id.clone()];
+    let row_1 = test.row_builder().build();
+    let row_2 = test.row_builder().build();
+    let row_ids = vec![row_1.id.clone(), row_2.id.clone()];
     let scripts = vec![
-        AssertRowCount(3),
-        CreateRow { payload: payload1 },
-        CreateRow { payload: payload2 },
+        AssertRowCount(5),
+        CreateRow { row_rev: row_1 },
+        CreateRow { row_rev: row_2 },
         AssertBlockCount(1),
         AssertBlock {
             block_index: 0,
-            row_count: 5,
+            row_count: 7,
             start_row_index: 0,
         },
         DeleteRows { row_ids },
         AssertBlock {
             block_index: 0,
-            row_count: 3,
+            row_count: 5,
             start_row_index: 0,
         },
     ];
@@ -73,78 +68,68 @@ async fn grid_delete_row() {
 #[tokio::test]
 async fn grid_row_add_cells_test() {
     let mut test = GridRowTest::new().await;
-    let mut builder = test.builder();
-    for field in test.field_revs() {
-        let field_type: FieldType = field.field_type_rev.into();
-        match field_type {
-            FieldType::RichText => {
-                builder.add_cell(&field.id, "hello world".to_owned()).unwrap();
-            }
-            FieldType::Number => {
-                builder.add_cell(&field.id, "18,443".to_owned()).unwrap();
-            }
-            FieldType::DateTime => {
-                builder
-                    .add_cell(&field.id, make_date_cell_string("1647251762"))
-                    .unwrap();
-            }
-            FieldType::SingleSelect => {
-                let type_option = SingleSelectTypeOption::from(field);
-                let option = type_option.options.first().unwrap();
-                builder.add_select_option_cell(&field.id, option.id.clone()).unwrap();
-            }
-            FieldType::MultiSelect => {
-                let type_option = MultiSelectTypeOption::from(field);
-                let ops_ids = type_option
-                    .options
-                    .iter()
-                    .map(|option| option.id.clone())
-                    .collect::<Vec<_>>()
-                    .join(SELECTION_IDS_SEPARATOR);
-                builder.add_select_option_cell(&field.id, ops_ids).unwrap();
-            }
-            FieldType::Checkbox => {
-                builder.add_cell(&field.id, "false".to_string()).unwrap();
-            }
-            FieldType::URL => {
-                builder.add_cell(&field.id, "1".to_string()).unwrap();
-            }
-        }
-    }
-    let context = builder.build();
-    let scripts = vec![CreateRow { payload: context }];
+    let mut builder = CreateRowScriptBuilder::new(&test);
+    builder.insert(FieldType::RichText, "hello world", "hello world");
+    builder.insert(FieldType::DateTime, "1647251762", "2022/03/14");
+    builder.insert(FieldType::Number, "18,443", "$18,443.00");
+    builder.insert(FieldType::Checkbox, "false", NO);
+    builder.insert(FieldType::URL, "https://appflowy.io", "https://appflowy.io");
+    builder.insert_single_select_cell(|mut options| options.remove(0), COMPLETED);
+    builder.insert_multi_select_cell(
+        |options| options,
+        &vec![GOOGLE, FACEBOOK, TWITTER].join(SELECTION_IDS_SEPARATOR),
+    );
+    let scripts = builder.build();
     test.run_scripts(scripts).await;
 }
 
 #[tokio::test]
-async fn grid_row_add_date_cell_test() {
+async fn grid_row_insert_number_test() {
+    let mut test = GridRowTest::new().await;
+    for (val, expected) in &[("1647251762", "2022/03/14"), ("2022/03/14", ""), ("", "")] {
+        let mut builder = CreateRowScriptBuilder::new(&test);
+        builder.insert(FieldType::DateTime, val, expected);
+        let scripts = builder.build();
+        test.run_scripts(scripts).await;
+    }
+}
+
+#[tokio::test]
+async fn grid_row_insert_date_test() {
     let mut test = GridRowTest::new().await;
-    let mut builder = test.builder();
-    let mut date_field = None;
-    let timestamp = 1647390674;
-    for field in test.field_revs() {
-        let field_type: FieldType = field.field_type_rev.into();
-        if field_type == FieldType::DateTime {
-            date_field = Some(field.clone());
-            NaiveDateTime::from_timestamp(123, 0);
-            // The data should not be empty
-            assert!(builder.add_cell(&field.id, "".to_string()).is_err());
-            assert!(builder.add_cell(&field.id, make_date_cell_string("123")).is_ok());
-            assert!(builder
-                .add_cell(&field.id, make_date_cell_string(&timestamp.to_string()))
-                .is_ok());
-        }
+    for (val, expected) in &[
+        ("18,443", "$18,443.00"),
+        ("0", "$0.00"),
+        ("100000", "$100,000.00"),
+        ("$100,000.00", "$100,000.00"),
+        ("", ""),
+    ] {
+        let mut builder = CreateRowScriptBuilder::new(&test);
+        builder.insert(FieldType::Number, val, expected);
+        let scripts = builder.build();
+        test.run_scripts(scripts).await;
     }
-    let context = builder.build();
-    let date_field = date_field.unwrap();
-    let cell_rev = context.cell_by_field_id.get(&date_field.id).unwrap();
-    assert_eq!(
-        decode_any_cell_data(cell_rev, &date_field)
-            .parse::<DateCellData>()
-            .unwrap()
-            .date,
-        "2022/03/16",
+}
+#[tokio::test]
+async fn grid_row_insert_single_select_test() {
+    let mut test = GridRowTest::new().await;
+    let mut builder = CreateRowScriptBuilder::new(&test);
+    builder.insert_single_select_cell(|mut options| options.pop().unwrap(), PAUSED);
+    let scripts = builder.build();
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn grid_row_insert_multi_select_test() {
+    let mut test = GridRowTest::new().await;
+    let mut builder = CreateRowScriptBuilder::new(&test);
+    builder.insert_multi_select_cell(
+        |mut options| {
+            options.remove(0);
+            options
+        },
+        &vec![FACEBOOK, TWITTER].join(SELECTION_IDS_SEPARATOR),
     );
-    let scripts = vec![CreateRow { payload: context }];
+    let scripts = builder.build();
     test.run_scripts(scripts).await;
 }

+ 235 - 12
frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs

@@ -1,15 +1,20 @@
+use crate::grid::block_test::script::RowScript::{AssertCell, CreateRow};
+use crate::grid::block_test::util::GridRowTestBuilder;
 use crate::grid::grid_editor::GridEditorTest;
-use flowy_grid::entities::Row;
-use flowy_grid::services::row::{CreateRowRevisionBuilder, CreateRowRevisionPayload};
+
+use flowy_grid::entities::{CellIdentifier, FieldType, Row};
+use flowy_grid::services::field::*;
 use flowy_grid_data_model::revision::{
-    FieldRevision, GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowMetaChangeset, RowRevision,
+    GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowMetaChangeset, RowRevision,
 };
+use std::collections::HashMap;
 use std::sync::Arc;
+use strum::IntoEnumIterator;
 
 pub enum RowScript {
     CreateEmptyRow,
     CreateRow {
-        payload: CreateRowRevisionPayload,
+        row_rev: RowRevision,
     },
     UpdateRow {
         changeset: RowMetaChangeset,
@@ -20,6 +25,12 @@ pub enum RowScript {
     DeleteRows {
         row_ids: Vec<String>,
     },
+    AssertCell {
+        row_id: String,
+        field_id: String,
+        field_type: FieldType,
+        expected: String,
+    },
     AssertRowCount(usize),
     CreateBlock {
         block: GridBlockMetaRevision,
@@ -49,10 +60,6 @@ impl GridRowTest {
         Self { inner: editor_test }
     }
 
-    pub fn field_revs(&self) -> &Vec<Arc<FieldRevision>> {
-        &self.field_revs
-    }
-
     pub fn last_row(&self) -> Option<RowRevision> {
         self.row_revs.last().map(|a| a.clone().as_ref().clone())
     }
@@ -63,8 +70,8 @@ impl GridRowTest {
         }
     }
 
-    pub fn builder(&self) -> CreateRowRevisionBuilder {
-        CreateRowRevisionBuilder::new(&self.field_revs)
+    pub fn row_builder(&self) -> GridRowTestBuilder {
+        GridRowTestBuilder::new(self.block_id(), &self.field_revs)
     }
 
     pub async fn run_script(&mut self, script: RowScript) {
@@ -76,8 +83,8 @@ impl GridRowTest {
                 self.row_revs = self.get_row_revs().await;
                 self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap();
             }
-            RowScript::CreateRow { payload: context } => {
-                let row_orders = self.editor.insert_rows(vec![context]).await.unwrap();
+            RowScript::CreateRow { row_rev } => {
+                let row_orders = self.editor.insert_rows(vec![row_rev]).await.unwrap();
                 for row_order in row_orders {
                     self.row_order_by_row_id
                         .insert(row_order.row_id().to_owned(), row_order);
@@ -96,6 +103,19 @@ impl GridRowTest {
                 self.row_revs = self.get_row_revs().await;
                 self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap();
             }
+            RowScript::AssertCell {
+                row_id,
+                field_id,
+                field_type,
+                expected,
+            } => {
+                let id = CellIdentifier {
+                    grid_id: self.grid_id.clone(),
+                    field_id,
+                    row_id,
+                };
+                self.compare_cell_content(id, field_type, expected).await;
+            }
             RowScript::AssertRow { expected_row } => {
                 let row = &*self
                     .row_revs
@@ -133,6 +153,99 @@ impl GridRowTest {
             }
         }
     }
+
+    async fn compare_cell_content(&self, cell_id: CellIdentifier, field_type: FieldType, expected: String) {
+        match field_type {
+            FieldType::RichText => {
+                let cell_data = self
+                    .editor
+                    .get_cell_bytes(&cell_id)
+                    .await
+                    .unwrap()
+                    .with_parser(TextCellDataParser())
+                    .unwrap();
+
+                assert_eq!(cell_data.as_ref(), &expected);
+            }
+            FieldType::Number => {
+                let field_rev = self.editor.get_field_rev(&cell_id.field_id).await.unwrap();
+                let number_type_option = field_rev
+                    .get_type_option_entry::<NumberTypeOption>(FieldType::Number.into())
+                    .unwrap();
+                let cell_data = self
+                    .editor
+                    .get_cell_bytes(&cell_id)
+                    .await
+                    .unwrap()
+                    .with_parser(NumberCellDataParser(number_type_option.format))
+                    .unwrap();
+                assert_eq!(cell_data.to_string(), expected);
+            }
+            FieldType::DateTime => {
+                let cell_data = self
+                    .editor
+                    .get_cell_bytes(&cell_id)
+                    .await
+                    .unwrap()
+                    .with_parser(DateCellDataParser())
+                    .unwrap();
+
+                assert_eq!(cell_data.date, expected);
+            }
+            FieldType::SingleSelect => {
+                let cell_data = self
+                    .editor
+                    .get_cell_bytes(&cell_id)
+                    .await
+                    .unwrap()
+                    .with_parser(SelectOptionCellDataParser())
+                    .unwrap();
+                let select_option = cell_data.select_options.first().unwrap();
+                assert_eq!(select_option.name, expected);
+            }
+            FieldType::MultiSelect => {
+                let cell_data = self
+                    .editor
+                    .get_cell_bytes(&cell_id)
+                    .await
+                    .unwrap()
+                    .with_parser(SelectOptionCellDataParser())
+                    .unwrap();
+
+                let s = cell_data
+                    .select_options
+                    .into_iter()
+                    .map(|option| option.name)
+                    .collect::<Vec<String>>()
+                    .join(SELECTION_IDS_SEPARATOR);
+
+                assert_eq!(s, expected);
+            }
+
+            FieldType::Checkbox => {
+                let cell_data = self
+                    .editor
+                    .get_cell_bytes(&cell_id)
+                    .await
+                    .unwrap()
+                    .with_parser(CheckboxCellDataParser())
+                    .unwrap();
+                assert_eq!(cell_data.to_string(), expected);
+            }
+            FieldType::URL => {
+                let cell_data = self
+                    .editor
+                    .get_cell_bytes(&cell_id)
+                    .await
+                    .unwrap()
+                    .with_parser(URLCellDataParser())
+                    .unwrap();
+
+                assert_eq!(cell_data.content, expected);
+                // assert_eq!(cell_data.url, expected);
+            }
+        }
+    }
 }
 
 impl std::ops::Deref for GridRowTest {
@@ -148,3 +261,113 @@ impl std::ops::DerefMut for GridRowTest {
         &mut self.inner
     }
 }
+
+pub struct CreateRowScriptBuilder<'a> {
+    builder: GridRowTestBuilder<'a>,
+    data_by_field_type: HashMap<FieldType, CellTestData>,
+    output_by_field_type: HashMap<FieldType, CellTestOutput>,
+}
+
+impl<'a> CreateRowScriptBuilder<'a> {
+    pub fn new(test: &'a GridRowTest) -> Self {
+        Self {
+            builder: test.row_builder(),
+            data_by_field_type: HashMap::new(),
+            output_by_field_type: HashMap::new(),
+        }
+    }
+
+    pub fn insert(&mut self, field_type: FieldType, input: &str, expected: &str) {
+        self.data_by_field_type.insert(
+            field_type,
+            CellTestData {
+                input: input.to_string(),
+                expected: expected.to_owned(),
+            },
+        );
+    }
+
+    pub fn insert_single_select_cell<F>(&mut self, f: F, expected: &str)
+    where
+        F: Fn(Vec<SelectOption>) -> SelectOption,
+    {
+        let field_id = self.builder.insert_single_select_cell(f);
+        self.output_by_field_type.insert(
+            FieldType::SingleSelect,
+            CellTestOutput {
+                field_id,
+                expected: expected.to_owned(),
+            },
+        );
+    }
+
+    pub fn insert_multi_select_cell<F>(&mut self, f: F, expected: &str)
+    where
+        F: Fn(Vec<SelectOption>) -> Vec<SelectOption>,
+    {
+        let field_id = self.builder.insert_multi_select_cell(f);
+        self.output_by_field_type.insert(
+            FieldType::MultiSelect,
+            CellTestOutput {
+                field_id,
+                expected: expected.to_owned(),
+            },
+        );
+    }
+
+    pub fn build(mut self) -> Vec<RowScript> {
+        let mut scripts = vec![];
+        let output_by_field_type = &mut self.output_by_field_type;
+
+        for field_type in FieldType::iter() {
+            let field_type: FieldType = field_type;
+            if let Some(data) = self.data_by_field_type.get(&field_type) {
+                let field_id = match field_type {
+                    FieldType::RichText => self.builder.insert_text_cell(&data.input),
+                    FieldType::Number => self.builder.insert_number_cell(&data.input),
+                    FieldType::DateTime => self.builder.insert_date_cell(&data.input),
+                    FieldType::Checkbox => self.builder.insert_checkbox_cell(&data.input),
+                    FieldType::URL => self.builder.insert_url_cell(&data.input),
+                    _ => "".to_owned(),
+                };
+
+                if !field_id.is_empty() {
+                    output_by_field_type.insert(
+                        field_type,
+                        CellTestOutput {
+                            field_id,
+                            expected: data.expected.clone(),
+                        },
+                    );
+                }
+            }
+        }
+
+        let row_rev = self.builder.build();
+        let row_id = row_rev.id.clone();
+        scripts.push(CreateRow { row_rev });
+
+        for field_type in FieldType::iter() {
+            if let Some(data) = output_by_field_type.get(&field_type) {
+                let script = AssertCell {
+                    row_id: row_id.clone(),
+                    field_id: data.field_id.clone(),
+                    field_type,
+                    expected: data.expected.clone(),
+                };
+                scripts.push(script);
+            }
+        }
+        scripts
+    }
+}
+
+pub struct CellTestData {
+    pub input: String,
+    pub expected: String,
+}
+
+struct CellTestOutput {
+    field_id: String,
+    expected: String,
+}

+ 98 - 41
frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs

@@ -1,66 +1,109 @@
-use crate::grid::block_test::script::GridRowTest;
-
 use flowy_grid::entities::FieldType;
-use flowy_grid::services::field::DateCellChangeset;
-use flowy_grid::services::row::{CreateRowRevisionBuilder, CreateRowRevisionPayload};
-use flowy_grid_data_model::revision::FieldRevision;
+use std::sync::Arc;
+
+use flowy_grid::services::field::{
+    DateCellChangeset, MultiSelectTypeOption, SelectOption, SingleSelectTypeOption, SELECTION_IDS_SEPARATOR,
+};
+use flowy_grid::services::row::RowRevisionBuilder;
+use flowy_grid_data_model::revision::{FieldRevision, RowRevision};
+
 use strum::EnumCount;
 
 pub struct GridRowTestBuilder<'a> {
-    test: &'a GridRowTest,
-    inner_builder: CreateRowRevisionBuilder<'a>,
+    block_id: String,
+    field_revs: &'a [Arc<FieldRevision>],
+    inner_builder: RowRevisionBuilder<'a>,
 }
 
 impl<'a> GridRowTestBuilder<'a> {
-    pub fn new(test: &'a GridRowTest) -> Self {
-        assert_eq!(test.field_revs().len(), FieldType::COUNT);
-
-        let inner_builder = CreateRowRevisionBuilder::new(test.field_revs());
-        Self { test, inner_builder }
+    pub fn new(block_id: &str, field_revs: &'a [Arc<FieldRevision>]) -> Self {
+        assert_eq!(field_revs.len(), FieldType::COUNT);
+        let inner_builder = RowRevisionBuilder::new(field_revs);
+        Self {
+            block_id: block_id.to_owned(),
+            field_revs,
+            inner_builder,
+        }
     }
-    #[allow(dead_code)]
-    pub fn update_text_cell(mut self, data: String) -> Self {
-        let text_field = self.field_rev_with_type(&FieldType::DateTime);
-        self.inner_builder.add_cell(&text_field.id, data).unwrap();
-        self
+
+    pub fn insert_text_cell(&mut self, data: &str) -> String {
+        let text_field = self.field_rev_with_type(&FieldType::RichText);
+        self.inner_builder
+            .insert_cell(&text_field.id, data.to_string())
+            .unwrap();
+
+        text_field.id.clone()
     }
 
-    #[allow(dead_code)]
-    pub fn update_number_cell(mut self, data: String) -> Self {
-        let number_field = self.field_rev_with_type(&FieldType::DateTime);
-        self.inner_builder.add_cell(&number_field.id, data).unwrap();
-        self
+    pub fn insert_number_cell(&mut self, data: &str) -> String {
+        let number_field = self.field_rev_with_type(&FieldType::Number);
+        self.inner_builder
+            .insert_cell(&number_field.id, data.to_string())
+            .unwrap();
+        number_field.id.clone()
     }
 
-    #[allow(dead_code)]
-    pub fn update_date_cell(mut self, value: i64) -> Self {
+    pub fn insert_date_cell(&mut self, data: &str) -> String {
         let value = serde_json::to_string(&DateCellChangeset {
-            date: Some(value.to_string()),
+            date: Some(data.to_string()),
             time: None,
         })
         .unwrap();
         let date_field = self.field_rev_with_type(&FieldType::DateTime);
-        self.inner_builder.add_cell(&date_field.id, value).unwrap();
-        self
+        self.inner_builder.insert_cell(&date_field.id, value).unwrap();
+        date_field.id.clone()
     }
 
-    #[allow(dead_code)]
-    pub fn update_checkbox_cell(mut self, data: bool) -> Self {
-        let number_field = self.field_rev_with_type(&FieldType::Checkbox);
-        self.inner_builder.add_cell(&number_field.id, data.to_string()).unwrap();
-        self
+    pub fn insert_checkbox_cell(&mut self, data: &str) -> String {
+        let checkbox_field = self.field_rev_with_type(&FieldType::Checkbox);
+        self.inner_builder
+            .insert_cell(&checkbox_field.id, data.to_string())
+            .unwrap();
+
+        checkbox_field.id.clone()
     }
 
-    #[allow(dead_code)]
-    pub fn update_url_cell(mut self, data: String) -> Self {
-        let number_field = self.field_rev_with_type(&FieldType::Checkbox);
-        self.inner_builder.add_cell(&number_field.id, data).unwrap();
-        self
+    pub fn insert_url_cell(&mut self, data: &str) -> String {
+        let url_field = self.field_rev_with_type(&FieldType::URL);
+        self.inner_builder.insert_cell(&url_field.id, data.to_string()).unwrap();
+        url_field.id.clone()
+    }
+
+    pub fn insert_single_select_cell<F>(&mut self, f: F) -> String
+    where
+        F: Fn(Vec<SelectOption>) -> SelectOption,
+    {
+        let single_select_field = self.field_rev_with_type(&FieldType::SingleSelect);
+        let type_option = SingleSelectTypeOption::from(&single_select_field);
+        let option = f(type_option.options);
+        self.inner_builder
+            .insert_select_option_cell(&single_select_field.id, option.id)
+            .unwrap();
+
+        single_select_field.id.clone()
+    }
+
+    pub fn insert_multi_select_cell<F>(&mut self, f: F) -> String
+    where
+        F: Fn(Vec<SelectOption>) -> Vec<SelectOption>,
+    {
+        let multi_select_field = self.field_rev_with_type(&FieldType::MultiSelect);
+        let type_option = MultiSelectTypeOption::from(&multi_select_field);
+        let options = f(type_option.options);
+        let ops_ids = options
+            .iter()
+            .map(|option| option.id.clone())
+            .collect::<Vec<_>>()
+            .join(SELECTION_IDS_SEPARATOR);
+        self.inner_builder
+            .insert_select_option_cell(&multi_select_field.id, ops_ids)
+            .unwrap();
+
+        multi_select_field.id.clone()
     }
 
     pub fn field_rev_with_type(&self, field_type: &FieldType) -> FieldRevision {
-        self.test
-            .field_revs()
+        self.field_revs
             .iter()
             .find(|field_rev| {
                 let t_field_type: FieldType = field_rev.field_type_rev.into();
@@ -71,7 +114,21 @@ impl<'a> GridRowTestBuilder<'a> {
             .clone()
     }
 
-    pub fn build(self) -> CreateRowRevisionPayload {
-        self.inner_builder.build()
+    pub fn build(self) -> RowRevision {
+        self.inner_builder.build(&self.block_id)
+    }
+}
+
+impl<'a> std::ops::Deref for GridRowTestBuilder<'a> {
+    type Target = RowRevisionBuilder<'a>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.inner_builder
+    }
+}
+
+impl<'a> std::ops::DerefMut for GridRowTestBuilder<'a> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.inner_builder
     }
 }

+ 1 - 1
frontend/rust-lib/flowy-grid/tests/grid/cell_test/test.rs

@@ -2,7 +2,7 @@ use crate::grid::cell_test::script::CellScript::*;
 use crate::grid::cell_test::script::GridCellTest;
 use crate::grid::field_test::util::make_date_cell_string;
 use flowy_grid::entities::{CellChangeset, FieldType};
-use flowy_grid::services::field::select_option::SelectOptionCellChangeset;
+use flowy_grid::services::field::selection_type_option::SelectOptionCellChangeset;
 use flowy_grid::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
 
 #[tokio::test]

+ 1 - 1
frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs

@@ -1,7 +1,7 @@
 use crate::grid::field_test::script::FieldScript::*;
 use crate::grid::field_test::script::GridFieldTest;
 use crate::grid::field_test::util::*;
-use flowy_grid::services::field::select_option::SelectOption;
+use flowy_grid::services::field::selection_type_option::SelectOption;
 use flowy_grid::services::field::SingleSelectTypeOption;
 use flowy_grid_data_model::revision::TypeOptionDataEntry;
 use flowy_sync::entities::grid::FieldChangesetParams;

+ 1 - 1
frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs

@@ -1,5 +1,5 @@
 use flowy_grid::entities::*;
-use flowy_grid::services::field::select_option::SelectOption;
+use flowy_grid::services::field::selection_type_option::SelectOption;
 use flowy_grid::services::field::*;
 use flowy_grid_data_model::revision::*;
 

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

@@ -1,12 +1,12 @@
 use crate::grid::filter_test::script::FilterScript::*;
 use crate::grid::filter_test::script::*;
-use flowy_grid::entities::{CreateGridFilterPayload, TextFilterCondition};
+use flowy_grid::entities::{CreateGridFilterPayload, FieldType, TextFilterCondition};
 use flowy_grid_data_model::revision::FieldRevision;
 
 #[tokio::test]
 async fn grid_filter_create_test() {
     let mut test = GridFilterTest::new().await;
-    let field_rev = test.text_field();
+    let field_rev = test.get_field_rev(FieldType::RichText);
     let payload = CreateGridFilterPayload::new(field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned()));
     let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }];
     test.run_scripts(scripts).await;
@@ -16,7 +16,7 @@ async fn grid_filter_create_test() {
 #[should_panic]
 async fn grid_filter_invalid_condition_panic_test() {
     let mut test = GridFilterTest::new().await;
-    let field_rev = test.text_field().clone();
+    let field_rev = test.get_field_rev(FieldType::RichText).clone();
 
     // 100 is not a valid condition, so this test should be panic.
     let payload = CreateGridFilterPayload::new(&field_rev, 100, Some("".to_owned()));
@@ -27,7 +27,7 @@ async fn grid_filter_invalid_condition_panic_test() {
 #[tokio::test]
 async fn grid_filter_delete_test() {
     let mut test = GridFilterTest::new().await;
-    let field_rev = test.text_field().clone();
+    let field_rev = test.get_field_rev(FieldType::RichText).clone();
     let payload = create_filter(&field_rev, TextFilterCondition::TextIsEmpty, "abc");
     let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }];
     test.run_scripts(scripts).await;
@@ -36,7 +36,7 @@ async fn grid_filter_delete_test() {
     test.run_scripts(vec![
         DeleteGridTableFilter {
             filter_id: filter.id,
-            field_rev,
+            field_rev: field_rev.as_ref().clone(),
         },
         AssertTableFilterCount { count: 0 },
     ])

+ 164 - 70
frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs

@@ -1,12 +1,13 @@
 #![allow(clippy::all)]
 #![allow(dead_code)]
 #![allow(unused_imports)]
+use crate::grid::block_test::util::GridRowTestBuilder;
 use bytes::Bytes;
 use flowy_grid::entities::*;
-use flowy_grid::services::field::select_option::SelectOption;
+use flowy_grid::services::field::SelectOption;
 use flowy_grid::services::field::*;
 use flowy_grid::services::grid_editor::{GridPadBuilder, GridRevisionEditor};
-use flowy_grid::services::row::CreateRowRevisionPayload;
+use flowy_grid::services::row::{CreateRowRevisionPayload, RowRevisionBuilder};
 use flowy_grid::services::setting::GridSettingChangesetBuilder;
 use flowy_grid_data_model::revision::*;
 use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
@@ -20,6 +21,7 @@ use std::collections::HashMap;
 use std::sync::Arc;
 use std::time::Duration;
 use strum::EnumCount;
+use strum::IntoEnumIterator;
 use tokio::time::sleep;
 
 pub struct GridEditorTest {
@@ -37,14 +39,13 @@ impl GridEditorTest {
     pub async fn new() -> Self {
         let sdk = FlowySDKTest::default();
         let _ = sdk.init_user().await;
-        let build_context = make_all_field_test_grid();
+        let build_context = make_test_grid();
         let view_data: Bytes = build_context.into();
         let test = ViewTest::new_grid_view(&sdk, view_data.to_vec()).await;
         let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap();
         let field_revs = editor.get_field_revs(None).await.unwrap();
         let block_meta_revs = editor.get_block_meta_revs().await.unwrap();
         let row_revs = editor.grid_block_snapshots(None).await.unwrap().pop().unwrap().row_revs;
-        assert_eq!(row_revs.len(), 3);
         assert_eq!(block_meta_revs.len(), 1);
 
         // It seems like you should add the field in the make_test_grid() function.
@@ -64,7 +65,7 @@ impl GridEditorTest {
         }
     }
 
-    pub(crate) async fn get_row_revs(&self) -> Vec<Arc<RowRevision>> {
+    pub async fn get_row_revs(&self) -> Vec<Arc<RowRevision>> {
         self.editor
             .grid_block_snapshots(None)
             .await
@@ -79,86 +80,179 @@ impl GridEditorTest {
         self.editor.get_grid_filter(&layout_type).await.unwrap()
     }
 
-    pub fn text_field(&self) -> &FieldRevision {
+    pub fn get_field_rev(&self, field_type: FieldType) -> &Arc<FieldRevision> {
         self.field_revs
             .iter()
             .filter(|field_rev| {
                 let t_field_type: FieldType = field_rev.field_type_rev.into();
-                t_field_type == FieldType::RichText
+                t_field_type == field_type
             })
             .collect::<Vec<_>>()
             .pop()
             .unwrap()
     }
-}
-
-fn make_all_field_test_grid() -> BuildGridContext {
-    let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
-        .name("Name")
-        .visibility(true)
-        .build();
 
-    // Single Select
-    let single_select = SingleSelectTypeOptionBuilder::default()
-        .option(SelectOption::new("Live"))
-        .option(SelectOption::new("Completed"))
-        .option(SelectOption::new("Planned"))
-        .option(SelectOption::new("Paused"));
-    let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
+    pub fn block_id(&self) -> &str {
+        &self.block_meta_revs.last().unwrap().block_id
+    }
+}
 
-    // MultiSelect
-    let multi_select = MultiSelectTypeOptionBuilder::default()
-        .option(SelectOption::new("Google"))
-        .option(SelectOption::new("Facebook"))
-        .option(SelectOption::new("Twitter"));
-    let multi_select_field = FieldBuilder::new(multi_select)
-        .name("Platform")
-        .visibility(true)
-        .build();
+pub const GOOGLE: &str = "Google";
+pub const FACEBOOK: &str = "Facebook";
+pub const TWITTER: &str = "Twitter";
 
-    // Number
-    let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
-    let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
+pub const COMPLETED: &str = "Completed";
+pub const PLANNED: &str = "Planned";
+pub const PAUSED: &str = "Paused";
 
-    // Date
-    let date = DateTypeOptionBuilder::default()
-        .date_format(DateFormat::US)
-        .time_format(TimeFormat::TwentyFourHour);
-    let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
+// This grid is assumed to contain all the Fields.
+fn make_test_grid() -> BuildGridContext {
+    let mut grid_builder = GridBuilder::new();
+    // Iterate through the FieldType to create the corresponding Field.
+    for field_type in FieldType::iter() {
+        let field_type: FieldType = field_type;
 
-    // Checkbox
-    let checkbox = CheckboxTypeOptionBuilder::default();
-    let checkbox_field = FieldBuilder::new(checkbox).name("is done").visibility(true).build();
+        // The
+        match field_type {
+            FieldType::RichText => {
+                let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
+                    .name("Name")
+                    .visibility(true)
+                    .build();
+                grid_builder.add_field(text_field);
+            }
+            FieldType::Number => {
+                // Number
+                let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
+                let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
+                grid_builder.add_field(number_field);
+            }
+            FieldType::DateTime => {
+                // Date
+                let date = DateTypeOptionBuilder::default()
+                    .date_format(DateFormat::US)
+                    .time_format(TimeFormat::TwentyFourHour);
+                let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
+                grid_builder.add_field(date_field);
+            }
+            FieldType::SingleSelect => {
+                // Single Select
+                let single_select = SingleSelectTypeOptionBuilder::default()
+                    .option(SelectOption::new(COMPLETED))
+                    .option(SelectOption::new(PLANNED))
+                    .option(SelectOption::new(PAUSED));
+                let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
+                grid_builder.add_field(single_select_field);
+            }
+            FieldType::MultiSelect => {
+                // MultiSelect
+                let multi_select = MultiSelectTypeOptionBuilder::default()
+                    .option(SelectOption::new(GOOGLE))
+                    .option(SelectOption::new(FACEBOOK))
+                    .option(SelectOption::new(TWITTER));
+                let multi_select_field = FieldBuilder::new(multi_select)
+                    .name("Platform")
+                    .visibility(true)
+                    .build();
+                grid_builder.add_field(multi_select_field);
+            }
+            FieldType::Checkbox => {
+                // Checkbox
+                let checkbox = CheckboxTypeOptionBuilder::default();
+                let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build();
+                grid_builder.add_field(checkbox_field);
+            }
+            FieldType::URL => {
+                // URL
+                let url = URLTypeOptionBuilder::default();
+                let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
+                grid_builder.add_field(url_field);
+            }
+        }
+    }
 
-    // URL
-    let url = URLTypeOptionBuilder::default();
-    let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
+    // We have many assumptions base on the number of the rows, so do not change the number of the loop.
+    for i in 0..5 {
+        let block_id = grid_builder.block_id().to_owned();
+        let field_revs = grid_builder.field_revs();
+        let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs);
+        match i {
+            0 => {
+                for field_type in FieldType::iter() {
+                    match field_type {
+                        FieldType::RichText => row_builder.insert_text_cell("A"),
+                        FieldType::Number => row_builder.insert_number_cell("1"),
+                        FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
+                        FieldType::SingleSelect => {
+                            row_builder.insert_single_select_cell(|mut options| options.remove(0))
+                        }
+                        FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
+                        _ => "".to_owned(),
+                    };
+                }
+            }
+            1 => {
+                for field_type in FieldType::iter() {
+                    match field_type {
+                        FieldType::RichText => row_builder.insert_text_cell("B"),
+                        FieldType::Number => row_builder.insert_number_cell("2"),
+                        FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
+                        FieldType::SingleSelect => {
+                            row_builder.insert_single_select_cell(|mut options| options.remove(0))
+                        }
+                        FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
+                        _ => "".to_owned(),
+                    };
+                }
+            }
+            2 => {
+                for field_type in FieldType::iter() {
+                    match field_type {
+                        FieldType::RichText => row_builder.insert_text_cell("C"),
+                        FieldType::Number => row_builder.insert_number_cell("3"),
+                        FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
+                        FieldType::SingleSelect => {
+                            row_builder.insert_single_select_cell(|mut options| options.remove(1))
+                        }
+                        FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
+                        _ => "".to_owned(),
+                    };
+                }
+            }
+            3 => {
+                for field_type in FieldType::iter() {
+                    match field_type {
+                        FieldType::RichText => row_builder.insert_text_cell("D"),
+                        FieldType::Number => row_builder.insert_number_cell("4"),
+                        FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
+                        FieldType::SingleSelect => {
+                            row_builder.insert_single_select_cell(|mut options| options.remove(1))
+                        }
+                        FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
+                        _ => "".to_owned(),
+                    };
+                }
+            }
+            4 => {
+                for field_type in FieldType::iter() {
+                    match field_type {
+                        FieldType::RichText => row_builder.insert_text_cell("E"),
+                        FieldType::Number => row_builder.insert_number_cell("5"),
+                        FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
+                        FieldType::SingleSelect => {
+                            row_builder.insert_single_select_cell(|mut options| options.remove(2))
+                        }
 
-    // for i in 0..3 {
-    //     for field_type in FieldType::iter() {
-    //         let field_type: FieldType = field_type;
-    //         match field_type {
-    //             FieldType::RichText => {}
-    //             FieldType::Number => {}
-    //             FieldType::DateTime => {}
-    //             FieldType::SingleSelect => {}
-    //             FieldType::MultiSelect => {}
-    //             FieldType::Checkbox => {}
-    //             FieldType::URL => {}
-    //         }
-    //     }
-    // }
+                        FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
+                        _ => "".to_owned(),
+                    };
+                }
+            }
+            _ => {}
+        }
 
-    GridBuilder::default()
-        .add_field(text_field)
-        .add_field(single_select_field)
-        .add_field(multi_select_field)
-        .add_field(number_field)
-        .add_field(date_field)
-        .add_field(checkbox_field)
-        .add_field(url_field)
-        .add_empty_row()
-        .add_empty_row()
-        .add_empty_row()
-        .build()
+        let row_rev = row_builder.build();
+        grid_builder.add_row(row_rev);
+    }
+    grid_builder.build()
 }

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

@@ -53,7 +53,7 @@ impl GridRevision {
     pub fn from_build_context(grid_id: &str, context: BuildGridContext) -> Self {
         Self {
             grid_id: grid_id.to_owned(),
-            fields: context.field_revs.into_iter().map(Arc::new).collect(),
+            fields: context.field_revs,
             blocks: context.blocks.into_iter().map(Arc::new).collect(),
             setting: Default::default(),
         }
@@ -245,7 +245,7 @@ impl CellRevision {
 
 #[derive(Clone, Default, Deserialize, Serialize)]
 pub struct BuildGridContext {
-    pub field_revs: Vec<FieldRevision>,
+    pub field_revs: Vec<Arc<FieldRevision>>,
     pub blocks: Vec<GridBlockMetaRevision>,
     pub blocks_meta_data: Vec<GridBlockRevision>,
 }

+ 20 - 7
shared-lib/flowy-sync/src/client_grid/grid_builder.rs

@@ -26,18 +26,31 @@ impl std::default::Default for GridBuilder {
 }
 
 impl GridBuilder {
-    pub fn add_field(mut self, field: FieldRevision) -> Self {
-        self.build_context.field_revs.push(field);
-        self
+    pub fn new() -> Self {
+        Self::default()
+    }
+    pub fn add_field(&mut self, field: FieldRevision) {
+        self.build_context.field_revs.push(Arc::new(field));
     }
 
-    pub fn add_empty_row(mut self) -> Self {
-        let row = RowRevision::new(&self.build_context.blocks.first().unwrap().block_id);
+    pub fn add_row(&mut self, row_rev: RowRevision) {
         let block_meta_rev = self.build_context.blocks.first_mut().unwrap();
         let block_rev = self.build_context.blocks_meta_data.first_mut().unwrap();
-        block_rev.rows.push(Arc::new(row));
+        block_rev.rows.push(Arc::new(row_rev));
         block_meta_rev.row_count += 1;
-        self
+    }
+
+    pub fn add_empty_row(&mut self) {
+        let row = RowRevision::new(self.block_id());
+        self.add_row(row);
+    }
+
+    pub fn field_revs(&self) -> &Vec<Arc<FieldRevision>> {
+        &self.build_context.field_revs
+    }
+
+    pub fn block_id(&self) -> &str {
+        &self.build_context.blocks.first().unwrap().block_id
     }
 
     pub fn build(self) -> BuildGridContext {