mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #599 from AppFlowy-IO/refactor/grid_decode_cell_data
Refactor/grid decode cell data
This commit is contained in:
commit
bb6a670d26
@ -38,7 +38,7 @@ class CellService {
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId
|
||||
..cellContentChangeset = data;
|
||||
..content = data;
|
||||
return GridEventUpdateCell(payload).send();
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ pub struct CellChangeset {
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub cell_content_changeset: Option<String>,
|
||||
pub content: Option<String>,
|
||||
}
|
||||
|
||||
impl std::convert::From<CellChangeset> for RowMetaChangeset {
|
||||
@ -142,7 +142,7 @@ impl std::convert::From<CellChangeset> for RowMetaChangeset {
|
||||
let mut cell_by_field_id = HashMap::with_capacity(1);
|
||||
let field_id = changeset.field_id;
|
||||
let cell_rev = CellRevision {
|
||||
data: changeset.cell_content_changeset.unwrap_or_else(|| "".to_owned()),
|
||||
data: changeset.content.unwrap_or_else(|| "".to_owned()),
|
||||
};
|
||||
cell_by_field_id.insert(field_id, cell_rev);
|
||||
|
||||
|
@ -458,6 +458,8 @@ impl TryInto<FieldChangesetParams> for FieldChangesetPayload {
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
)]
|
||||
/// The order of the enum can't be changed. If you want to add a new type,
|
||||
/// it would be better to append it to the end of the list.
|
||||
#[repr(u8)]
|
||||
pub enum FieldType {
|
||||
RichText = 0,
|
||||
|
@ -1,10 +1,10 @@
|
||||
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,
|
||||
};
|
||||
use crate::services::row::AnyCellData;
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::revision::FieldRevision;
|
||||
use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams};
|
||||
@ -323,7 +323,7 @@ pub(crate) async fn update_select_option_handler(
|
||||
let mut cell_content_changeset = None;
|
||||
|
||||
if let Some(option) = changeset.insert_option {
|
||||
cell_content_changeset = Some(SelectOptionCellContentChangeset::from_insert(&option.id).to_str());
|
||||
cell_content_changeset = Some(SelectOptionCellChangeset::from_insert(&option.id).to_str());
|
||||
type_option.insert_option(option);
|
||||
}
|
||||
|
||||
@ -332,7 +332,7 @@ pub(crate) async fn update_select_option_handler(
|
||||
}
|
||||
|
||||
if let Some(option) = changeset.delete_option {
|
||||
cell_content_changeset = Some(SelectOptionCellContentChangeset::from_delete(&option.id).to_str());
|
||||
cell_content_changeset = Some(SelectOptionCellChangeset::from_delete(&option.id).to_str());
|
||||
type_option.delete_option(option);
|
||||
}
|
||||
|
||||
@ -343,7 +343,7 @@ pub(crate) async fn update_select_option_handler(
|
||||
grid_id: changeset.cell_identifier.grid_id,
|
||||
row_id: changeset.cell_identifier.row_id,
|
||||
field_id: changeset.cell_identifier.field_id,
|
||||
cell_content_changeset,
|
||||
content: cell_content_changeset,
|
||||
};
|
||||
let _ = editor.update_cell(changeset).await?;
|
||||
}
|
||||
|
151
frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs
Normal file
151
frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs
Normal file
@ -0,0 +1,151 @@
|
||||
use crate::entities::FieldType;
|
||||
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.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AnyCellData {
|
||||
pub cell_data: String,
|
||||
pub field_type: FieldType,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for AnyCellData {
|
||||
type Err = FlowyError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let type_option_cell_data: AnyCellData = serde_json::from_str(s)?;
|
||||
Ok(type_option_cell_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryInto<AnyCellData> for String {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_into(self) -> Result<AnyCellData, Self::Error> {
|
||||
AnyCellData::from_str(&self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<&CellRevision> for AnyCellData {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(value: &CellRevision) -> Result<Self, Self::Error> {
|
||||
Self::from_str(&value.data)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<&Option<CellRevision>> for AnyCellData {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(value: &Option<CellRevision>) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
None => Err(FlowyError::invalid_data().context("Expected CellRevision, but receive None")),
|
||||
Some(cell_rev) => AnyCellData::try_from(cell_rev),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<Option<CellRevision>> for AnyCellData {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(value: Option<CellRevision>) -> Result<Self, Self::Error> {
|
||||
Self::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyCellData {
|
||||
pub fn new(content: String, field_type: FieldType) -> Self {
|
||||
AnyCellData {
|
||||
cell_data: content,
|
||||
field_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn json(&self) -> String {
|
||||
serde_json::to_string(self).unwrap_or_else(|_| "".to_owned())
|
||||
}
|
||||
|
||||
pub fn is_number(&self) -> bool {
|
||||
self.field_type == FieldType::Number
|
||||
}
|
||||
|
||||
pub fn is_text(&self) -> bool {
|
||||
self.field_type == FieldType::RichText
|
||||
}
|
||||
|
||||
pub fn is_checkbox(&self) -> bool {
|
||||
self.field_type == FieldType::Checkbox
|
||||
}
|
||||
|
||||
pub fn is_date(&self) -> bool {
|
||||
self.field_type == FieldType::DateTime
|
||||
}
|
||||
|
||||
pub fn is_single_select(&self) -> bool {
|
||||
self.field_type == FieldType::SingleSelect
|
||||
}
|
||||
|
||||
pub fn is_multi_select(&self) -> bool {
|
||||
self.field_type == FieldType::MultiSelect
|
||||
}
|
||||
|
||||
pub fn is_url(&self) -> bool {
|
||||
self.field_type == FieldType::URL
|
||||
}
|
||||
|
||||
pub fn is_select_option(&self) -> bool {
|
||||
self.field_type == FieldType::MultiSelect || self.field_type == FieldType::SingleSelect
|
||||
}
|
||||
}
|
||||
|
||||
/// The data is encoded by protobuf or utf8. You should choose the corresponding decode struct to parse it.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// * Use DateCellData to parse the data when the FieldType is Date.
|
||||
/// * Use URLCellData to parse the data when the FieldType is URL.
|
||||
/// * 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>,
|
||||
}
|
||||
|
||||
impl DecodedCellData {
|
||||
pub fn new<T: AsRef<[u8]>>(data: T) -> Self {
|
||||
Self {
|
||||
data: data.as_ref().to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_bytes<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() })
|
||||
}
|
||||
|
||||
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.data.as_ref()).map_err(internal_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for DecodedCellData {
|
||||
fn to_string(&self) -> String {
|
||||
match String::from_utf8(self.data.clone()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
tracing::error!("DecodedCellData to string failed: {:?}", e);
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
194
frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs
Normal file
194
frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs
Normal file
@ -0,0 +1,194 @@
|
||||
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::field::*;
|
||||
|
||||
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.
|
||||
/// For example:
|
||||
/// URLCellData, DateCellData. etc.
|
||||
fn decode_cell_data(
|
||||
&self,
|
||||
cell_data: CellData<D>,
|
||||
decoded_field_type: &FieldType,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<DecodedCellData>;
|
||||
|
||||
/// The changeset is able to parse into the specific data that was impl the FromCellChangeset trait.
|
||||
/// For example:
|
||||
/// SelectOptionCellChangeset,DateCellChangeset. etc.
|
||||
fn apply_changeset(&self, changeset: CellDataChangeset<C>, 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
|
||||
pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
|
||||
changeset: C,
|
||||
cell_rev: Option<CellRevision>,
|
||||
field_rev: T,
|
||||
) -> Result<String, FlowyError> {
|
||||
let field_rev = field_rev.as_ref();
|
||||
let changeset = changeset.to_string();
|
||||
let field_type = field_rev.field_type_rev.into();
|
||||
let s = match field_type {
|
||||
FieldType::RichText => RichTextTypeOption::from(field_rev).apply_changeset(changeset.into(), cell_rev),
|
||||
FieldType::Number => NumberTypeOption::from(field_rev).apply_changeset(changeset.into(), cell_rev),
|
||||
FieldType::DateTime => DateTypeOption::from(field_rev).apply_changeset(changeset.into(), cell_rev),
|
||||
FieldType::SingleSelect => SingleSelectTypeOption::from(field_rev).apply_changeset(changeset.into(), cell_rev),
|
||||
FieldType::MultiSelect => MultiSelectTypeOption::from(field_rev).apply_changeset(changeset.into(), cell_rev),
|
||||
FieldType::Checkbox => CheckboxTypeOption::from(field_rev).apply_changeset(changeset.into(), cell_rev),
|
||||
FieldType::URL => URLTypeOption::from(field_rev).apply_changeset(changeset.into(), cell_rev),
|
||||
}?;
|
||||
|
||||
Ok(AnyCellData::new(s, field_type).json())
|
||||
}
|
||||
|
||||
pub fn decode_any_cell_data<T: TryInto<AnyCellData>>(data: T, field_rev: &FieldRevision) -> DecodedCellData {
|
||||
if let Ok(any_cell_data) = data.try_into() {
|
||||
let AnyCellData { cell_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,
|
||||
Err(e) => {
|
||||
tracing::error!("Decode cell data failed, {:?}", e);
|
||||
DecodedCellData::default()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::error!("Decode type option data failed");
|
||||
DecodedCellData::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_decode_cell_data(
|
||||
cell_data: CellData<String>,
|
||||
field_rev: &FieldRevision,
|
||||
s_field_type: &FieldType,
|
||||
t_field_type: &FieldType,
|
||||
) -> FlowyResult<DecodedCellData> {
|
||||
let cell_data = cell_data.try_into_inner()?;
|
||||
let get_cell_data = || {
|
||||
let field_type: FieldTypeRevision = t_field_type.into();
|
||||
let data = match t_field_type {
|
||||
FieldType::RichText => field_rev
|
||||
.get_type_option_entry::<RichTextTypeOption>(field_type)?
|
||||
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
|
||||
FieldType::Number => field_rev
|
||||
.get_type_option_entry::<NumberTypeOption>(field_type)?
|
||||
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
|
||||
FieldType::DateTime => field_rev
|
||||
.get_type_option_entry::<DateTypeOption>(field_type)?
|
||||
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
|
||||
FieldType::SingleSelect => field_rev
|
||||
.get_type_option_entry::<SingleSelectTypeOption>(field_type)?
|
||||
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
|
||||
FieldType::MultiSelect => field_rev
|
||||
.get_type_option_entry::<MultiSelectTypeOption>(field_type)?
|
||||
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
|
||||
FieldType::Checkbox => field_rev
|
||||
.get_type_option_entry::<CheckboxTypeOption>(field_type)?
|
||||
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
|
||||
FieldType::URL => field_rev
|
||||
.get_type_option_entry::<URLTypeOption>(field_type)?
|
||||
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
|
||||
};
|
||||
Some(data)
|
||||
};
|
||||
|
||||
match get_cell_data() {
|
||||
Some(Ok(data)) => Ok(data),
|
||||
Some(Err(err)) => {
|
||||
tracing::error!("{:?}", err);
|
||||
Ok(DecodedCellData::default())
|
||||
}
|
||||
None => Ok(DecodedCellData::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromCellString {
|
||||
fn from_cell_str(s: &str) -> FlowyResult<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub struct CellData<T>(pub Option<T>);
|
||||
|
||||
impl<T> CellData<T> {
|
||||
pub fn try_into_inner(self) -> FlowyResult<T> {
|
||||
match self.0 {
|
||||
None => Err(ErrorCode::InvalidData.into()),
|
||||
Some(data) => Ok(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::convert::From<String> for CellData<T>
|
||||
where
|
||||
T: FromCellString,
|
||||
{
|
||||
fn from(s: String) -> Self {
|
||||
match T::from_cell_str(&s) {
|
||||
Ok(inner) => CellData(Some(inner)),
|
||||
Err(e) => {
|
||||
tracing::error!("Deserialize Cell Data failed: {}", e);
|
||||
CellData(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<String> for CellData<String> {
|
||||
fn from(s: String) -> Self {
|
||||
CellData(Some(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<CellData<String>> for String {
|
||||
fn from(p: CellData<String>) -> Self {
|
||||
p.try_into_inner().unwrap_or_else(|_| String::new())
|
||||
}
|
||||
}
|
||||
|
||||
// CellChangeset
|
||||
pub trait FromCellChangeset {
|
||||
fn from_changeset(changeset: String) -> FlowyResult<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub struct CellDataChangeset<T>(pub Option<T>);
|
||||
|
||||
impl<T> CellDataChangeset<T> {
|
||||
pub fn try_into_inner(self) -> FlowyResult<T> {
|
||||
match self.0 {
|
||||
None => Err(ErrorCode::InvalidData.into()),
|
||||
Some(data) => Ok(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C: ToString> std::convert::From<C> for CellDataChangeset<T>
|
||||
where
|
||||
T: FromCellChangeset,
|
||||
{
|
||||
fn from(changeset: C) -> Self {
|
||||
match T::from_changeset(changeset.to_string()) {
|
||||
Ok(data) => CellDataChangeset(Some(data)),
|
||||
Err(e) => {
|
||||
tracing::error!("Deserialize CellDataChangeset failed: {}", e);
|
||||
CellDataChangeset(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::convert::From<String> for CellDataChangeset<String> {
|
||||
fn from(s: String) -> Self {
|
||||
CellDataChangeset(Some(s))
|
||||
}
|
||||
}
|
5
frontend/rust-lib/flowy-grid/src/services/cell/mod.rs
Normal file
5
frontend/rust-lib/flowy-grid/src/services/cell/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod any_cell_data;
|
||||
mod cell_operation;
|
||||
|
||||
pub use any_cell_data::*;
|
||||
pub use cell_operation::*;
|
@ -1,8 +1,8 @@
|
||||
use crate::entities::{CellChangeset, CellIdentifier, CellIdentifierPayload, FieldType};
|
||||
use crate::services::cell::{AnyCellData, FromCellChangeset, FromCellString};
|
||||
use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
|
||||
use crate::services::row::AnyCellData;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::parser::NotEmptyStr;
|
||||
use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataEntry};
|
||||
use nanoid::nanoid;
|
||||
@ -155,6 +155,15 @@ impl std::convert::TryFrom<AnyCellData> for SelectOptionIds {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromCellString for SelectOptionIds {
|
||||
fn from_cell_str(s: &str) -> FlowyResult<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(Self::from(s.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<String> for SelectOptionIds {
|
||||
fn from(s: String) -> Self {
|
||||
let ids = s
|
||||
@ -208,7 +217,7 @@ pub struct SelectOptionCellChangesetParams {
|
||||
|
||||
impl std::convert::From<SelectOptionCellChangesetParams> for CellChangeset {
|
||||
fn from(params: SelectOptionCellChangesetParams) -> Self {
|
||||
let changeset = SelectOptionCellContentChangeset {
|
||||
let changeset = SelectOptionCellChangeset {
|
||||
insert_option_id: params.insert_option_id,
|
||||
delete_option_id: params.delete_option_id,
|
||||
};
|
||||
@ -217,7 +226,7 @@ impl std::convert::From<SelectOptionCellChangesetParams> for CellChangeset {
|
||||
grid_id: params.cell_identifier.grid_id,
|
||||
row_id: params.cell_identifier.row_id,
|
||||
field_id: params.cell_identifier.field_id,
|
||||
cell_content_changeset: Some(s),
|
||||
content: Some(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -254,21 +263,30 @@ impl TryInto<SelectOptionCellChangesetParams> for SelectOptionCellChangesetPaylo
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct SelectOptionCellContentChangeset {
|
||||
pub struct SelectOptionCellChangeset {
|
||||
pub insert_option_id: Option<String>,
|
||||
pub delete_option_id: Option<String>,
|
||||
}
|
||||
|
||||
impl SelectOptionCellContentChangeset {
|
||||
impl FromCellChangeset for SelectOptionCellChangeset {
|
||||
fn from_changeset(changeset: String) -> FlowyResult<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
serde_json::from_str::<SelectOptionCellChangeset>(&changeset).map_err(internal_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectOptionCellChangeset {
|
||||
pub fn from_insert(option_id: &str) -> Self {
|
||||
SelectOptionCellContentChangeset {
|
||||
SelectOptionCellChangeset {
|
||||
insert_option_id: Some(option_id.to_string()),
|
||||
delete_option_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_delete(option_id: &str) -> Self {
|
||||
SelectOptionCellContentChangeset {
|
||||
SelectOptionCellChangeset {
|
||||
insert_option_id: None,
|
||||
delete_option_id: Some(option_id.to_string()),
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::entities::{FieldType, GridCheckboxFilter};
|
||||
use crate::impl_type_option;
|
||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||
use crate::services::row::{
|
||||
AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
|
||||
use crate::services::cell::{
|
||||
AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
|
||||
};
|
||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
@ -52,33 +52,31 @@ impl CellFilterOperation<GridCheckboxFilter> for CheckboxTypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
impl CellDataOperation<String> for CheckboxTypeOption {
|
||||
fn decode_cell_data<T>(
|
||||
impl CellDataOperation<String, String> for CheckboxTypeOption {
|
||||
fn decode_cell_data(
|
||||
&self,
|
||||
cell_data: T,
|
||||
cell_data: CellData<String>,
|
||||
decoded_field_type: &FieldType,
|
||||
_field_rev: &FieldRevision,
|
||||
) -> FlowyResult<DecodedCellData>
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
) -> FlowyResult<DecodedCellData> {
|
||||
if !decoded_field_type.is_checkbox() {
|
||||
return Ok(DecodedCellData::default());
|
||||
}
|
||||
|
||||
let encoded_data = cell_data.into();
|
||||
if encoded_data == YES || encoded_data == NO {
|
||||
return Ok(DecodedCellData::new(encoded_data));
|
||||
let s: String = cell_data.try_into_inner()?;
|
||||
if s == YES || s == NO {
|
||||
return Ok(DecodedCellData::new(s));
|
||||
}
|
||||
|
||||
Ok(DecodedCellData::default())
|
||||
}
|
||||
|
||||
fn apply_changeset<C>(&self, changeset: C, _cell_rev: Option<CellRevision>) -> Result<String, FlowyError>
|
||||
where
|
||||
C: Into<CellContentChangeset>,
|
||||
{
|
||||
let changeset = changeset.into();
|
||||
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,
|
||||
@ -117,10 +115,9 @@ impl std::convert::TryFrom<AnyCellData> for CheckboxCellData {
|
||||
|
||||
#[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::services::row::{apply_cell_data_changeset, decode_any_cell_data};
|
||||
|
||||
use crate::entities::FieldType;
|
||||
|
||||
|
@ -1,15 +1,16 @@
|
||||
use crate::entities::{CellChangeset, FieldType, GridDateFilter};
|
||||
use crate::entities::{CellIdentifier, CellIdentifierPayload};
|
||||
use crate::impl_type_option;
|
||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||
use crate::services::row::{
|
||||
AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
|
||||
use crate::services::cell::{
|
||||
AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, 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::{ErrorCode, FlowyError, FlowyResult};
|
||||
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;
|
||||
@ -126,16 +127,13 @@ impl CellFilterOperation<GridDateFilter> for DateTypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
impl CellDataOperation<String> for DateTypeOption {
|
||||
fn decode_cell_data<T>(
|
||||
impl CellDataOperation<TimestampParser, DateCellChangeset> for DateTypeOption {
|
||||
fn decode_cell_data(
|
||||
&self,
|
||||
cell_data: T,
|
||||
cell_data: CellData<TimestampParser>,
|
||||
decoded_field_type: &FieldType,
|
||||
_field_rev: &FieldRevision,
|
||||
) -> FlowyResult<DecodedCellData>
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
) -> 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:
|
||||
@ -143,20 +141,20 @@ impl CellDataOperation<String> for DateTypeOption {
|
||||
if !decoded_field_type.is_date() {
|
||||
return Ok(DecodedCellData::default());
|
||||
}
|
||||
|
||||
let timestamp = cell_data.into().parse::<i64>().unwrap_or(0);
|
||||
let date = self.today_desc_from_timestamp(timestamp);
|
||||
let timestamp = cell_data.try_into_inner()?;
|
||||
let date = self.today_desc_from_timestamp(timestamp.0);
|
||||
DecodedCellData::try_from_bytes(date)
|
||||
}
|
||||
|
||||
fn apply_changeset<C>(&self, changeset: C, _cell_rev: Option<CellRevision>) -> Result<String, FlowyError>
|
||||
where
|
||||
C: Into<CellContentChangeset>,
|
||||
{
|
||||
let content_changeset: DateCellContentChangeset = serde_json::from_str(&changeset.into())?;
|
||||
let cell_data = match content_changeset.date_timestamp() {
|
||||
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, content_changeset.time) {
|
||||
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);
|
||||
@ -170,6 +168,17 @@ impl CellDataOperation<String> for DateTypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TimestampParser(i64);
|
||||
|
||||
impl FromCellString for TimestampParser {
|
||||
fn from_cell_str(s: &str) -> FlowyResult<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let num = s.parse::<i64>().unwrap_or(0);
|
||||
Ok(TimestampParser(num))
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct DateTypeOptionBuilder(DateTypeOption);
|
||||
impl_into_box_type_option_builder!(DateTypeOptionBuilder);
|
||||
@ -323,7 +332,7 @@ impl TryInto<DateChangesetParams> for DateChangesetPayload {
|
||||
|
||||
impl std::convert::From<DateChangesetParams> for CellChangeset {
|
||||
fn from(params: DateChangesetParams) -> Self {
|
||||
let changeset = DateCellContentChangeset {
|
||||
let changeset = DateCellChangeset {
|
||||
date: params.date,
|
||||
time: params.time,
|
||||
};
|
||||
@ -332,18 +341,18 @@ impl std::convert::From<DateChangesetParams> for CellChangeset {
|
||||
grid_id: params.cell_identifier.grid_id,
|
||||
row_id: params.cell_identifier.row_id,
|
||||
field_id: params.cell_identifier.field_id,
|
||||
cell_content_changeset: Some(s),
|
||||
content: Some(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct DateCellContentChangeset {
|
||||
pub struct DateCellChangeset {
|
||||
pub date: Option<String>,
|
||||
pub time: Option<String>,
|
||||
}
|
||||
|
||||
impl DateCellContentChangeset {
|
||||
impl DateCellChangeset {
|
||||
pub fn date_timestamp(&self) -> Option<i64> {
|
||||
if let Some(date) = &self.date {
|
||||
match date.parse::<i64>() {
|
||||
@ -356,19 +365,21 @@ impl DateCellContentChangeset {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<DateCellContentChangeset> for CellContentChangeset {
|
||||
fn from(changeset: DateCellContentChangeset) -> Self {
|
||||
let s = serde_json::to_string(&changeset).unwrap();
|
||||
CellContentChangeset::from(s)
|
||||
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::{DateCellContentChangeset, DateCellData, DateFormat, DateTypeOption, TimeFormat};
|
||||
use crate::services::row::CellDataOperation;
|
||||
use crate::services::field::{DateCellChangeset, DateCellData, DateFormat, DateTypeOption, TimeFormat};
|
||||
use flowy_grid_data_model::revision::FieldRevision;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
@ -379,7 +390,7 @@ mod tests {
|
||||
let field_rev = FieldBuilder::from_field_type(&field_type).build();
|
||||
assert_changeset_result(
|
||||
&type_option,
|
||||
DateCellContentChangeset {
|
||||
DateCellChangeset {
|
||||
date: Some("1e".to_string()),
|
||||
time: Some("23:00".to_owned()),
|
||||
},
|
||||
@ -425,7 +436,7 @@ mod tests {
|
||||
TimeFormat::TwentyFourHour => {
|
||||
assert_changeset_result(
|
||||
&type_option,
|
||||
DateCellContentChangeset {
|
||||
DateCellChangeset {
|
||||
date: Some(1653609600.to_string()),
|
||||
time: None,
|
||||
},
|
||||
@ -435,7 +446,7 @@ mod tests {
|
||||
);
|
||||
assert_changeset_result(
|
||||
&type_option,
|
||||
DateCellContentChangeset {
|
||||
DateCellChangeset {
|
||||
date: Some(1653609600.to_string()),
|
||||
time: Some("23:00".to_owned()),
|
||||
},
|
||||
@ -447,7 +458,7 @@ mod tests {
|
||||
TimeFormat::TwelveHour => {
|
||||
assert_changeset_result(
|
||||
&type_option,
|
||||
DateCellContentChangeset {
|
||||
DateCellChangeset {
|
||||
date: Some(1653609600.to_string()),
|
||||
time: None,
|
||||
},
|
||||
@ -458,7 +469,7 @@ mod tests {
|
||||
//
|
||||
assert_changeset_result(
|
||||
&type_option,
|
||||
DateCellContentChangeset {
|
||||
DateCellChangeset {
|
||||
date: Some(1653609600.to_string()),
|
||||
time: Some("".to_owned()),
|
||||
},
|
||||
@ -469,7 +480,7 @@ mod tests {
|
||||
|
||||
assert_changeset_result(
|
||||
&type_option,
|
||||
DateCellContentChangeset {
|
||||
DateCellChangeset {
|
||||
date: Some(1653609600.to_string()),
|
||||
time: Some("11:23 pm".to_owned()),
|
||||
},
|
||||
@ -491,7 +502,7 @@ mod tests {
|
||||
|
||||
assert_changeset_result(
|
||||
&type_option,
|
||||
DateCellContentChangeset {
|
||||
DateCellChangeset {
|
||||
date: Some(date_timestamp.clone()),
|
||||
time: None,
|
||||
},
|
||||
@ -503,7 +514,7 @@ mod tests {
|
||||
type_option.include_time = true;
|
||||
assert_changeset_result(
|
||||
&type_option,
|
||||
DateCellContentChangeset {
|
||||
DateCellChangeset {
|
||||
date: Some(date_timestamp.clone()),
|
||||
time: None,
|
||||
},
|
||||
@ -514,7 +525,7 @@ mod tests {
|
||||
|
||||
assert_changeset_result(
|
||||
&type_option,
|
||||
DateCellContentChangeset {
|
||||
DateCellChangeset {
|
||||
date: Some(date_timestamp.clone()),
|
||||
time: Some("1:00".to_owned()),
|
||||
},
|
||||
@ -526,7 +537,7 @@ mod tests {
|
||||
type_option.time_format = TimeFormat::TwelveHour;
|
||||
assert_changeset_result(
|
||||
&type_option,
|
||||
DateCellContentChangeset {
|
||||
DateCellChangeset {
|
||||
date: Some(date_timestamp),
|
||||
time: Some("1:00 am".to_owned()),
|
||||
},
|
||||
@ -546,7 +557,7 @@ mod tests {
|
||||
|
||||
assert_changeset_result(
|
||||
&type_option,
|
||||
DateCellContentChangeset {
|
||||
DateCellChangeset {
|
||||
date: Some(date_timestamp.clone()),
|
||||
time: Some("1:".to_owned()),
|
||||
},
|
||||
@ -557,7 +568,7 @@ mod tests {
|
||||
|
||||
assert_changeset_result(
|
||||
&type_option,
|
||||
DateCellContentChangeset {
|
||||
DateCellChangeset {
|
||||
date: Some(date_timestamp),
|
||||
time: Some("1:00".to_owned()),
|
||||
},
|
||||
@ -577,7 +588,7 @@ mod tests {
|
||||
|
||||
assert_changeset_result(
|
||||
&type_option,
|
||||
DateCellContentChangeset {
|
||||
DateCellChangeset {
|
||||
date: Some(date_timestamp),
|
||||
time: Some("1:00 am".to_owned()),
|
||||
},
|
||||
@ -589,11 +600,12 @@ mod tests {
|
||||
|
||||
fn assert_changeset_result(
|
||||
type_option: &DateTypeOption,
|
||||
changeset: DateCellContentChangeset,
|
||||
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(),
|
||||
@ -607,15 +619,12 @@ mod tests {
|
||||
field_rev: &FieldRevision,
|
||||
expected: &str,
|
||||
) {
|
||||
let encoded_data = type_option
|
||||
.apply_changeset(
|
||||
DateCellContentChangeset {
|
||||
date: Some(timestamp.to_string()),
|
||||
time: None,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
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(),
|
||||
@ -623,13 +632,9 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
fn decode_cell_data<T: Into<String>>(
|
||||
encoded_data: T,
|
||||
type_option: &DateTypeOption,
|
||||
field_rev: &FieldRevision,
|
||||
) -> String {
|
||||
fn decode_cell_data(encoded_data: String, type_option: &DateTypeOption, field_rev: &FieldRevision) -> String {
|
||||
let decoded_data = type_option
|
||||
.decode_cell_data(encoded_data, &FieldType::DateTime, field_rev)
|
||||
.decode_cell_data(encoded_data.into(), &FieldType::DateTime, field_rev)
|
||||
.unwrap()
|
||||
.parse::<DateCellData>()
|
||||
.unwrap();
|
||||
|
@ -1,15 +1,15 @@
|
||||
use crate::entities::{FieldType, GridSelectOptionFilter};
|
||||
|
||||
use crate::impl_type_option;
|
||||
use crate::services::cell::{
|
||||
AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
|
||||
};
|
||||
use crate::services::field::select_option::{
|
||||
make_selected_select_options, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData,
|
||||
SelectOptionIds, SelectOptionOperation, SelectedSelectOptions, SELECTION_IDS_SEPARATOR,
|
||||
make_selected_select_options, SelectOption, SelectOptionCellChangeset, SelectOptionCellData, SelectOptionIds,
|
||||
SelectOptionOperation, SelectedSelectOptions, SELECTION_IDS_SEPARATOR,
|
||||
};
|
||||
use crate::services::field::type_options::util::get_cell_data;
|
||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||
use crate::services::row::{
|
||||
AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
@ -56,22 +56,18 @@ impl CellFilterOperation<GridSelectOptionFilter> for MultiSelectTypeOption {
|
||||
Ok(filter.apply(&selected_options))
|
||||
}
|
||||
}
|
||||
impl CellDataOperation<String> for MultiSelectTypeOption {
|
||||
fn decode_cell_data<T>(
|
||||
impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for MultiSelectTypeOption {
|
||||
fn decode_cell_data(
|
||||
&self,
|
||||
cell_data: T,
|
||||
cell_data: CellData<SelectOptionIds>,
|
||||
decoded_field_type: &FieldType,
|
||||
_field_rev: &FieldRevision,
|
||||
) -> FlowyResult<DecodedCellData>
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
) -> FlowyResult<DecodedCellData> {
|
||||
if !decoded_field_type.is_select_option() {
|
||||
return Ok(DecodedCellData::default());
|
||||
}
|
||||
|
||||
let encoded_data = cell_data.into();
|
||||
let ids: SelectOptionIds = encoded_data.into();
|
||||
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())
|
||||
@ -85,11 +81,12 @@ impl CellDataOperation<String> for MultiSelectTypeOption {
|
||||
DecodedCellData::try_from_bytes(cell_data)
|
||||
}
|
||||
|
||||
fn apply_changeset<T>(&self, changeset: T, cell_rev: Option<CellRevision>) -> Result<String, FlowyError>
|
||||
where
|
||||
T: Into<CellContentChangeset>,
|
||||
{
|
||||
let content_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset.into())?;
|
||||
fn apply_changeset(
|
||||
&self,
|
||||
changeset: CellDataChangeset<SelectOptionCellChangeset>,
|
||||
cell_rev: Option<CellRevision>,
|
||||
) -> Result<String, FlowyError> {
|
||||
let content_changeset = changeset.try_into_inner()?;
|
||||
let new_cell_data: String;
|
||||
match cell_rev {
|
||||
None => {
|
||||
@ -144,10 +141,10 @@ impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
|
||||
#[cfg(test)]
|
||||
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::{MultiSelectTypeOption, MultiSelectTypeOptionBuilder};
|
||||
use crate::services::row::CellDataOperation;
|
||||
use flowy_grid_data_model::revision::FieldRevision;
|
||||
|
||||
#[test]
|
||||
@ -168,8 +165,8 @@ mod tests {
|
||||
let type_option = MultiSelectTypeOption::from(&field_rev);
|
||||
|
||||
let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
|
||||
let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
|
||||
let cell_data = type_option.apply_changeset(data, None).unwrap();
|
||||
let data = SelectOptionCellChangeset::from_insert(&option_ids).to_str();
|
||||
let cell_data = type_option.apply_changeset(data.into(), None).unwrap();
|
||||
assert_multi_select_options(
|
||||
cell_data,
|
||||
&type_option,
|
||||
@ -177,24 +174,24 @@ mod tests {
|
||||
vec![google_option.clone(), facebook_option],
|
||||
);
|
||||
|
||||
let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
|
||||
let cell_data = type_option.apply_changeset(data, None).unwrap();
|
||||
let data = SelectOptionCellChangeset::from_insert(&google_option.id).to_str();
|
||||
let cell_data = type_option.apply_changeset(data.into(), None).unwrap();
|
||||
assert_multi_select_options(cell_data, &type_option, &field_rev, vec![google_option]);
|
||||
|
||||
// Invalid option id
|
||||
let cell_data = type_option
|
||||
.apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
|
||||
.apply_changeset(SelectOptionCellChangeset::from_insert("").to_str().into(), None)
|
||||
.unwrap();
|
||||
assert_multi_select_options(cell_data, &type_option, &field_rev, vec![]);
|
||||
|
||||
// Invalid option id
|
||||
let cell_data = type_option
|
||||
.apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None)
|
||||
.apply_changeset(SelectOptionCellChangeset::from_insert("123,456").to_str().into(), None)
|
||||
.unwrap();
|
||||
assert_multi_select_options(cell_data, &type_option, &field_rev, vec![]);
|
||||
|
||||
// Invalid changeset
|
||||
assert!(type_option.apply_changeset("123", None).is_err());
|
||||
assert!(type_option.apply_changeset("123".to_owned().into(), None).is_err());
|
||||
}
|
||||
|
||||
fn assert_multi_select_options(
|
||||
@ -207,7 +204,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
expected,
|
||||
type_option
|
||||
.decode_cell_data(cell_data, &field_type, field_rev)
|
||||
.decode_cell_data(cell_data.into(), &field_type, field_rev)
|
||||
.unwrap()
|
||||
.parse::<SelectOptionCellData>()
|
||||
.unwrap()
|
||||
|
@ -1,12 +1,12 @@
|
||||
use crate::impl_type_option;
|
||||
|
||||
use crate::entities::{FieldType, GridNumberFilter};
|
||||
use crate::services::cell::{
|
||||
AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
|
||||
};
|
||||
use crate::services::field::number_currency::Currency;
|
||||
use crate::services::field::type_options::number_type_option::format::*;
|
||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||
use crate::services::row::{
|
||||
AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
@ -118,32 +118,30 @@ impl CellFilterOperation<GridNumberFilter> for NumberTypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
impl CellDataOperation<String> for NumberTypeOption {
|
||||
fn decode_cell_data<T>(
|
||||
impl CellDataOperation<String, String> for NumberTypeOption {
|
||||
fn decode_cell_data(
|
||||
&self,
|
||||
cell_data: T,
|
||||
cell_data: CellData<String>,
|
||||
decoded_field_type: &FieldType,
|
||||
_field_rev: &FieldRevision,
|
||||
) -> FlowyResult<DecodedCellData>
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
) -> FlowyResult<DecodedCellData> {
|
||||
if decoded_field_type.is_date() {
|
||||
return Ok(DecodedCellData::default());
|
||||
}
|
||||
|
||||
let cell_data = cell_data.into();
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_changeset<C>(&self, changeset: C, _cell_rev: Option<CellRevision>) -> Result<String, FlowyError>
|
||||
where
|
||||
C: Into<CellContentChangeset>,
|
||||
{
|
||||
let changeset = changeset.into();
|
||||
fn apply_changeset(
|
||||
&self,
|
||||
changeset: CellDataChangeset<String>,
|
||||
_cell_rev: Option<CellRevision>,
|
||||
) -> Result<String, FlowyError> {
|
||||
let changeset = changeset.try_into_inner()?;
|
||||
let data = changeset.trim().to_string();
|
||||
let _ = self.format_cell_data(&data)?;
|
||||
Ok(data)
|
||||
@ -254,9 +252,9 @@ impl ToString for NumberCellData {
|
||||
#[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 crate::services::row::CellDataOperation;
|
||||
use flowy_grid_data_model::revision::FieldRevision;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
@ -383,7 +381,7 @@ mod tests {
|
||||
) {
|
||||
assert_eq!(
|
||||
type_option
|
||||
.decode_cell_data(cell_data, field_type, field_rev)
|
||||
.decode_cell_data(cell_data.to_owned().into(), field_type, field_rev)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
expected_str.to_owned()
|
||||
|
@ -1,13 +1,13 @@
|
||||
use crate::entities::{FieldType, GridSelectOptionFilter};
|
||||
use crate::impl_type_option;
|
||||
use crate::services::cell::{
|
||||
AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
|
||||
};
|
||||
use crate::services::field::select_option::{
|
||||
make_selected_select_options, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData,
|
||||
SelectOptionIds, SelectOptionOperation,
|
||||
make_selected_select_options, SelectOption, SelectOptionCellChangeset, SelectOptionCellData, SelectOptionIds,
|
||||
SelectOptionOperation,
|
||||
};
|
||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||
use crate::services::row::{
|
||||
AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
@ -53,27 +53,22 @@ impl CellFilterOperation<GridSelectOptionFilter> for SingleSelectTypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
impl CellDataOperation<String> for SingleSelectTypeOption {
|
||||
fn decode_cell_data<T>(
|
||||
impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for SingleSelectTypeOption {
|
||||
fn decode_cell_data(
|
||||
&self,
|
||||
cell_data: T,
|
||||
cell_data: CellData<SelectOptionIds>,
|
||||
decoded_field_type: &FieldType,
|
||||
_field_rev: &FieldRevision,
|
||||
) -> FlowyResult<DecodedCellData>
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
) -> FlowyResult<DecodedCellData> {
|
||||
if !decoded_field_type.is_select_option() {
|
||||
return Ok(DecodedCellData::default());
|
||||
}
|
||||
|
||||
let encoded_data = cell_data.into();
|
||||
let ids: SelectOptionIds = cell_data.try_into_inner()?;
|
||||
let mut cell_data = SelectOptionCellData {
|
||||
options: self.options.clone(),
|
||||
select_options: vec![],
|
||||
};
|
||||
|
||||
let ids: SelectOptionIds = encoded_data.into();
|
||||
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());
|
||||
@ -83,12 +78,12 @@ impl CellDataOperation<String> for SingleSelectTypeOption {
|
||||
DecodedCellData::try_from_bytes(cell_data)
|
||||
}
|
||||
|
||||
fn apply_changeset<C>(&self, changeset: C, _cell_rev: Option<CellRevision>) -> Result<String, FlowyError>
|
||||
where
|
||||
C: Into<CellContentChangeset>,
|
||||
{
|
||||
let changeset = changeset.into();
|
||||
let select_option_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset)?;
|
||||
fn apply_changeset(
|
||||
&self,
|
||||
changeset: CellDataChangeset<SelectOptionCellChangeset>,
|
||||
_cell_rev: Option<CellRevision>,
|
||||
) -> Result<String, FlowyError> {
|
||||
let select_option_changeset = changeset.try_into_inner()?;
|
||||
let new_cell_data: String;
|
||||
if let Some(insert_option_id) = select_option_changeset.insert_option_id {
|
||||
tracing::trace!("Insert single select option: {}", &insert_option_id);
|
||||
@ -127,10 +122,10 @@ impl TypeOptionBuilder for SingleSelectTypeOptionBuilder {
|
||||
#[cfg(test)]
|
||||
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 crate::services::row::CellDataOperation;
|
||||
use flowy_grid_data_model::revision::FieldRevision;
|
||||
|
||||
#[test]
|
||||
@ -151,29 +146,29 @@ mod tests {
|
||||
let type_option = SingleSelectTypeOption::from(&field_rev);
|
||||
|
||||
let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR);
|
||||
let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
|
||||
let cell_data = type_option.apply_changeset(data, None).unwrap();
|
||||
let data = SelectOptionCellChangeset::from_insert(&option_ids).to_str();
|
||||
let cell_data = type_option.apply_changeset(data.into(), None).unwrap();
|
||||
assert_single_select_options(cell_data, &type_option, &field_rev, vec![google_option.clone()]);
|
||||
|
||||
let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
|
||||
let cell_data = type_option.apply_changeset(data, None).unwrap();
|
||||
let data = SelectOptionCellChangeset::from_insert(&google_option.id).to_str();
|
||||
let cell_data = type_option.apply_changeset(data.into(), None).unwrap();
|
||||
assert_single_select_options(cell_data, &type_option, &field_rev, vec![google_option]);
|
||||
|
||||
// Invalid option id
|
||||
let cell_data = type_option
|
||||
.apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
|
||||
.apply_changeset(SelectOptionCellChangeset::from_insert("").to_str().into(), None)
|
||||
.unwrap();
|
||||
assert_single_select_options(cell_data, &type_option, &field_rev, vec![]);
|
||||
|
||||
// Invalid option id
|
||||
let cell_data = type_option
|
||||
.apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None)
|
||||
.apply_changeset(SelectOptionCellChangeset::from_insert("123").to_str().into(), None)
|
||||
.unwrap();
|
||||
|
||||
assert_single_select_options(cell_data, &type_option, &field_rev, vec![]);
|
||||
|
||||
// Invalid changeset
|
||||
assert!(type_option.apply_changeset("123", None).is_err());
|
||||
assert!(type_option.apply_changeset("123".to_owned().into(), None).is_err());
|
||||
}
|
||||
|
||||
fn assert_single_select_options(
|
||||
@ -186,7 +181,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
expected,
|
||||
type_option
|
||||
.decode_cell_data(cell_data, &field_type, field_rev)
|
||||
.decode_cell_data(cell_data.into(), &field_type, field_rev)
|
||||
.unwrap()
|
||||
.parse::<SelectOptionCellData>()
|
||||
.unwrap()
|
||||
|
@ -1,9 +1,10 @@
|
||||
use crate::entities::{FieldType, GridTextFilter};
|
||||
use crate::impl_type_option;
|
||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||
use crate::services::row::{
|
||||
try_decode_cell_data, AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
|
||||
use crate::services::cell::{
|
||||
try_decode_cell_data, AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation,
|
||||
DecodedCellData,
|
||||
};
|
||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
@ -43,37 +44,35 @@ impl CellFilterOperation<GridTextFilter> for RichTextTypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
impl CellDataOperation<String> for RichTextTypeOption {
|
||||
fn decode_cell_data<T>(
|
||||
impl CellDataOperation<String, String> for RichTextTypeOption {
|
||||
fn decode_cell_data(
|
||||
&self,
|
||||
cell_data: T,
|
||||
cell_data: CellData<String>,
|
||||
decoded_field_type: &FieldType,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<DecodedCellData>
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
) -> FlowyResult<DecodedCellData> {
|
||||
if decoded_field_type.is_date()
|
||||
|| decoded_field_type.is_single_select()
|
||||
|| decoded_field_type.is_multi_select()
|
||||
|| decoded_field_type.is_number()
|
||||
{
|
||||
try_decode_cell_data(cell_data.into(), field_rev, decoded_field_type, decoded_field_type)
|
||||
try_decode_cell_data(cell_data, field_rev, decoded_field_type, decoded_field_type)
|
||||
} else {
|
||||
let cell_data = cell_data.into();
|
||||
let cell_data: String = cell_data.try_into_inner()?;
|
||||
Ok(DecodedCellData::new(cell_data))
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_changeset<C>(&self, changeset: C, _cell_rev: Option<CellRevision>) -> Result<String, FlowyError>
|
||||
where
|
||||
C: Into<CellContentChangeset>,
|
||||
{
|
||||
let data = changeset.into();
|
||||
fn apply_changeset(
|
||||
&self,
|
||||
changeset: CellDataChangeset<String>,
|
||||
_cell_rev: Option<CellRevision>,
|
||||
) -> Result<String, FlowyError> {
|
||||
let data = changeset.try_into_inner()?;
|
||||
if data.len() > 10000 {
|
||||
Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000"))
|
||||
} else {
|
||||
Ok(data.0)
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,10 +95,10 @@ impl std::convert::TryFrom<AnyCellData> for TextCellData {
|
||||
#[cfg(test)]
|
||||
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::*;
|
||||
use crate::services::row::CellDataOperation;
|
||||
|
||||
#[test]
|
||||
fn text_description_test() {
|
||||
@ -111,7 +110,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
type_option
|
||||
.decode_cell_data(1647251762.to_string(), &field_type, &date_time_field_rev)
|
||||
.decode_cell_data(1647251762.to_string().into(), &field_type, &date_time_field_rev)
|
||||
.unwrap()
|
||||
.parse::<DateCellData>()
|
||||
.unwrap()
|
||||
@ -127,7 +126,11 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
type_option
|
||||
.decode_cell_data(done_option_id, &FieldType::SingleSelect, &single_select_field_rev)
|
||||
.decode_cell_data(
|
||||
done_option_id.into(),
|
||||
&FieldType::SingleSelect,
|
||||
&single_select_field_rev
|
||||
)
|
||||
.unwrap()
|
||||
.parse::<SelectOptionCellData>()
|
||||
.unwrap()
|
||||
@ -139,16 +142,18 @@ mod tests {
|
||||
let google_option = SelectOption::new("Google");
|
||||
let facebook_option = SelectOption::new("Facebook");
|
||||
let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
|
||||
let cell_data_changeset = SelectOptionCellContentChangeset::from_insert(&ids).to_str();
|
||||
let cell_data_changeset = SelectOptionCellChangeset::from_insert(&ids).to_str();
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.option(google_option.clone())
|
||||
.option(facebook_option.clone());
|
||||
let multi_select_field_rev = FieldBuilder::new(multi_select).build();
|
||||
let multi_type_option = MultiSelectTypeOption::from(&multi_select_field_rev);
|
||||
let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap();
|
||||
let cell_data = multi_type_option
|
||||
.apply_changeset(cell_data_changeset.into(), None)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
type_option
|
||||
.decode_cell_data(cell_data, &FieldType::MultiSelect, &multi_select_field_rev)
|
||||
.decode_cell_data(cell_data.into(), &FieldType::MultiSelect, &multi_select_field_rev)
|
||||
.unwrap()
|
||||
.parse::<SelectOptionCellData>()
|
||||
.unwrap()
|
||||
@ -161,7 +166,7 @@ mod tests {
|
||||
let number_field_rev = FieldBuilder::new(number).build();
|
||||
assert_eq!(
|
||||
type_option
|
||||
.decode_cell_data("18443".to_owned(), &FieldType::Number, &number_field_rev)
|
||||
.decode_cell_data("18443".to_owned().into(), &FieldType::Number, &number_field_rev)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"$18,443".to_owned()
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::entities::{FieldType, GridTextFilter};
|
||||
use crate::impl_type_option;
|
||||
use crate::services::field::{BoxTypeOptionBuilder, TextCellData, TypeOptionBuilder};
|
||||
use crate::services::row::{
|
||||
AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData, EncodedCellData,
|
||||
use crate::services::cell::{
|
||||
AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData, FromCellString,
|
||||
};
|
||||
use crate::services::field::{BoxTypeOptionBuilder, TextCellData, TypeOptionBuilder};
|
||||
use bytes::Bytes;
|
||||
use fancy_regex::Regex;
|
||||
use flowy_derive::ProtoBuf;
|
||||
@ -11,7 +11,6 @@ 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};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct URLTypeOptionBuilder(URLTypeOption);
|
||||
@ -46,35 +45,33 @@ impl CellFilterOperation<GridTextFilter> for URLTypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
impl CellDataOperation<EncodedCellData<URLCellData>> for URLTypeOption {
|
||||
fn decode_cell_data<T>(
|
||||
impl CellDataOperation<URLCellData, String> for URLTypeOption {
|
||||
fn decode_cell_data(
|
||||
&self,
|
||||
cell_data: T,
|
||||
cell_data: CellData<URLCellData>,
|
||||
decoded_field_type: &FieldType,
|
||||
_field_rev: &FieldRevision,
|
||||
) -> FlowyResult<DecodedCellData>
|
||||
where
|
||||
T: Into<EncodedCellData<URLCellData>>,
|
||||
{
|
||||
) -> FlowyResult<DecodedCellData> {
|
||||
if !decoded_field_type.is_url() {
|
||||
return Ok(DecodedCellData::default());
|
||||
}
|
||||
let cell_data = cell_data.into().try_into_inner()?;
|
||||
let cell_data: URLCellData = cell_data.try_into_inner()?;
|
||||
DecodedCellData::try_from_bytes(cell_data)
|
||||
}
|
||||
|
||||
fn apply_changeset<C>(&self, changeset: C, _cell_rev: Option<CellRevision>) -> Result<String, FlowyError>
|
||||
where
|
||||
C: Into<CellContentChangeset>,
|
||||
{
|
||||
let changeset = changeset.into();
|
||||
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_string(),
|
||||
content: changeset,
|
||||
}
|
||||
.to_json()
|
||||
}
|
||||
@ -118,25 +115,17 @@ impl URLCellData {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for URLCellData {
|
||||
type Err = FlowyError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
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::From<AnyCellData> for URLCellData {
|
||||
// fn from(any_cell_data: AnyCellData) -> Self {
|
||||
// URLCellData::from_str(&any_cell_data.cell_data).unwrap_or_default()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl std::convert::TryFrom<AnyCellData> for URLCellData {
|
||||
type Error = ();
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(_value: AnyCellData) -> Result<Self, Self::Error> {
|
||||
todo!()
|
||||
fn try_from(data: AnyCellData) -> Result<Self, Self::Error> {
|
||||
serde_json::from_str::<URLCellData>(&data.cell_data).map_err(internal_error)
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,9 +139,9 @@ lazy_static! {
|
||||
#[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 crate::services::row::{CellDataOperation, EncodedCellData};
|
||||
use flowy_grid_data_model::revision::FieldRevision;
|
||||
|
||||
#[test]
|
||||
@ -195,20 +184,20 @@ mod tests {
|
||||
expected: &str,
|
||||
expected_url: &str,
|
||||
) {
|
||||
let encoded_data = type_option.apply_changeset(cell_data, None).unwrap();
|
||||
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<EncodedCellData<URLCellData>>>(
|
||||
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, field_type, field_rev)
|
||||
.decode_cell_data(encoded_data.into(), field_type, field_rev)
|
||||
.unwrap()
|
||||
.parse::<URLCellData>()
|
||||
.unwrap()
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::services::row::AnyCellData;
|
||||
use crate::services::cell::AnyCellData;
|
||||
use flowy_grid_data_model::revision::CellRevision;
|
||||
use std::str::FromStr;
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::dart_notification::{send_dart_notification, GridNotification};
|
||||
use crate::entities::{FieldType, GridBlockChangeset};
|
||||
use crate::services::block_manager::GridBlockManager;
|
||||
use crate::services::cell::{AnyCellData, CellFilterOperation};
|
||||
use crate::services::field::{
|
||||
CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RichTextTypeOption,
|
||||
SingleSelectTypeOption, URLTypeOption,
|
||||
@ -9,15 +10,13 @@ use crate::services::filter::filter_cache::{
|
||||
reload_filter_cache, FilterCache, FilterId, FilterResult, FilterResultCache,
|
||||
};
|
||||
use crate::services::grid_editor_task::GridServiceTaskScheduler;
|
||||
use crate::services::row::{AnyCellData, CellFilterOperation, GridBlockSnapshot};
|
||||
use crate::services::row::GridBlockSnapshot;
|
||||
use crate::services::tasks::{FilterTaskContext, Task, TaskContent};
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_grid_data_model::revision::{CellRevision, FieldId, FieldRevision, RowRevision};
|
||||
use flowy_sync::client_grid::GridRevisionPad;
|
||||
use flowy_sync::entities::grid::GridSettingChangesetParams;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
@ -1,13 +1,16 @@
|
||||
use crate::dart_notification::{send_dart_notification, GridNotification};
|
||||
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::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::*;
|
||||
|
||||
use crate::entities::*;
|
||||
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,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::revision::*;
|
||||
@ -369,7 +372,7 @@ impl GridRevisionEditor {
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub async fn update_cell(&self, cell_changeset: CellChangeset) -> FlowyResult<()> {
|
||||
if cell_changeset.cell_content_changeset.as_ref().is_none() {
|
||||
if cell_changeset.content.as_ref().is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -377,7 +380,7 @@ impl GridRevisionEditor {
|
||||
grid_id,
|
||||
row_id,
|
||||
field_id,
|
||||
mut cell_content_changeset,
|
||||
mut content,
|
||||
} = cell_changeset;
|
||||
|
||||
match self.grid_pad.read().await.get_field_rev(&field_id) {
|
||||
@ -386,21 +389,17 @@ impl GridRevisionEditor {
|
||||
Err(FlowyError::internal().context(msg))
|
||||
}
|
||||
Some((_, field_rev)) => {
|
||||
tracing::trace!("field changeset: id:{} / value:{:?}", &field_id, cell_content_changeset);
|
||||
tracing::trace!("field changeset: id:{} / value:{:?}", &field_id, content);
|
||||
|
||||
let cell_rev = self.get_cell_rev(&row_id, &field_id).await?;
|
||||
// Update the changeset.data property with the return value.
|
||||
cell_content_changeset = Some(apply_cell_data_changeset(
|
||||
cell_content_changeset.unwrap(),
|
||||
cell_rev,
|
||||
field_rev,
|
||||
)?);
|
||||
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,
|
||||
field_id,
|
||||
cell_content_changeset,
|
||||
content,
|
||||
};
|
||||
let _ = self
|
||||
.block_manager
|
||||
|
@ -2,6 +2,7 @@ mod util;
|
||||
|
||||
mod block_manager;
|
||||
pub mod block_revision_editor;
|
||||
pub mod cell;
|
||||
pub mod field;
|
||||
mod filter;
|
||||
pub mod grid_editor;
|
||||
|
@ -1,305 +0,0 @@
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::field::*;
|
||||
use bytes::Bytes;
|
||||
use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, FieldTypeRevision};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Formatter;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub trait CellFilterOperation<T> {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &T) -> FlowyResult<bool>;
|
||||
}
|
||||
|
||||
pub trait CellDataOperation<D> {
|
||||
fn decode_cell_data<T>(
|
||||
&self,
|
||||
cell_data: T,
|
||||
decoded_field_type: &FieldType,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<DecodedCellData>
|
||||
where
|
||||
T: Into<D>;
|
||||
|
||||
fn apply_changeset<C: Into<CellContentChangeset>>(
|
||||
&self,
|
||||
changeset: C,
|
||||
cell_rev: Option<CellRevision>,
|
||||
) -> FlowyResult<String>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CellContentChangeset(pub String);
|
||||
|
||||
impl std::fmt::Display for CellContentChangeset {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> std::convert::From<T> for CellContentChangeset {
|
||||
fn from(s: T) -> Self {
|
||||
let s = s.as_ref().to_owned();
|
||||
CellContentChangeset(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for CellContentChangeset {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AnyCellData {
|
||||
pub cell_data: String,
|
||||
pub field_type: FieldType,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for AnyCellData {
|
||||
type Err = FlowyError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let type_option_cell_data: AnyCellData = serde_json::from_str(s)?;
|
||||
Ok(type_option_cell_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryInto<AnyCellData> for String {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_into(self) -> Result<AnyCellData, Self::Error> {
|
||||
AnyCellData::from_str(&self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<&CellRevision> for AnyCellData {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(value: &CellRevision) -> Result<Self, Self::Error> {
|
||||
Self::from_str(&value.data)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<&Option<CellRevision>> for AnyCellData {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(value: &Option<CellRevision>) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
None => Err(FlowyError::invalid_data().context("Expected CellRevision, but receive None")),
|
||||
Some(cell_rev) => AnyCellData::try_from(cell_rev),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<Option<CellRevision>> for AnyCellData {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(value: Option<CellRevision>) -> Result<Self, Self::Error> {
|
||||
Self::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyCellData {
|
||||
pub fn new(content: String, field_type: FieldType) -> Self {
|
||||
AnyCellData {
|
||||
cell_data: content,
|
||||
field_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn json(&self) -> String {
|
||||
serde_json::to_string(self).unwrap_or_else(|_| "".to_owned())
|
||||
}
|
||||
|
||||
pub fn is_number(&self) -> bool {
|
||||
self.field_type == FieldType::Number
|
||||
}
|
||||
|
||||
pub fn is_text(&self) -> bool {
|
||||
self.field_type == FieldType::RichText
|
||||
}
|
||||
|
||||
pub fn is_checkbox(&self) -> bool {
|
||||
self.field_type == FieldType::Checkbox
|
||||
}
|
||||
|
||||
pub fn is_date(&self) -> bool {
|
||||
self.field_type == FieldType::DateTime
|
||||
}
|
||||
|
||||
pub fn is_single_select(&self) -> bool {
|
||||
self.field_type == FieldType::SingleSelect
|
||||
}
|
||||
|
||||
pub fn is_multi_select(&self) -> bool {
|
||||
self.field_type == FieldType::MultiSelect
|
||||
}
|
||||
|
||||
pub fn is_url(&self) -> bool {
|
||||
self.field_type == FieldType::URL
|
||||
}
|
||||
|
||||
pub fn is_select_option(&self) -> bool {
|
||||
self.field_type == FieldType::MultiSelect || self.field_type == FieldType::SingleSelect
|
||||
}
|
||||
}
|
||||
|
||||
/// The changeset will be deserialized into specific data base on the FieldType.
|
||||
/// For example, it's String on FieldType::RichText, and SelectOptionChangeset on FieldType::SingleSelect
|
||||
pub fn apply_cell_data_changeset<C: Into<CellContentChangeset>, T: AsRef<FieldRevision>>(
|
||||
changeset: C,
|
||||
cell_rev: Option<CellRevision>,
|
||||
field_rev: T,
|
||||
) -> Result<String, FlowyError> {
|
||||
let field_rev = field_rev.as_ref();
|
||||
let field_type = field_rev.field_type_rev.into();
|
||||
let s = match field_type {
|
||||
FieldType::RichText => RichTextTypeOption::from(field_rev).apply_changeset(changeset, cell_rev),
|
||||
FieldType::Number => NumberTypeOption::from(field_rev).apply_changeset(changeset, cell_rev),
|
||||
FieldType::DateTime => DateTypeOption::from(field_rev).apply_changeset(changeset, cell_rev),
|
||||
FieldType::SingleSelect => SingleSelectTypeOption::from(field_rev).apply_changeset(changeset, cell_rev),
|
||||
FieldType::MultiSelect => MultiSelectTypeOption::from(field_rev).apply_changeset(changeset, cell_rev),
|
||||
FieldType::Checkbox => CheckboxTypeOption::from(field_rev).apply_changeset(changeset, cell_rev),
|
||||
FieldType::URL => URLTypeOption::from(field_rev).apply_changeset(changeset, cell_rev),
|
||||
}?;
|
||||
|
||||
Ok(AnyCellData::new(s, field_type).json())
|
||||
}
|
||||
|
||||
pub fn decode_any_cell_data<T: TryInto<AnyCellData>>(data: T, field_rev: &FieldRevision) -> DecodedCellData {
|
||||
if let Ok(any_cell_data) = data.try_into() {
|
||||
let AnyCellData { cell_data, field_type } = any_cell_data;
|
||||
let to_field_type = field_rev.field_type_rev.into();
|
||||
match try_decode_cell_data(cell_data, field_rev, &field_type, &to_field_type) {
|
||||
Ok(cell_data) => cell_data,
|
||||
Err(e) => {
|
||||
tracing::error!("Decode cell data failed, {:?}", e);
|
||||
DecodedCellData::default()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::error!("Decode type option data failed");
|
||||
DecodedCellData::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_decode_cell_data(
|
||||
cell_data: String,
|
||||
field_rev: &FieldRevision,
|
||||
s_field_type: &FieldType,
|
||||
t_field_type: &FieldType,
|
||||
) -> FlowyResult<DecodedCellData> {
|
||||
let get_cell_data = || {
|
||||
let field_type: FieldTypeRevision = t_field_type.into();
|
||||
let data = match t_field_type {
|
||||
FieldType::RichText => field_rev
|
||||
.get_type_option_entry::<RichTextTypeOption>(field_type)?
|
||||
.decode_cell_data(cell_data, s_field_type, field_rev),
|
||||
FieldType::Number => field_rev
|
||||
.get_type_option_entry::<NumberTypeOption>(field_type)?
|
||||
.decode_cell_data(cell_data, s_field_type, field_rev),
|
||||
FieldType::DateTime => field_rev
|
||||
.get_type_option_entry::<DateTypeOption>(field_type)?
|
||||
.decode_cell_data(cell_data, s_field_type, field_rev),
|
||||
FieldType::SingleSelect => field_rev
|
||||
.get_type_option_entry::<SingleSelectTypeOption>(field_type)?
|
||||
.decode_cell_data(cell_data, s_field_type, field_rev),
|
||||
FieldType::MultiSelect => field_rev
|
||||
.get_type_option_entry::<MultiSelectTypeOption>(field_type)?
|
||||
.decode_cell_data(cell_data, s_field_type, field_rev),
|
||||
FieldType::Checkbox => field_rev
|
||||
.get_type_option_entry::<CheckboxTypeOption>(field_type)?
|
||||
.decode_cell_data(cell_data, s_field_type, field_rev),
|
||||
FieldType::URL => field_rev
|
||||
.get_type_option_entry::<URLTypeOption>(field_type)?
|
||||
.decode_cell_data(cell_data, s_field_type, field_rev),
|
||||
};
|
||||
Some(data)
|
||||
};
|
||||
|
||||
match get_cell_data() {
|
||||
Some(Ok(data)) => Ok(data),
|
||||
Some(Err(err)) => {
|
||||
tracing::error!("{:?}", err);
|
||||
Ok(DecodedCellData::default())
|
||||
}
|
||||
None => Ok(DecodedCellData::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct EncodedCellData<T>(pub Option<T>);
|
||||
|
||||
impl<T> EncodedCellData<T> {
|
||||
pub fn try_into_inner(self) -> FlowyResult<T> {
|
||||
match self.0 {
|
||||
None => Err(ErrorCode::InvalidData.into()),
|
||||
Some(data) => Ok(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::convert::From<String> for EncodedCellData<T>
|
||||
where
|
||||
T: FromStr<Err = FlowyError>,
|
||||
{
|
||||
fn from(s: String) -> Self {
|
||||
match T::from_str(&s) {
|
||||
Ok(inner) => EncodedCellData(Some(inner)),
|
||||
Err(e) => {
|
||||
tracing::error!("Deserialize Cell Data failed: {}", e);
|
||||
EncodedCellData(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The data is encoded by protobuf or utf8. You should choose the corresponding decode struct to parse it.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// * Use DateCellData to parse the data when the FieldType is Date.
|
||||
/// * Use URLCellData to parse the data when the FieldType is URL.
|
||||
/// * 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>,
|
||||
}
|
||||
|
||||
impl DecodedCellData {
|
||||
pub fn new<T: AsRef<[u8]>>(data: T) -> Self {
|
||||
Self {
|
||||
data: data.as_ref().to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_bytes<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() })
|
||||
}
|
||||
|
||||
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.data.as_ref()).map_err(internal_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for DecodedCellData {
|
||||
fn to_string(&self) -> String {
|
||||
match String::from_utf8(self.data.clone()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
tracing::error!("DecodedCellData to string failed: {:?}", e);
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
mod cell_data_operation;
|
||||
mod row_builder;
|
||||
mod row_loader;
|
||||
|
||||
pub use cell_data_operation::*;
|
||||
pub use row_builder::*;
|
||||
pub(crate) use row_loader::*;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::services::field::select_option::SelectOptionCellContentChangeset;
|
||||
use crate::services::row::apply_cell_data_changeset;
|
||||
use crate::services::cell::apply_cell_data_changeset;
|
||||
use crate::services::field::select_option::SelectOptionCellChangeset;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::revision::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT};
|
||||
use indexmap::IndexMap;
|
||||
@ -35,7 +35,7 @@ impl<'a> CreateRowRevisionBuilder<'a> {
|
||||
Err(FlowyError::internal().context(msg))
|
||||
}
|
||||
Some(field_rev) => {
|
||||
let data = apply_cell_data_changeset(&data, None, field_rev)?;
|
||||
let data = apply_cell_data_changeset(data, None, field_rev)?;
|
||||
let cell = CellRevision::new(data);
|
||||
self.payload.cell_by_field_id.insert(field_id.to_owned(), cell);
|
||||
Ok(())
|
||||
@ -50,8 +50,8 @@ impl<'a> CreateRowRevisionBuilder<'a> {
|
||||
Err(FlowyError::internal().context(msg))
|
||||
}
|
||||
Some(field_rev) => {
|
||||
let cell_data = SelectOptionCellContentChangeset::from_insert(&data).to_str();
|
||||
let data = apply_cell_data_changeset(&cell_data, None, field_rev)?;
|
||||
let cell_data = SelectOptionCellChangeset::from_insert(&data).to_str();
|
||||
let data = apply_cell_data_changeset(cell_data, None, field_rev)?;
|
||||
let cell = CellRevision::new(data);
|
||||
self.payload.cell_by_field_id.insert(field_id.to_owned(), cell);
|
||||
Ok(())
|
||||
|
@ -2,7 +2,7 @@ use crate::grid::field_util::make_date_cell_string;
|
||||
use crate::grid::script::EditorScript::*;
|
||||
use crate::grid::script::*;
|
||||
use flowy_grid::entities::{CellChangeset, FieldType};
|
||||
use flowy_grid::services::field::select_option::SelectOptionCellContentChangeset;
|
||||
use flowy_grid::services::field::select_option::SelectOptionCellChangeset;
|
||||
use flowy_grid::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
|
||||
|
||||
#[tokio::test]
|
||||
@ -25,11 +25,11 @@ async fn grid_cell_update() {
|
||||
FieldType::DateTime => make_date_cell_string("123"),
|
||||
FieldType::SingleSelect => {
|
||||
let type_option = SingleSelectTypeOption::from(field_rev);
|
||||
SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str()
|
||||
SelectOptionCellChangeset::from_insert(&type_option.options.first().unwrap().id).to_str()
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
let type_option = MultiSelectTypeOption::from(field_rev);
|
||||
SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str()
|
||||
SelectOptionCellChangeset::from_insert(&type_option.options.first().unwrap().id).to_str()
|
||||
}
|
||||
FieldType::Checkbox => "1".to_string(),
|
||||
FieldType::URL => "1".to_string(),
|
||||
@ -40,7 +40,7 @@ async fn grid_cell_update() {
|
||||
grid_id: block_id.to_string(),
|
||||
row_id: row_rev.id.clone(),
|
||||
field_id: field_rev.id.clone(),
|
||||
cell_content_changeset: Some(data),
|
||||
content: Some(data),
|
||||
},
|
||||
is_err: false,
|
||||
});
|
||||
|
@ -73,7 +73,7 @@ pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldRev
|
||||
// The grid will contains all existing field types and there are three empty rows in this grid.
|
||||
|
||||
pub fn make_date_cell_string(s: &str) -> String {
|
||||
serde_json::to_string(&DateCellContentChangeset {
|
||||
serde_json::to_string(&DateCellChangeset {
|
||||
date: Some(s.to_string()),
|
||||
time: None,
|
||||
})
|
||||
|
@ -4,9 +4,10 @@ use crate::grid::script::EditorScript::*;
|
||||
use crate::grid::script::*;
|
||||
use chrono::NaiveDateTime;
|
||||
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 flowy_grid::services::row::{decode_any_cell_data, CreateRowRevisionBuilder};
|
||||
use flowy_grid::services::row::CreateRowRevisionBuilder;
|
||||
use flowy_grid_data_model::revision::RowMetaChangeset;
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::grid::script::GridEditorTest;
|
||||
use flowy_grid::entities::FieldType;
|
||||
use flowy_grid::services::field::DateCellContentChangeset;
|
||||
use flowy_grid::services::field::DateCellChangeset;
|
||||
use flowy_grid::services::row::{CreateRowRevisionBuilder, CreateRowRevisionPayload};
|
||||
use flowy_grid_data_model::revision::FieldRevision;
|
||||
use strum::EnumCount;
|
||||
@ -33,7 +33,7 @@ impl<'a> GridRowTestBuilder<'a> {
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn update_date_cell(mut self, value: i64) -> Self {
|
||||
let value = serde_json::to_string(&DateCellContentChangeset {
|
||||
let value = serde_json::to_string(&DateCellChangeset {
|
||||
date: Some(value.to_string()),
|
||||
time: None,
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user