diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart index 25c51e9c07..5d508cdef5 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart @@ -9,7 +9,7 @@ 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/field_entities.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.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:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart index c6393e4831..a6e140f707 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart index 87eabdf759..9ce003b154 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart index 806389d67c..f3458454d1 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart @@ -2,8 +2,8 @@ import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'cell_service/cell_service.dart'; class SelectOptionService { diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/edit_select_option_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/edit_select_option_bloc.dart index 2854fcf199..f0969e2793 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/edit_select_option_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/edit_select_option_bloc.dart @@ -1,4 +1,4 @@ -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart index bb72277f0b..0a6e2de14c 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart @@ -1,6 +1,7 @@ import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'dart:async'; import 'package:protobuf/protobuf.dart'; import 'select_option_type_option_bloc.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart index d4290cd8ff..ff8244546b 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart @@ -1,4 +1,4 @@ -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart index 91d8de7a28..c09d17f11e 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart @@ -1,6 +1,7 @@ import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart'; import 'dart:async'; import 'package:protobuf/protobuf.dart'; import 'select_option_type_option_bloc.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart index c6abc71cbb..3a30ef30ca 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart @@ -6,7 +6,7 @@ import 'package:flowy_sdk/dispatch/dispatch.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/field_entities.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:protobuf/protobuf.dart'; class TypeOptionService { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart index 27e36ad46d..90a6503079 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart @@ -1,7 +1,7 @@ import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart index e878ac2c58..63f7e0fc28 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart @@ -5,7 +5,7 @@ import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; // ignore: unused_import import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart index 51c550ccb2..76d9a379d7 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart @@ -10,8 +10,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart index 398d98a994..9bffd4554f 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart @@ -2,7 +2,7 @@ import 'dart:collection'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart index b308995dd1..60c1bfcdd6 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart @@ -8,7 +8,7 @@ import 'package:flowy_infra/theme.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/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart index cec32bd99e..3abb0445c7 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart @@ -8,7 +8,7 @@ import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.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/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; diff --git a/frontend/rust-lib/flowy-grid/Flowy.toml b/frontend/rust-lib/flowy-grid/Flowy.toml index 00e9cf7c04..94bd0c0225 100644 --- a/frontend/rust-lib/flowy-grid/Flowy.toml +++ b/frontend/rust-lib/flowy-grid/Flowy.toml @@ -2,6 +2,7 @@ proto_input = [ "src/event_map.rs", "src/services/field/type_options", + "src/services/field/select_option.rs", "src/entities", "src/dart_notification.rs" ] diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities.rs deleted file mode 100644 index 747c7787a8..0000000000 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities.rs +++ /dev/null @@ -1,393 +0,0 @@ -use crate::entities::FieldType; -use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_error::ErrorCode; -use flowy_grid_data_model::parser::NotEmptyStr; -use flowy_grid_data_model::revision::{FieldRevision, GridFilterRevision}; -use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams}; -use std::convert::TryInto; -use std::sync::Arc; - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridFilter { - #[pb(index = 1)] - pub id: String, -} - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct RepeatedGridFilter { - #[pb(index = 1)] - pub items: Vec, -} - -impl std::convert::From<&Arc> for GridFilter { - fn from(rev: &Arc) -> Self { - Self { id: rev.id.clone() } - } -} - -impl std::convert::From<&Vec>> for RepeatedGridFilter { - fn from(revs: &Vec>) -> Self { - RepeatedGridFilter { - items: revs.iter().map(|rev| rev.into()).collect(), - } - } -} - -impl std::convert::From> for RepeatedGridFilter { - fn from(items: Vec) -> Self { - Self { items } - } -} - -#[derive(ProtoBuf, Debug, Default, Clone)] -pub struct DeleteFilterPayload { - #[pb(index = 1)] - pub filter_id: String, - - #[pb(index = 2)] - pub field_type: FieldType, -} - -impl TryInto for DeleteFilterPayload { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let filter_id = NotEmptyStr::parse(self.filter_id) - .map_err(|_| ErrorCode::UnexpectedEmptyString)? - .0; - Ok(DeleteFilterParams { - filter_id, - field_type_rev: self.field_type.into(), - }) - } -} - -#[derive(ProtoBuf, Debug, Default, Clone)] -pub struct CreateGridFilterPayload { - #[pb(index = 1)] - pub field_id: String, - - #[pb(index = 2)] - pub field_type: FieldType, - - #[pb(index = 3)] - pub condition: i32, - - #[pb(index = 4, one_of)] - pub content: Option, -} - -impl CreateGridFilterPayload { - #[allow(dead_code)] - pub fn new>(field_rev: &FieldRevision, condition: T, content: Option) -> Self { - Self { - field_id: field_rev.id.clone(), - field_type: field_rev.field_type_rev.into(), - condition: condition.into(), - content, - } - } -} - -impl TryInto for CreateGridFilterPayload { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; - let condition = self.condition as u8; - match self.field_type { - FieldType::RichText | FieldType::URL => { - let _ = TextFilterCondition::try_from(condition)?; - } - FieldType::Checkbox => { - let _ = CheckboxCondition::try_from(condition)?; - } - FieldType::Number => { - let _ = NumberFilterCondition::try_from(condition)?; - } - FieldType::DateTime => { - let _ = DateFilterCondition::try_from(condition)?; - } - FieldType::SingleSelect | FieldType::MultiSelect => { - let _ = SelectOptionCondition::try_from(condition)?; - } - } - - Ok(CreateGridFilterParams { - field_id, - field_type_rev: self.field_type.into(), - condition, - content: self.content, - }) - } -} - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridTextFilter { - #[pb(index = 1)] - pub condition: TextFilterCondition, - - #[pb(index = 2, one_of)] - pub content: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] -#[repr(u8)] -pub enum TextFilterCondition { - Is = 0, - IsNot = 1, - Contains = 2, - DoesNotContain = 3, - StartsWith = 4, - EndsWith = 5, - TextIsEmpty = 6, - TextIsNotEmpty = 7, -} -impl std::convert::From for i32 { - fn from(value: TextFilterCondition) -> Self { - value as i32 - } -} - -impl std::default::Default for TextFilterCondition { - fn default() -> Self { - TextFilterCondition::Is - } -} -impl std::convert::TryFrom for TextFilterCondition { - type Error = ErrorCode; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(TextFilterCondition::Is), - 1 => Ok(TextFilterCondition::IsNot), - 2 => Ok(TextFilterCondition::Contains), - 3 => Ok(TextFilterCondition::DoesNotContain), - 4 => Ok(TextFilterCondition::StartsWith), - 5 => Ok(TextFilterCondition::EndsWith), - 6 => Ok(TextFilterCondition::TextIsEmpty), - 7 => Ok(TextFilterCondition::TextIsNotEmpty), - _ => Err(ErrorCode::InvalidData), - } - } -} - -impl std::convert::From> for GridTextFilter { - fn from(rev: Arc) -> Self { - GridTextFilter { - condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is), - content: rev.content.clone(), - } - } -} - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridNumberFilter { - #[pb(index = 1)] - pub condition: NumberFilterCondition, - - #[pb(index = 2, one_of)] - pub content: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] -#[repr(u8)] -pub enum NumberFilterCondition { - Equal = 0, - NotEqual = 1, - GreaterThan = 2, - LessThan = 3, - GreaterThanOrEqualTo = 4, - LessThanOrEqualTo = 5, - NumberIsEmpty = 6, - NumberIsNotEmpty = 7, -} -impl std::default::Default for NumberFilterCondition { - fn default() -> Self { - NumberFilterCondition::Equal - } -} - -impl std::convert::From for i32 { - fn from(value: NumberFilterCondition) -> Self { - value as i32 - } -} -impl std::convert::TryFrom for NumberFilterCondition { - type Error = ErrorCode; - - fn try_from(n: u8) -> Result { - match n { - 0 => Ok(NumberFilterCondition::Equal), - 1 => Ok(NumberFilterCondition::NotEqual), - 2 => Ok(NumberFilterCondition::GreaterThan), - 3 => Ok(NumberFilterCondition::LessThan), - 4 => Ok(NumberFilterCondition::GreaterThanOrEqualTo), - 5 => Ok(NumberFilterCondition::LessThanOrEqualTo), - 6 => Ok(NumberFilterCondition::NumberIsEmpty), - 7 => Ok(NumberFilterCondition::NumberIsNotEmpty), - _ => Err(ErrorCode::InvalidData), - } - } -} - -impl std::convert::From> for GridNumberFilter { - fn from(rev: Arc) -> Self { - GridNumberFilter { - condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal), - content: rev.content.clone(), - } - } -} - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridSelectOptionFilter { - #[pb(index = 1)] - pub condition: SelectOptionCondition, - - #[pb(index = 2, one_of)] - pub content: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] -#[repr(u8)] -pub enum SelectOptionCondition { - OptionIs = 0, - OptionIsNot = 1, - OptionIsEmpty = 2, - OptionIsNotEmpty = 3, -} - -impl std::convert::From for i32 { - fn from(value: SelectOptionCondition) -> Self { - value as i32 - } -} - -impl std::default::Default for SelectOptionCondition { - fn default() -> Self { - SelectOptionCondition::OptionIs - } -} - -impl std::convert::TryFrom for SelectOptionCondition { - type Error = ErrorCode; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(SelectOptionCondition::OptionIs), - 1 => Ok(SelectOptionCondition::OptionIsNot), - 2 => Ok(SelectOptionCondition::OptionIsEmpty), - 3 => Ok(SelectOptionCondition::OptionIsNotEmpty), - _ => Err(ErrorCode::InvalidData), - } - } -} - -impl std::convert::From> for GridSelectOptionFilter { - fn from(rev: Arc) -> Self { - GridSelectOptionFilter { - condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs), - content: rev.content.clone(), - } - } -} - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridDateFilter { - #[pb(index = 1)] - pub condition: DateFilterCondition, - - #[pb(index = 2, one_of)] - pub content: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] -#[repr(u8)] -pub enum DateFilterCondition { - DateIs = 0, - DateBefore = 1, - DateAfter = 2, - DateOnOrBefore = 3, - DateOnOrAfter = 4, - DateWithIn = 5, - DateIsEmpty = 6, -} - -impl std::default::Default for DateFilterCondition { - fn default() -> Self { - DateFilterCondition::DateIs - } -} - -impl std::convert::TryFrom for DateFilterCondition { - type Error = ErrorCode; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(DateFilterCondition::DateIs), - 1 => Ok(DateFilterCondition::DateBefore), - 2 => Ok(DateFilterCondition::DateAfter), - 3 => Ok(DateFilterCondition::DateOnOrBefore), - 4 => Ok(DateFilterCondition::DateOnOrAfter), - 5 => Ok(DateFilterCondition::DateWithIn), - 6 => Ok(DateFilterCondition::DateIsEmpty), - _ => Err(ErrorCode::InvalidData), - } - } -} -impl std::convert::From> for GridDateFilter { - fn from(rev: Arc) -> Self { - GridDateFilter { - condition: DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs), - content: rev.content.clone(), - } - } -} - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridCheckboxFilter { - #[pb(index = 1)] - pub condition: CheckboxCondition, -} - -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] -#[repr(u8)] -pub enum CheckboxCondition { - IsChecked = 0, - IsUnChecked = 1, -} - -impl std::convert::From for i32 { - fn from(value: CheckboxCondition) -> Self { - value as i32 - } -} - -impl std::default::Default for CheckboxCondition { - fn default() -> Self { - CheckboxCondition::IsChecked - } -} - -impl std::convert::TryFrom for CheckboxCondition { - type Error = ErrorCode; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(CheckboxCondition::IsChecked), - 1 => Ok(CheckboxCondition::IsUnChecked), - _ => Err(ErrorCode::InvalidData), - } - } -} - -impl std::convert::From> for GridCheckboxFilter { - fn from(rev: Arc) -> Self { - GridCheckboxFilter { - condition: CheckboxCondition::try_from(rev.condition).unwrap_or(CheckboxCondition::IsChecked), - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs new file mode 100644 index 0000000000..c13cad33e8 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs @@ -0,0 +1,77 @@ +use crate::services::field::CheckboxCellData; +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use flowy_error::ErrorCode; +use flowy_grid_data_model::revision::GridFilterRevision; +use std::sync::Arc; + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct GridCheckboxFilter { + #[pb(index = 1)] + pub condition: CheckboxCondition, +} + +impl GridCheckboxFilter { + pub fn apply(&self, cell_data: &CheckboxCellData) -> bool { + let is_check = cell_data.is_check(); + match self.condition { + CheckboxCondition::IsChecked => is_check, + CheckboxCondition::IsUnChecked => !is_check, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[repr(u8)] +pub enum CheckboxCondition { + IsChecked = 0, + IsUnChecked = 1, +} + +impl std::convert::From for i32 { + fn from(value: CheckboxCondition) -> Self { + value as i32 + } +} + +impl std::default::Default for CheckboxCondition { + fn default() -> Self { + CheckboxCondition::IsChecked + } +} + +impl std::convert::TryFrom for CheckboxCondition { + type Error = ErrorCode; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(CheckboxCondition::IsChecked), + 1 => Ok(CheckboxCondition::IsUnChecked), + _ => Err(ErrorCode::InvalidData), + } + } +} + +impl std::convert::From> for GridCheckboxFilter { + fn from(rev: Arc) -> Self { + GridCheckboxFilter { + condition: CheckboxCondition::try_from(rev.condition).unwrap_or(CheckboxCondition::IsChecked), + } + } +} + +#[cfg(test)] +mod tests { + use crate::entities::{CheckboxCondition, GridCheckboxFilter}; + use crate::services::field::CheckboxCellData; + + #[test] + fn checkbox_filter_is_check_test() { + let checkbox_filter = GridCheckboxFilter { + condition: CheckboxCondition::IsChecked, + }; + for (value, r) in [("true", true), ("yes", true), ("false", false), ("no", false)] { + let data = CheckboxCellData(value.to_owned()); + assert_eq!(checkbox_filter.apply(&data), r); + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs new file mode 100644 index 0000000000..f892c8b10d --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs @@ -0,0 +1,56 @@ +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use flowy_error::ErrorCode; +use flowy_grid_data_model::revision::GridFilterRevision; +use std::sync::Arc; + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct GridDateFilter { + #[pb(index = 1)] + pub condition: DateFilterCondition, + + #[pb(index = 2, one_of)] + pub content: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[repr(u8)] +pub enum DateFilterCondition { + DateIs = 0, + DateBefore = 1, + DateAfter = 2, + DateOnOrBefore = 3, + DateOnOrAfter = 4, + DateWithIn = 5, + DateIsEmpty = 6, +} + +impl std::default::Default for DateFilterCondition { + fn default() -> Self { + DateFilterCondition::DateIs + } +} + +impl std::convert::TryFrom for DateFilterCondition { + type Error = ErrorCode; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(DateFilterCondition::DateIs), + 1 => Ok(DateFilterCondition::DateBefore), + 2 => Ok(DateFilterCondition::DateAfter), + 3 => Ok(DateFilterCondition::DateOnOrBefore), + 4 => Ok(DateFilterCondition::DateOnOrAfter), + 5 => Ok(DateFilterCondition::DateWithIn), + 6 => Ok(DateFilterCondition::DateIsEmpty), + _ => Err(ErrorCode::InvalidData), + } + } +} +impl std::convert::From> for GridDateFilter { + fn from(rev: Arc) -> Self { + GridDateFilter { + condition: DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs), + content: rev.content.clone(), + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs new file mode 100644 index 0000000000..bb033f5600 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs @@ -0,0 +1,13 @@ +mod checkbox_filter; +mod date_filter; +mod number_filter; +mod select_option_filter; +mod text_filter; +mod util; + +pub use checkbox_filter::*; +pub use date_filter::*; +pub use number_filter::*; +pub use select_option_filter::*; +pub use text_filter::*; +pub use util::*; diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs new file mode 100644 index 0000000000..b54f94972d --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs @@ -0,0 +1,142 @@ +use crate::services::field::NumberCellData; +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use flowy_error::ErrorCode; +use flowy_grid_data_model::revision::GridFilterRevision; +use rust_decimal::prelude::Zero; +use rust_decimal::Decimal; +use std::str::FromStr; +use std::sync::Arc; + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct GridNumberFilter { + #[pb(index = 1)] + pub condition: NumberFilterCondition, + + #[pb(index = 2, one_of)] + pub content: Option, +} + +impl GridNumberFilter { + pub fn apply(&self, num_cell_data: &NumberCellData) -> bool { + if self.content.is_none() { + return false; + } + + let content = self.content.as_ref().unwrap(); + let zero_decimal = Decimal::zero(); + let cell_decimal = num_cell_data.decimal().as_ref().unwrap_or(&zero_decimal); + match Decimal::from_str(content) { + Ok(decimal) => match self.condition { + NumberFilterCondition::Equal => cell_decimal == &decimal, + NumberFilterCondition::NotEqual => cell_decimal != &decimal, + NumberFilterCondition::GreaterThan => cell_decimal > &decimal, + NumberFilterCondition::LessThan => cell_decimal < &decimal, + NumberFilterCondition::GreaterThanOrEqualTo => cell_decimal >= &decimal, + NumberFilterCondition::LessThanOrEqualTo => cell_decimal <= &decimal, + NumberFilterCondition::NumberIsEmpty => num_cell_data.is_empty(), + NumberFilterCondition::NumberIsNotEmpty => !num_cell_data.is_empty(), + }, + Err(_) => false, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[repr(u8)] +pub enum NumberFilterCondition { + Equal = 0, + NotEqual = 1, + GreaterThan = 2, + LessThan = 3, + GreaterThanOrEqualTo = 4, + LessThanOrEqualTo = 5, + NumberIsEmpty = 6, + NumberIsNotEmpty = 7, +} + +impl std::default::Default for NumberFilterCondition { + fn default() -> Self { + NumberFilterCondition::Equal + } +} + +impl std::convert::From for i32 { + fn from(value: NumberFilterCondition) -> Self { + value as i32 + } +} +impl std::convert::TryFrom for NumberFilterCondition { + type Error = ErrorCode; + + fn try_from(n: u8) -> Result { + match n { + 0 => Ok(NumberFilterCondition::Equal), + 1 => Ok(NumberFilterCondition::NotEqual), + 2 => Ok(NumberFilterCondition::GreaterThan), + 3 => Ok(NumberFilterCondition::LessThan), + 4 => Ok(NumberFilterCondition::GreaterThanOrEqualTo), + 5 => Ok(NumberFilterCondition::LessThanOrEqualTo), + 6 => Ok(NumberFilterCondition::NumberIsEmpty), + 7 => Ok(NumberFilterCondition::NumberIsNotEmpty), + _ => Err(ErrorCode::InvalidData), + } + } +} + +impl std::convert::From> for GridNumberFilter { + fn from(rev: Arc) -> Self { + GridNumberFilter { + condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal), + content: rev.content.clone(), + } + } +} + +#[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 { + condition: NumberFilterCondition::Equal, + content: Some("123".to_owned()), + }; + + for (num_str, r) in [("123", true), ("1234", false), ("", false)] { + let data = NumberCellData::from_str(num_str).unwrap(); + assert_eq!(number_filter.apply(&data), r); + } + + let format = NumberFormat::USD; + for (num_str, r) in [("$123", true), ("1234", false), ("", false)] { + let data = NumberCellData::from_format_str(num_str, true, &format).unwrap(); + assert_eq!(number_filter.apply(&data), r); + } + } + #[test] + fn number_filter_greater_than_test() { + let number_filter = GridNumberFilter { + condition: NumberFilterCondition::GreaterThan, + content: Some("12".to_owned()), + }; + for (num_str, r) in [("123", true), ("10", false), ("30", true), ("", false)] { + let data = NumberCellData::from_str(num_str).unwrap(); + assert_eq!(number_filter.apply(&data), r); + } + } + + #[test] + fn number_filter_less_than_test() { + let number_filter = GridNumberFilter { + condition: NumberFilterCondition::LessThan, + content: Some("100".to_owned()), + }; + for (num_str, r) in [("12", true), ("1234", false), ("30", true), ("", true)] { + let data = NumberCellData::from_str(num_str).unwrap(); + assert_eq!(number_filter.apply(&data), r); + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs new file mode 100644 index 0000000000..15edec0707 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs @@ -0,0 +1,121 @@ +#![allow(clippy::needless_collect)] +use crate::services::field::select_option::{SelectOptionIds, SelectedSelectOptions}; +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use flowy_error::ErrorCode; +use flowy_grid_data_model::revision::GridFilterRevision; +use std::sync::Arc; + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct GridSelectOptionFilter { + #[pb(index = 1)] + pub condition: SelectOptionCondition, + + #[pb(index = 2)] + pub option_ids: Vec, +} + +impl GridSelectOptionFilter { + pub fn apply(&self, selected_options: &SelectedSelectOptions) -> bool { + let selected_option_ids: Vec<&String> = selected_options.options.iter().map(|option| &option.id).collect(); + match self.condition { + SelectOptionCondition::OptionIs => { + // if selected options equal to filter's options, then the required_options will be empty. + let required_options = self + .option_ids + .iter() + .filter(|id| !selected_option_ids.contains(id)) + .collect::>(); + + // https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect + !required_options.is_empty() + } + SelectOptionCondition::OptionIsNot => { + for option_id in selected_option_ids { + if self.option_ids.contains(option_id) { + return true; + } + } + false + } + SelectOptionCondition::OptionIsEmpty => selected_option_ids.is_empty(), + SelectOptionCondition::OptionIsNotEmpty => !selected_option_ids.is_empty(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[repr(u8)] +pub enum SelectOptionCondition { + OptionIs = 0, + OptionIsNot = 1, + OptionIsEmpty = 2, + OptionIsNotEmpty = 3, +} + +impl std::convert::From for i32 { + fn from(value: SelectOptionCondition) -> Self { + value as i32 + } +} + +impl std::default::Default for SelectOptionCondition { + fn default() -> Self { + SelectOptionCondition::OptionIs + } +} + +impl std::convert::TryFrom for SelectOptionCondition { + type Error = ErrorCode; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(SelectOptionCondition::OptionIs), + 1 => Ok(SelectOptionCondition::OptionIsNot), + 2 => Ok(SelectOptionCondition::OptionIsEmpty), + 3 => Ok(SelectOptionCondition::OptionIsNotEmpty), + _ => Err(ErrorCode::InvalidData), + } + } +} + +impl std::convert::From> for GridSelectOptionFilter { + fn from(rev: Arc) -> Self { + let ids = SelectOptionIds::from(rev.content.clone()); + GridSelectOptionFilter { + condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs), + option_ids: ids.into_inner(), + } + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + use crate::entities::{GridSelectOptionFilter, SelectOptionCondition}; + use crate::services::field::select_option::{SelectOption, SelectedSelectOptions}; + + #[test] + fn select_option_filter_is_test() { + let option_1 = SelectOption::new("A"); + let option_2 = SelectOption::new("B"); + + let filter_1 = GridSelectOptionFilter { + condition: SelectOptionCondition::OptionIs, + option_ids: vec![option_1.id.clone(), option_2.id.clone()], + }; + + assert_eq!( + filter_1.apply(&SelectedSelectOptions { + options: vec![option_1.clone(), option_2.clone()], + }), + false + ); + + assert_eq!( + filter_1.apply(&SelectedSelectOptions { + options: vec![option_1.clone()], + }), + true, + ); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs new file mode 100644 index 0000000000..65253d2faa --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs @@ -0,0 +1,150 @@ +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use flowy_error::ErrorCode; +use flowy_grid_data_model::revision::GridFilterRevision; +use std::sync::Arc; + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct GridTextFilter { + #[pb(index = 1)] + pub condition: TextFilterCondition, + + #[pb(index = 2, one_of)] + pub content: Option, +} + +impl GridTextFilter { + pub fn apply>(&self, cell_data: T) -> bool { + let cell_data = cell_data.as_ref(); + let s = cell_data.to_lowercase(); + if let Some(content) = self.content.as_ref() { + match self.condition { + TextFilterCondition::Is => &s == content, + TextFilterCondition::IsNot => &s != content, + TextFilterCondition::Contains => s.contains(content), + TextFilterCondition::DoesNotContain => !s.contains(content), + TextFilterCondition::StartsWith => s.starts_with(content), + TextFilterCondition::EndsWith => s.ends_with(content), + TextFilterCondition::TextIsEmpty => s.is_empty(), + TextFilterCondition::TextIsNotEmpty => !s.is_empty(), + } + } else { + false + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[repr(u8)] +pub enum TextFilterCondition { + Is = 0, + IsNot = 1, + Contains = 2, + DoesNotContain = 3, + StartsWith = 4, + EndsWith = 5, + TextIsEmpty = 6, + TextIsNotEmpty = 7, +} + +impl std::convert::From for i32 { + fn from(value: TextFilterCondition) -> Self { + value as i32 + } +} + +impl std::default::Default for TextFilterCondition { + fn default() -> Self { + TextFilterCondition::Is + } +} +impl std::convert::TryFrom for TextFilterCondition { + type Error = ErrorCode; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(TextFilterCondition::Is), + 1 => Ok(TextFilterCondition::IsNot), + 2 => Ok(TextFilterCondition::Contains), + 3 => Ok(TextFilterCondition::DoesNotContain), + 4 => Ok(TextFilterCondition::StartsWith), + 5 => Ok(TextFilterCondition::EndsWith), + 6 => Ok(TextFilterCondition::TextIsEmpty), + 7 => Ok(TextFilterCondition::TextIsNotEmpty), + _ => Err(ErrorCode::InvalidData), + } + } +} + +impl std::convert::From> for GridTextFilter { + fn from(rev: Arc) -> Self { + GridTextFilter { + condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is), + content: rev.content.clone(), + } + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + use crate::entities::{GridTextFilter, TextFilterCondition}; + + #[test] + fn text_filter_equal_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::Is, + content: Some("appflowy".to_owned()), + }; + + assert!(text_filter.apply("AppFlowy")); + assert_eq!(text_filter.apply("appflowy"), true); + assert_eq!(text_filter.apply("Appflowy"), true); + assert_eq!(text_filter.apply("AppFlowy.io"), false); + } + #[test] + fn text_filter_start_with_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::StartsWith, + content: Some("appflowy".to_owned()), + }; + + assert_eq!(text_filter.apply("AppFlowy.io"), true); + assert_eq!(text_filter.apply(""), false); + assert_eq!(text_filter.apply("https"), false); + } + + #[test] + fn text_filter_end_with_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::EndsWith, + content: Some("appflowy".to_owned()), + }; + + assert_eq!(text_filter.apply("https://github.com/appflowy"), true); + assert_eq!(text_filter.apply("App"), false); + assert_eq!(text_filter.apply("appflowy.io"), false); + } + #[test] + fn text_filter_empty_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::TextIsEmpty, + content: Some("appflowy".to_owned()), + }; + + assert_eq!(text_filter.apply(""), true); + assert_eq!(text_filter.apply("App"), false); + } + #[test] + fn text_filter_contain_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::Contains, + content: Some("appflowy".to_owned()), + }; + + assert_eq!(text_filter.apply("https://github.com/appflowy"), true); + assert_eq!(text_filter.apply("AppFlowy"), true); + assert_eq!(text_filter.apply("App"), false); + assert_eq!(text_filter.apply(""), false); + assert_eq!(text_filter.apply("github"), false); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs new file mode 100644 index 0000000000..32ac38f986 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs @@ -0,0 +1,128 @@ +use crate::entities::{ + CheckboxCondition, DateFilterCondition, FieldType, NumberFilterCondition, SelectOptionCondition, + TextFilterCondition, +}; +use flowy_derive::ProtoBuf; +use flowy_error::ErrorCode; +use flowy_grid_data_model::parser::NotEmptyStr; +use flowy_grid_data_model::revision::{FieldRevision, GridFilterRevision}; +use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams}; +use std::convert::TryInto; +use std::sync::Arc; + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct GridFilter { + #[pb(index = 1)] + pub id: String, +} + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct RepeatedGridFilter { + #[pb(index = 1)] + pub items: Vec, +} + +impl std::convert::From<&Arc> for GridFilter { + fn from(rev: &Arc) -> Self { + Self { id: rev.id.clone() } + } +} + +impl std::convert::From<&Vec>> for RepeatedGridFilter { + fn from(revs: &Vec>) -> Self { + RepeatedGridFilter { + items: revs.iter().map(|rev| rev.into()).collect(), + } + } +} + +impl std::convert::From> for RepeatedGridFilter { + fn from(items: Vec) -> Self { + Self { items } + } +} + +#[derive(ProtoBuf, Debug, Default, Clone)] +pub struct DeleteFilterPayload { + #[pb(index = 1)] + pub filter_id: String, + + #[pb(index = 2)] + pub field_type: FieldType, +} + +impl TryInto for DeleteFilterPayload { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let filter_id = NotEmptyStr::parse(self.filter_id) + .map_err(|_| ErrorCode::UnexpectedEmptyString)? + .0; + Ok(DeleteFilterParams { + filter_id, + field_type_rev: self.field_type.into(), + }) + } +} + +#[derive(ProtoBuf, Debug, Default, Clone)] +pub struct CreateGridFilterPayload { + #[pb(index = 1)] + pub field_id: String, + + #[pb(index = 2)] + pub field_type: FieldType, + + #[pb(index = 3)] + pub condition: i32, + + #[pb(index = 4, one_of)] + pub content: Option, +} + +impl CreateGridFilterPayload { + #[allow(dead_code)] + pub fn new>(field_rev: &FieldRevision, condition: T, content: Option) -> Self { + Self { + field_id: field_rev.id.clone(), + field_type: field_rev.field_type_rev.into(), + condition: condition.into(), + content, + } + } +} + +impl TryInto for CreateGridFilterPayload { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let field_id = NotEmptyStr::parse(self.field_id) + .map_err(|_| ErrorCode::FieldIdIsEmpty)? + .0; + let condition = self.condition as u8; + match self.field_type { + FieldType::RichText | FieldType::URL => { + let _ = TextFilterCondition::try_from(condition)?; + } + FieldType::Checkbox => { + let _ = CheckboxCondition::try_from(condition)?; + } + FieldType::Number => { + let _ = NumberFilterCondition::try_from(condition)?; + } + FieldType::DateTime => { + let _ = DateFilterCondition::try_from(condition)?; + } + FieldType::SingleSelect | FieldType::MultiSelect => { + let _ = SelectOptionCondition::try_from(condition)?; + } + } + + Ok(CreateGridFilterParams { + field_id, + field_type_rev: self.field_type.into(), + condition, + content: self.content, + }) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index cbe43c8f82..6c86c30381 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -1,7 +1,10 @@ use crate::entities::*; use crate::manager::GridManager; -use crate::services::field::type_options::*; -use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_json_str}; +use crate::services::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}; @@ -362,7 +365,8 @@ pub(crate) async fn get_select_option_handler( Some(field_rev) => { let cell_rev = editor.get_cell_rev(¶ms.row_id, ¶ms.field_id).await?; let type_option = select_option_operation(&field_rev)?; - let option_context = type_option.select_option_cell_data(&cell_rev); + let any_cell_data: AnyCellData = cell_rev.try_into()?; + let option_context = type_option.selected_select_option(any_cell_data); data_result(option_context) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/mod.rs index c1b689fbf4..8a01d90d1f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/mod.rs @@ -1,4 +1,5 @@ mod field_builder; +pub mod select_option; pub(crate) mod type_options; pub use field_builder::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/select_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/select_option.rs new file mode 100644 index 0000000000..3838405d93 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/select_option.rs @@ -0,0 +1,337 @@ +use crate::entities::{CellChangeset, CellIdentifier, CellIdentifierPayload, FieldType}; +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_grid_data_model::parser::NotEmptyStr; +use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataEntry}; +use nanoid::nanoid; +use serde::{Deserialize, Serialize}; + +pub const SELECTION_IDS_SEPARATOR: &str = ","; + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, ProtoBuf)] +pub struct SelectOption { + #[pb(index = 1)] + pub id: String, + + #[pb(index = 2)] + pub name: String, + + #[pb(index = 3)] + pub color: SelectOptionColor, +} + +impl SelectOption { + pub fn new(name: &str) -> Self { + SelectOption { + id: nanoid!(4), + name: name.to_owned(), + color: SelectOptionColor::default(), + } + } + + pub fn with_color(name: &str, color: SelectOptionColor) -> Self { + SelectOption { + id: nanoid!(4), + name: name.to_owned(), + color, + } + } +} + +#[derive(ProtoBuf_Enum, PartialEq, Eq, Serialize, Deserialize, Debug, Clone)] +#[repr(u8)] +pub enum SelectOptionColor { + Purple = 0, + Pink = 1, + LightPink = 2, + Orange = 3, + Yellow = 4, + Lime = 5, + Green = 6, + Aqua = 7, + Blue = 8, +} + +impl std::default::Default for SelectOptionColor { + fn default() -> Self { + SelectOptionColor::Purple + } +} + +pub fn make_selected_select_options>( + any_cell_data: T, + options: &[SelectOption], +) -> Vec { + if let Ok(type_option_cell_data) = any_cell_data.try_into() { + let ids = SelectOptionIds::from(type_option_cell_data.cell_data); + ids.iter() + .flat_map(|option_id| options.iter().find(|option| &option.id == option_id).cloned()) + .collect() + } else { + vec![] + } +} + +pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync { + fn insert_option(&mut self, new_option: SelectOption) { + let options = self.mut_options(); + if let Some(index) = options + .iter() + .position(|option| option.id == new_option.id || option.name == new_option.name) + { + options.remove(index); + options.insert(index, new_option); + } else { + options.insert(0, new_option); + } + } + + fn delete_option(&mut self, delete_option: SelectOption) { + let options = self.mut_options(); + if let Some(index) = options.iter().position(|option| option.id == delete_option.id) { + options.remove(index); + } + } + + fn create_option(&self, name: &str) -> SelectOption { + let color = select_option_color_from_index(self.options().len()); + SelectOption::with_color(name, color) + } + + fn selected_select_option(&self, any_cell_data: AnyCellData) -> SelectOptionCellData; + + fn options(&self) -> &Vec; + + fn mut_options(&mut self) -> &mut Vec; +} + +pub fn select_option_operation(field_rev: &FieldRevision) -> FlowyResult> { + let field_type: FieldType = field_rev.field_type_rev.into(); + match &field_type { + FieldType::SingleSelect => { + let type_option = SingleSelectTypeOption::from(field_rev); + Ok(Box::new(type_option)) + } + FieldType::MultiSelect => { + let type_option = MultiSelectTypeOption::from(field_rev); + Ok(Box::new(type_option)) + } + ty => { + tracing::error!("Unsupported field type: {:?} for this handler", ty); + Err(ErrorCode::FieldInvalidOperation.into()) + } + } +} + +pub fn select_option_color_from_index(index: usize) -> SelectOptionColor { + match index % 8 { + 0 => SelectOptionColor::Purple, + 1 => SelectOptionColor::Pink, + 2 => SelectOptionColor::LightPink, + 3 => SelectOptionColor::Orange, + 4 => SelectOptionColor::Yellow, + 5 => SelectOptionColor::Lime, + 6 => SelectOptionColor::Green, + 7 => SelectOptionColor::Aqua, + 8 => SelectOptionColor::Blue, + _ => SelectOptionColor::Purple, + } +} +pub struct SelectOptionIds(Vec); + +impl SelectOptionIds { + pub fn into_inner(self) -> Vec { + self.0 + } +} + +impl std::convert::TryFrom for SelectOptionIds { + type Error = FlowyError; + + fn try_from(value: AnyCellData) -> Result { + Ok(Self::from(value.cell_data)) + } +} + +impl std::convert::From for SelectOptionIds { + fn from(s: String) -> Self { + let ids = s + .split(SELECTION_IDS_SEPARATOR) + .map(|id| id.to_string()) + .collect::>(); + Self(ids) + } +} + +impl std::convert::From> for SelectOptionIds { + fn from(s: Option) -> Self { + match s { + None => Self { 0: vec![] }, + Some(s) => Self::from(s), + } + } +} + +impl std::ops::Deref for SelectOptionIds { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for SelectOptionIds { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[derive(Clone, Debug, Default, ProtoBuf)] +pub struct SelectOptionCellChangesetPayload { + #[pb(index = 1)] + pub cell_identifier: CellIdentifierPayload, + + #[pb(index = 2, one_of)] + pub insert_option_id: Option, + + #[pb(index = 3, one_of)] + pub delete_option_id: Option, +} + +pub struct SelectOptionCellChangesetParams { + pub cell_identifier: CellIdentifier, + pub insert_option_id: Option, + pub delete_option_id: Option, +} + +impl std::convert::From for CellChangeset { + fn from(params: SelectOptionCellChangesetParams) -> Self { + let changeset = SelectOptionCellContentChangeset { + insert_option_id: params.insert_option_id, + delete_option_id: params.delete_option_id, + }; + 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, + cell_content_changeset: Some(s), + } + } +} + +impl TryInto for SelectOptionCellChangesetPayload { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let cell_identifier: CellIdentifier = self.cell_identifier.try_into()?; + let insert_option_id = match self.insert_option_id { + None => None, + Some(insert_option_id) => Some( + NotEmptyStr::parse(insert_option_id) + .map_err(|_| ErrorCode::OptionIdIsEmpty)? + .0, + ), + }; + + let delete_option_id = match self.delete_option_id { + None => None, + Some(delete_option_id) => Some( + NotEmptyStr::parse(delete_option_id) + .map_err(|_| ErrorCode::OptionIdIsEmpty)? + .0, + ), + }; + + Ok(SelectOptionCellChangesetParams { + cell_identifier, + insert_option_id, + delete_option_id, + }) + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct SelectOptionCellContentChangeset { + pub insert_option_id: Option, + pub delete_option_id: Option, +} + +impl SelectOptionCellContentChangeset { + pub fn from_insert(option_id: &str) -> Self { + SelectOptionCellContentChangeset { + insert_option_id: Some(option_id.to_string()), + delete_option_id: None, + } + } + + pub fn from_delete(option_id: &str) -> Self { + SelectOptionCellContentChangeset { + insert_option_id: None, + delete_option_id: Some(option_id.to_string()), + } + } + + pub fn to_str(&self) -> String { + serde_json::to_string(self).unwrap() + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] +pub struct SelectOptionCellData { + #[pb(index = 1)] + pub options: Vec, + + #[pb(index = 2)] + pub select_options: Vec, +} + +#[derive(Clone, Debug, Default, ProtoBuf)] +pub struct SelectOptionChangesetPayload { + #[pb(index = 1)] + pub cell_identifier: CellIdentifierPayload, + + #[pb(index = 2, one_of)] + pub insert_option: Option, + + #[pb(index = 3, one_of)] + pub update_option: Option, + + #[pb(index = 4, one_of)] + pub delete_option: Option, +} + +pub struct SelectOptionChangeset { + pub cell_identifier: CellIdentifier, + pub insert_option: Option, + pub update_option: Option, + pub delete_option: Option, +} + +impl TryInto for SelectOptionChangesetPayload { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let cell_identifier = self.cell_identifier.try_into()?; + Ok(SelectOptionChangeset { + cell_identifier, + insert_option: self.insert_option, + update_option: self.update_option, + delete_option: self.delete_option, + }) + } +} + +pub struct SelectedSelectOptions { + pub(crate) options: Vec, +} + +impl std::convert::From for SelectedSelectOptions { + fn from(data: SelectOptionCellData) -> Self { + Self { + options: data.select_options, + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs index cb8c372991..bb8f43c6fb 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs @@ -42,9 +42,13 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox); const YES: &str = "Yes"; const NO: &str = "No"; -impl CellFilterOperation for CheckboxTypeOption { - fn apply_filter(&self, _cell_data: CheckboxCellData, _filter: &GridCheckboxFilter) -> bool { - false +impl CellFilterOperation for CheckboxTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridCheckboxFilter) -> FlowyResult { + if !any_cell_data.is_checkbox() { + return Ok(true); + } + let checkbox_cell_data: CheckboxCellData = any_cell_data.try_into()?; + Ok(filter.apply(&checkbox_cell_data)) } } @@ -96,10 +100,18 @@ fn string_to_bool(bool_str: &str) -> bool { } } -pub struct CheckboxCellData(String); -impl std::convert::From for CheckboxCellData { - fn from(any_cell_data: AnyCellData) -> Self { - CheckboxCellData(any_cell_data.cell_data) +pub struct CheckboxCellData(pub String); + +impl CheckboxCellData { + pub fn is_check(&self) -> bool { + string_to_bool(&self.0) + } +} +impl std::convert::TryFrom for CheckboxCellData { + type Error = FlowyError; + + fn try_from(_value: AnyCellData) -> Result { + todo!() } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs index 1bf5228857..21c608f3b4 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs @@ -117,9 +117,12 @@ impl DateTypeOption { } } -impl CellFilterOperation for DateTypeOption { - fn apply_filter(&self, _cell_data: AnyCellData, _filter: &GridDateFilter) -> bool { - false +impl CellFilterOperation for DateTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, _filter: &GridDateFilter) -> FlowyResult { + if !any_cell_data.is_date() { + return Ok(true); + } + Ok(false) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs index 3cfe390b38..754e441876 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs @@ -1,14 +1,17 @@ mod checkbox_type_option; mod date_type_option; +mod multi_select_type_option; mod number_type_option; -mod selection_type_option; +mod single_select_type_option; mod text_type_option; 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 selection_type_option::*; +pub use single_select_type_option::*; pub use text_type_option::*; pub use url_type_option::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/multi_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/multi_select_type_option.rs new file mode 100644 index 0000000000..513d509f9b --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/multi_select_type_option.rs @@ -0,0 +1,217 @@ +use crate::entities::{FieldType, GridSelectOptionFilter}; + +use crate::impl_type_option; +use crate::services::field::select_option::{ + make_selected_select_options, SelectOption, SelectOptionCellContentChangeset, 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}; + +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; + +use serde::{Deserialize, Serialize}; + +// Multiple select +#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] +pub struct MultiSelectTypeOption { + #[pb(index = 1)] + pub options: Vec, + + #[pb(index = 2)] + pub disable_color: bool, +} +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); + SelectOptionCellData { + options: self.options.clone(), + select_options, + } + } + + fn options(&self) -> &Vec { + &self.options + } + + fn mut_options(&mut self) -> &mut Vec { + &mut self.options + } +} +impl CellFilterOperation for MultiSelectTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult { + if !any_cell_data.is_multi_select() { + return Ok(true); + } + + let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data)); + Ok(filter.apply(&selected_options)) + } +} +impl CellDataOperation for MultiSelectTypeOption { + fn decode_cell_data( + &self, + cell_data: T, + decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> FlowyResult + where + T: Into, + { + if !decoded_field_type.is_select_option() { + return Ok(DecodedCellData::default()); + } + + let encoded_data = cell_data.into(); + let ids: SelectOptionIds = encoded_data.into(); + let select_options = ids + .iter() + .flat_map(|option_id| self.options.iter().find(|option| &option.id == option_id).cloned()) + .collect::>(); + + let cell_data = SelectOptionCellData { + options: self.options.clone(), + select_options, + }; + + DecodedCellData::try_from_bytes(cell_data) + } + + fn apply_changeset(&self, changeset: T, cell_rev: Option) -> Result + where + T: Into, + { + let content_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset.into())?; + let new_cell_data: String; + match cell_rev { + None => { + new_cell_data = content_changeset.insert_option_id.unwrap_or_else(|| "".to_owned()); + } + Some(cell_rev) => { + let cell_data = get_cell_data(&cell_rev); + let mut select_ids: SelectOptionIds = cell_data.into(); + if let Some(insert_option_id) = content_changeset.insert_option_id { + tracing::trace!("Insert multi select option: {}", &insert_option_id); + if select_ids.contains(&insert_option_id) { + select_ids.retain(|id| id != &insert_option_id); + } else { + select_ids.push(insert_option_id); + } + } + + if let Some(delete_option_id) = content_changeset.delete_option_id { + tracing::trace!("Delete multi select option: {}", &delete_option_id); + select_ids.retain(|id| id != &delete_option_id); + } + + new_cell_data = select_ids.join(SELECTION_IDS_SEPARATOR); + tracing::trace!("Multi select cell data: {}", &new_cell_data); + } + } + + Ok(new_cell_data) + } +} + +#[derive(Default)] +pub struct MultiSelectTypeOptionBuilder(MultiSelectTypeOption); +impl_into_box_type_option_builder!(MultiSelectTypeOptionBuilder); +impl_builder_from_json_str_and_from_bytes!(MultiSelectTypeOptionBuilder, MultiSelectTypeOption); +impl MultiSelectTypeOptionBuilder { + pub fn option(mut self, opt: SelectOption) -> Self { + self.0.options.push(opt); + self + } +} + +impl TypeOptionBuilder for MultiSelectTypeOptionBuilder { + fn field_type(&self) -> FieldType { + FieldType::MultiSelect + } + + fn entry(&self) -> &dyn TypeOptionDataEntry { + &self.0 + } +} +#[cfg(test)] +mod tests { + use crate::entities::FieldType; + 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] + fn multi_select_test() { + let google_option = SelectOption::new("Google"); + let facebook_option = SelectOption::new("Facebook"); + let twitter_option = SelectOption::new("Twitter"); + let multi_select = MultiSelectTypeOptionBuilder::default() + .option(google_option.clone()) + .option(facebook_option.clone()) + .option(twitter_option); + + let field_rev = FieldBuilder::new(multi_select) + .name("Platform") + .visibility(true) + .build(); + + 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(); + assert_multi_select_options( + cell_data, + &type_option, + &field_rev, + 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(); + 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) + .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) + .unwrap(); + assert_multi_select_options(cell_data, &type_option, &field_rev, vec![]); + + // Invalid changeset + assert!(type_option.apply_changeset("123", None).is_err()); + } + + fn assert_multi_select_options( + cell_data: String, + type_option: &MultiSelectTypeOption, + field_rev: &FieldRevision, + expected: Vec, + ) { + let field_type: FieldType = field_rev.field_type_rev.into(); + assert_eq!( + expected, + type_option + .decode_cell_data(cell_data, &field_type, field_rev) + .unwrap() + .parse::() + .unwrap() + .select_options, + ); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs index 456dfb19db..2b9717458b 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs @@ -1,6 +1,7 @@ use crate::impl_type_option; use crate::entities::{FieldType, GridNumberFilter}; +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::{ @@ -10,7 +11,9 @@ 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; @@ -76,24 +79,13 @@ impl NumberTypeOption { Self::default() } - fn cell_content_from_number_str(&self, s: &str) -> FlowyResult { + fn format_cell_data(&self, s: &str) -> FlowyResult { match self.format { - NumberFormat::Num => { - if let Ok(v) = s.parse::() { - return Ok(v.to_string()); - } - - if let Ok(v) = s.parse::() { - return Ok(v.to_string()); - } - - Ok("".to_string()) - } - NumberFormat::Percent => { - let content = s.parse::().map_or(String::new(), |v| v.to_string()); - Ok(content) - } - _ => self.money_from_number_str(s), + NumberFormat::Num | NumberFormat::Percent => match Decimal::from_str(s) { + Ok(value, ..) => Ok(NumberCellData::from_decimal(value)), + Err(_) => Ok(NumberCellData::new()), + }, + _ => NumberCellData::from_format_str(s, self.sign_positive, &self.format), } } @@ -101,49 +93,28 @@ impl NumberTypeOption { self.format = format; self.symbol = format.symbol(); } - - fn money_from_number_str(&self, s: &str) -> FlowyResult { - let mut number = self.strip_currency_symbol(s); - - if s.is_empty() { - return Ok("".to_string()); - } - - match Decimal::from_str(&number) { - Ok(mut decimal) => { - decimal.set_sign_positive(self.sign_positive); - let money = rusty_money::Money::from_decimal(decimal, self.format.currency()).to_string(); - Ok(money) - } - Err(_) => match rusty_money::Money::from_str(&number, self.format.currency()) { - Ok(money) => Ok(money.to_string()), - Err(_) => { - number.retain(|c| !STRIP_SYMBOL.contains(&c.to_string())); - if number.chars().all(char::is_numeric) { - self.money_from_number_str(&number) - } else { - Err(FlowyError::invalid_data().context("Should only contain numbers")) - } - } - }, - } - } - - fn strip_currency_symbol(&self, s: T) -> String { - let mut s = s.to_string(); - for symbol in CURRENCY_SYMBOL.iter() { - if s.starts_with(symbol) { - s = s.strip_prefix(symbol).unwrap_or("").to_string(); - break; - } - } - s - } } -impl CellFilterOperation for NumberTypeOption { - fn apply_filter(&self, any_cell_data: AnyCellData, _filter: &GridNumberFilter) -> bool { - let _number_cell_data = NumberCellData::from_number_type_option(self, any_cell_data); - false + +pub(crate) fn strip_currency_symbol(s: T) -> String { + let mut s = s.to_string(); + for symbol in CURRENCY_SYMBOL.iter() { + if s.starts_with(symbol) { + s = s.strip_prefix(symbol).unwrap_or("").to_string(); + break; + } + } + s +} +impl CellFilterOperation for NumberTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridNumberFilter) -> FlowyResult { + if !any_cell_data.is_number() { + return Ok(true); + } + + let cell_data = any_cell_data.cell_data; + let num_cell_data = self.format_cell_data(&cell_data)?; + + Ok(filter.apply(&num_cell_data)) } } @@ -162,28 +133,9 @@ impl CellDataOperation for NumberTypeOption { } let cell_data = cell_data.into(); - match self.format { - NumberFormat::Num => { - if let Ok(v) = cell_data.parse::() { - return Ok(DecodedCellData::new(v.to_string())); - } - - if let Ok(v) = cell_data.parse::() { - return Ok(DecodedCellData::new(v.to_string())); - } - - Ok(DecodedCellData::default()) - } - NumberFormat::Percent => { - let content = cell_data.parse::().map_or(String::new(), |v| v.to_string()); - Ok(DecodedCellData::new(content)) - } - _ => { - let content = self - .money_from_number_str(&cell_data) - .unwrap_or_else(|_| "".to_string()); - Ok(DecodedCellData::new(content)) - } + match self.format_cell_data(&cell_data) { + Ok(num) => Ok(DecodedCellData::new(num.to_string())), + Err(_) => Ok(DecodedCellData::default()), } } @@ -193,7 +145,7 @@ impl CellDataOperation for NumberTypeOption { { let changeset = changeset.into(); let data = changeset.trim().to_string(); - let _ = self.cell_content_from_number_str(&data)?; + let _ = self.format_cell_data(&data)?; Ok(data) } } @@ -213,33 +165,88 @@ impl std::default::Default for NumberTypeOption { } #[derive(Default)] -pub struct NumberCellData(String); +pub struct NumberCellData { + decimal: Option, + money: Option, +} impl NumberCellData { - fn from_number_type_option(type_option: &NumberTypeOption, any_cell_data: AnyCellData) -> Self { - let cell_data = any_cell_data.cell_data; - match type_option.format { - NumberFormat::Num => { - if let Ok(v) = cell_data.parse::() { - return Self(v.to_string()); - } + pub fn new() -> Self { + Self { + decimal: Default::default(), + money: None, + } + } - if let Ok(v) = cell_data.parse::() { - return Self(v.to_string()); + pub fn from_format_str(s: &str, sign_positive: bool, format: &NumberFormat) -> FlowyResult { + 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")) + } } + }, + } + } - Self::default() - } - NumberFormat::Percent => { - let content = cell_data.parse::().map_or(String::new(), |v| v.to_string()); - Self(content) - } - _ => { - let content = type_option - .money_from_number_str(&cell_data) - .unwrap_or_else(|_| "".to_string()); - Self(content) - } + pub fn from_decimal(decimal: Decimal) -> Self { + Self { + decimal: Some(decimal), + money: None, + } + } + + pub fn from_money(money: Money) -> Self { + Self { + decimal: Some(*money.amount()), + money: Some(money.to_string()), + } + } + + pub fn decimal(&self) -> &Option { + &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 { + 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(), } } } @@ -248,7 +255,7 @@ impl NumberCellData { mod tests { use crate::entities::FieldType; use crate::services::field::FieldBuilder; - use crate::services::field::{NumberFormat, NumberTypeOption}; + use crate::services::field::{strip_currency_symbol, NumberFormat, NumberTypeOption}; use crate::services::row::CellDataOperation; use flowy_grid_data_model::revision::FieldRevision; use strum::IntoEnumIterator; @@ -266,10 +273,10 @@ mod tests { fn number_type_option_strip_symbol_test() { let mut type_option = NumberTypeOption::new(); type_option.format = NumberFormat::USD; - assert_eq!(type_option.strip_currency_symbol("$18,443"), "18,443".to_owned()); + assert_eq!(strip_currency_symbol("$18,443"), "18,443".to_owned()); type_option.format = NumberFormat::Yuan; - assert_eq!(type_option.strip_currency_symbol("$0.2"), "0.2".to_owned()); + assert_eq!(strip_currency_symbol("$0.2"), "0.2".to_owned()); } #[test] diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs deleted file mode 100644 index 9d1876b348..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs +++ /dev/null @@ -1,660 +0,0 @@ -use crate::entities::{CellChangeset, FieldType, GridSelectOptionFilter}; -use crate::entities::{CellIdentifier, CellIdentifierPayload}; -use crate::impl_type_option; -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, ProtoBuf_Enum}; -use flowy_error::{ErrorCode, FlowyError, FlowyResult}; -use flowy_grid_data_model::parser::NotEmptyStr; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; -use nanoid::nanoid; -use serde::{Deserialize, Serialize}; -use std::str::FromStr; - -pub const SELECTION_IDS_SEPARATOR: &str = ","; - -pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync { - fn insert_option(&mut self, new_option: SelectOption) { - let options = self.mut_options(); - if let Some(index) = options - .iter() - .position(|option| option.id == new_option.id || option.name == new_option.name) - { - options.remove(index); - options.insert(index, new_option); - } else { - options.insert(0, new_option); - } - } - - fn delete_option(&mut self, delete_option: SelectOption) { - let options = self.mut_options(); - if let Some(index) = options.iter().position(|option| option.id == delete_option.id) { - options.remove(index); - } - } - - fn create_option(&self, name: &str) -> SelectOption { - let color = select_option_color_from_index(self.options().len()); - SelectOption::with_color(name, color) - } - - fn select_option_cell_data(&self, cell_rev: &Option) -> SelectOptionCellData; - - fn options(&self) -> &Vec; - - fn mut_options(&mut self) -> &mut Vec; -} - -pub fn select_option_operation(field_rev: &FieldRevision) -> FlowyResult> { - let field_type: FieldType = field_rev.field_type_rev.into(); - match &field_type { - FieldType::SingleSelect => { - let type_option = SingleSelectTypeOption::from(field_rev); - Ok(Box::new(type_option)) - } - FieldType::MultiSelect => { - let type_option = MultiSelectTypeOption::from(field_rev); - Ok(Box::new(type_option)) - } - ty => { - tracing::error!("Unsupported field type: {:?} for this handler", ty); - Err(ErrorCode::FieldInvalidOperation.into()) - } - } -} - -// Single select -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] -pub struct SingleSelectTypeOption { - #[pb(index = 1)] - pub options: Vec, - - #[pb(index = 2)] - pub disable_color: bool, -} -impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect); - -impl SelectOptionOperation for SingleSelectTypeOption { - fn select_option_cell_data(&self, cell_rev: &Option) -> SelectOptionCellData { - let select_options = make_select_context_from(cell_rev, &self.options); - SelectOptionCellData { - options: self.options.clone(), - select_options, - } - } - - fn options(&self) -> &Vec { - &self.options - } - - fn mut_options(&mut self) -> &mut Vec { - &mut self.options - } -} - -impl CellFilterOperation for SingleSelectTypeOption { - fn apply_filter(&self, _cell_data: SelectOptionIds, _filter: &GridSelectOptionFilter) -> bool { - false - } -} - -impl CellDataOperation for SingleSelectTypeOption { - fn decode_cell_data( - &self, - cell_data: T, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult - where - T: Into, - { - if !decoded_field_type.is_select_option() { - return Ok(DecodedCellData::default()); - } - - let encoded_data = cell_data.into(); - let mut cell_data = SelectOptionCellData { - options: self.options.clone(), - select_options: vec![], - }; - if let Some(option_id) = select_option_ids(encoded_data).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) - } - - fn apply_changeset(&self, changeset: C, _cell_rev: Option) -> Result - where - C: Into, - { - let changeset = changeset.into(); - let select_option_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset)?; - 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); - new_cell_data = insert_option_id; - } else { - tracing::trace!("Delete single select option"); - new_cell_data = "".to_string() - } - - Ok(new_cell_data) - } -} - -#[derive(Default)] -pub struct SingleSelectTypeOptionBuilder(SingleSelectTypeOption); -impl_into_box_type_option_builder!(SingleSelectTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(SingleSelectTypeOptionBuilder, SingleSelectTypeOption); - -impl SingleSelectTypeOptionBuilder { - pub fn option(mut self, opt: SelectOption) -> Self { - self.0.options.push(opt); - self - } -} - -impl TypeOptionBuilder for SingleSelectTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::SingleSelect - } - - fn entry(&self) -> &dyn TypeOptionDataEntry { - &self.0 - } -} - -// Multiple select -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] -pub struct MultiSelectTypeOption { - #[pb(index = 1)] - pub options: Vec, - - #[pb(index = 2)] - pub disable_color: bool, -} -impl_type_option!(MultiSelectTypeOption, FieldType::MultiSelect); - -impl SelectOptionOperation for MultiSelectTypeOption { - fn select_option_cell_data(&self, cell_rev: &Option) -> SelectOptionCellData { - let select_options = make_select_context_from(cell_rev, &self.options); - SelectOptionCellData { - options: self.options.clone(), - select_options, - } - } - - fn options(&self) -> &Vec { - &self.options - } - - fn mut_options(&mut self) -> &mut Vec { - &mut self.options - } -} -impl CellFilterOperation for MultiSelectTypeOption { - fn apply_filter(&self, _cell_data: SelectOptionIds, _filter: &GridSelectOptionFilter) -> bool { - false - } -} -impl CellDataOperation for MultiSelectTypeOption { - fn decode_cell_data( - &self, - cell_data: T, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult - where - T: Into, - { - if !decoded_field_type.is_select_option() { - return Ok(DecodedCellData::default()); - } - - let encoded_data = cell_data.into(); - let select_options = select_option_ids(encoded_data) - .into_iter() - .flat_map(|option_id| self.options.iter().find(|option| option.id == option_id).cloned()) - .collect::>(); - - let cell_data = SelectOptionCellData { - options: self.options.clone(), - select_options, - }; - - DecodedCellData::try_from_bytes(cell_data) - } - - fn apply_changeset(&self, changeset: T, cell_rev: Option) -> Result - where - T: Into, - { - let content_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset.into())?; - let new_cell_data: String; - match cell_rev { - None => { - new_cell_data = content_changeset.insert_option_id.unwrap_or_else(|| "".to_owned()); - } - Some(cell_rev) => { - let cell_data = get_cell_data(&cell_rev); - let mut selected_options = select_option_ids(cell_data); - if let Some(insert_option_id) = content_changeset.insert_option_id { - tracing::trace!("Insert multi select option: {}", &insert_option_id); - if selected_options.contains(&insert_option_id) { - selected_options.retain(|id| id != &insert_option_id); - } else { - selected_options.push(insert_option_id); - } - } - - if let Some(delete_option_id) = content_changeset.delete_option_id { - tracing::trace!("Delete multi select option: {}", &delete_option_id); - selected_options.retain(|id| id != &delete_option_id); - } - - new_cell_data = selected_options.join(SELECTION_IDS_SEPARATOR); - tracing::trace!("Multi select cell data: {}", &new_cell_data); - } - } - - Ok(new_cell_data) - } -} - -#[derive(Default)] -pub struct MultiSelectTypeOptionBuilder(MultiSelectTypeOption); -impl_into_box_type_option_builder!(MultiSelectTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(MultiSelectTypeOptionBuilder, MultiSelectTypeOption); -impl MultiSelectTypeOptionBuilder { - pub fn option(mut self, opt: SelectOption) -> Self { - self.0.options.push(opt); - self - } -} - -impl TypeOptionBuilder for MultiSelectTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::MultiSelect - } - - fn entry(&self) -> &dyn TypeOptionDataEntry { - &self.0 - } -} - -pub struct SelectOptionIds(Vec); -impl std::convert::From for SelectOptionIds { - fn from(any_cell_data: AnyCellData) -> Self { - let ids = select_option_ids(any_cell_data.cell_data); - Self(ids) - } -} - -fn select_option_ids(data: String) -> Vec { - data.split(SELECTION_IDS_SEPARATOR) - .map(|id| id.to_string()) - .collect::>() -} - -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, ProtoBuf)] -pub struct SelectOption { - #[pb(index = 1)] - pub id: String, - - #[pb(index = 2)] - pub name: String, - - #[pb(index = 3)] - pub color: SelectOptionColor, -} - -impl SelectOption { - pub fn new(name: &str) -> Self { - SelectOption { - id: nanoid!(4), - name: name.to_owned(), - color: SelectOptionColor::default(), - } - } - - pub fn with_color(name: &str, color: SelectOptionColor) -> Self { - SelectOption { - id: nanoid!(4), - name: name.to_owned(), - color, - } - } -} - -#[derive(Clone, Debug, Default, ProtoBuf)] -pub struct SelectOptionChangesetPayload { - #[pb(index = 1)] - pub cell_identifier: CellIdentifierPayload, - - #[pb(index = 2, one_of)] - pub insert_option: Option, - - #[pb(index = 3, one_of)] - pub update_option: Option, - - #[pb(index = 4, one_of)] - pub delete_option: Option, -} - -pub struct SelectOptionChangeset { - pub cell_identifier: CellIdentifier, - pub insert_option: Option, - pub update_option: Option, - pub delete_option: Option, -} - -impl TryInto for SelectOptionChangesetPayload { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let cell_identifier = self.cell_identifier.try_into()?; - Ok(SelectOptionChangeset { - cell_identifier, - insert_option: self.insert_option, - update_option: self.update_option, - delete_option: self.delete_option, - }) - } -} - -#[derive(Clone, Debug, Default, ProtoBuf)] -pub struct SelectOptionCellChangesetPayload { - #[pb(index = 1)] - pub cell_identifier: CellIdentifierPayload, - - #[pb(index = 2, one_of)] - pub insert_option_id: Option, - - #[pb(index = 3, one_of)] - pub delete_option_id: Option, -} - -pub struct SelectOptionCellChangesetParams { - pub cell_identifier: CellIdentifier, - pub insert_option_id: Option, - pub delete_option_id: Option, -} - -impl std::convert::From for CellChangeset { - fn from(params: SelectOptionCellChangesetParams) -> Self { - let changeset = SelectOptionCellContentChangeset { - insert_option_id: params.insert_option_id, - delete_option_id: params.delete_option_id, - }; - 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, - cell_content_changeset: Some(s), - } - } -} - -impl TryInto for SelectOptionCellChangesetPayload { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let cell_identifier: CellIdentifier = self.cell_identifier.try_into()?; - let insert_option_id = match self.insert_option_id { - None => None, - Some(insert_option_id) => Some( - NotEmptyStr::parse(insert_option_id) - .map_err(|_| ErrorCode::OptionIdIsEmpty)? - .0, - ), - }; - - let delete_option_id = match self.delete_option_id { - None => None, - Some(delete_option_id) => Some( - NotEmptyStr::parse(delete_option_id) - .map_err(|_| ErrorCode::OptionIdIsEmpty)? - .0, - ), - }; - - Ok(SelectOptionCellChangesetParams { - cell_identifier, - insert_option_id, - delete_option_id, - }) - } -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct SelectOptionCellContentChangeset { - pub insert_option_id: Option, - pub delete_option_id: Option, -} - -impl SelectOptionCellContentChangeset { - pub fn from_insert(option_id: &str) -> Self { - SelectOptionCellContentChangeset { - insert_option_id: Some(option_id.to_string()), - delete_option_id: None, - } - } - - pub fn from_delete(option_id: &str) -> Self { - SelectOptionCellContentChangeset { - insert_option_id: None, - delete_option_id: Some(option_id.to_string()), - } - } - - pub fn to_str(&self) -> String { - serde_json::to_string(self).unwrap() - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] -pub struct SelectOptionCellData { - #[pb(index = 1)] - pub options: Vec, - - #[pb(index = 2)] - pub select_options: Vec, -} - -#[derive(ProtoBuf_Enum, PartialEq, Eq, Serialize, Deserialize, Debug, Clone)] -#[repr(u8)] -pub enum SelectOptionColor { - Purple = 0, - Pink = 1, - LightPink = 2, - Orange = 3, - Yellow = 4, - Lime = 5, - Green = 6, - Aqua = 7, - Blue = 8, -} - -pub fn select_option_color_from_index(index: usize) -> SelectOptionColor { - match index % 8 { - 0 => SelectOptionColor::Purple, - 1 => SelectOptionColor::Pink, - 2 => SelectOptionColor::LightPink, - 3 => SelectOptionColor::Orange, - 4 => SelectOptionColor::Yellow, - 5 => SelectOptionColor::Lime, - 6 => SelectOptionColor::Green, - 7 => SelectOptionColor::Aqua, - 8 => SelectOptionColor::Blue, - _ => SelectOptionColor::Purple, - } -} - -impl std::default::Default for SelectOptionColor { - fn default() -> Self { - SelectOptionColor::Purple - } -} - -fn make_select_context_from(cell_rev: &Option, options: &[SelectOption]) -> Vec { - match cell_rev { - None => vec![], - Some(cell_rev) => { - if let Ok(type_option_cell_data) = AnyCellData::from_str(&cell_rev.data) { - select_option_ids(type_option_cell_data.cell_data) - .into_iter() - .flat_map(|option_id| options.iter().find(|option| option.id == option_id).cloned()) - .collect() - } else { - vec![] - } - } - } -} - -#[cfg(test)] -mod tests { - use crate::entities::FieldType; - use crate::services::field::FieldBuilder; - use crate::services::field::{ - MultiSelectTypeOption, MultiSelectTypeOptionBuilder, SelectOption, SelectOptionCellContentChangeset, - SelectOptionCellData, SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR, - }; - use crate::services::row::CellDataOperation; - use flowy_grid_data_model::revision::FieldRevision; - - #[test] - fn single_select_test() { - let google_option = SelectOption::new("Google"); - let facebook_option = SelectOption::new("Facebook"); - let twitter_option = SelectOption::new("Twitter"); - let single_select = SingleSelectTypeOptionBuilder::default() - .option(google_option.clone()) - .option(facebook_option.clone()) - .option(twitter_option); - - let field_rev = FieldBuilder::new(single_select) - .name("Platform") - .visibility(true) - .build(); - - 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(); - 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(); - 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) - .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) - .unwrap(); - - assert_single_select_options(cell_data, &type_option, &field_rev, vec![]); - - // Invalid changeset - assert!(type_option.apply_changeset("123", None).is_err()); - } - - #[test] - fn multi_select_test() { - let google_option = SelectOption::new("Google"); - let facebook_option = SelectOption::new("Facebook"); - let twitter_option = SelectOption::new("Twitter"); - let multi_select = MultiSelectTypeOptionBuilder::default() - .option(google_option.clone()) - .option(facebook_option.clone()) - .option(twitter_option); - - let field_rev = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); - - 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(); - assert_multi_select_options( - cell_data, - &type_option, - &field_rev, - 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(); - 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) - .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) - .unwrap(); - assert_multi_select_options(cell_data, &type_option, &field_rev, vec![]); - - // Invalid changeset - assert!(type_option.apply_changeset("123", None).is_err()); - } - - fn assert_multi_select_options( - cell_data: String, - type_option: &MultiSelectTypeOption, - field_rev: &FieldRevision, - expected: Vec, - ) { - let field_type: FieldType = field_rev.field_type_rev.into(); - assert_eq!( - expected, - type_option - .decode_cell_data(cell_data, &field_type, field_rev) - .unwrap() - .parse::() - .unwrap() - .select_options, - ); - } - - fn assert_single_select_options( - cell_data: String, - type_option: &SingleSelectTypeOption, - field_rev: &FieldRevision, - expected: Vec, - ) { - let field_type: FieldType = field_rev.field_type_rev.into(); - assert_eq!( - expected, - type_option - .decode_cell_data(cell_data, &field_type, field_rev) - .unwrap() - .parse::() - .unwrap() - .select_options, - ); - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/single_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/single_select_type_option.rs new file mode 100644 index 0000000000..3df9107f6e --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/single_select_type_option.rs @@ -0,0 +1,196 @@ +use crate::entities::{FieldType, GridSelectOptionFilter}; +use crate::impl_type_option; +use crate::services::field::select_option::{ + make_selected_select_options, SelectOption, SelectOptionCellContentChangeset, 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}; +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; +use serde::{Deserialize, Serialize}; + +// Single select +#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] +pub struct SingleSelectTypeOption { + #[pb(index = 1)] + pub options: Vec, + + #[pb(index = 2)] + pub disable_color: bool, +} +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); + SelectOptionCellData { + options: self.options.clone(), + select_options, + } + } + + fn options(&self) -> &Vec { + &self.options + } + + fn mut_options(&mut self) -> &mut Vec { + &mut self.options + } +} + +impl CellFilterOperation for SingleSelectTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, _filter: &GridSelectOptionFilter) -> FlowyResult { + if !any_cell_data.is_single_select() { + return Ok(true); + } + let _ids: SelectOptionIds = any_cell_data.try_into()?; + Ok(false) + } +} + +impl CellDataOperation for SingleSelectTypeOption { + fn decode_cell_data( + &self, + cell_data: T, + decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> FlowyResult + where + T: Into, + { + if !decoded_field_type.is_select_option() { + return Ok(DecodedCellData::default()); + } + + let encoded_data = cell_data.into(); + 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()); + } + } + + DecodedCellData::try_from_bytes(cell_data) + } + + fn apply_changeset(&self, changeset: C, _cell_rev: Option) -> Result + where + C: Into, + { + let changeset = changeset.into(); + let select_option_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset)?; + 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); + new_cell_data = insert_option_id; + } else { + tracing::trace!("Delete single select option"); + new_cell_data = "".to_string() + } + + Ok(new_cell_data) + } +} + +#[derive(Default)] +pub struct SingleSelectTypeOptionBuilder(SingleSelectTypeOption); +impl_into_box_type_option_builder!(SingleSelectTypeOptionBuilder); +impl_builder_from_json_str_and_from_bytes!(SingleSelectTypeOptionBuilder, SingleSelectTypeOption); + +impl SingleSelectTypeOptionBuilder { + pub fn option(mut self, opt: SelectOption) -> Self { + self.0.options.push(opt); + self + } +} + +impl TypeOptionBuilder for SingleSelectTypeOptionBuilder { + fn field_type(&self) -> FieldType { + FieldType::SingleSelect + } + + fn entry(&self) -> &dyn TypeOptionDataEntry { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use crate::entities::FieldType; + 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] + fn single_select_test() { + let google_option = SelectOption::new("Google"); + let facebook_option = SelectOption::new("Facebook"); + let twitter_option = SelectOption::new("Twitter"); + let single_select = SingleSelectTypeOptionBuilder::default() + .option(google_option.clone()) + .option(facebook_option.clone()) + .option(twitter_option); + + let field_rev = FieldBuilder::new(single_select) + .name("Platform") + .visibility(true) + .build(); + + 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(); + 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(); + 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) + .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) + .unwrap(); + + assert_single_select_options(cell_data, &type_option, &field_rev, vec![]); + + // Invalid changeset + assert!(type_option.apply_changeset("123", None).is_err()); + } + + fn assert_single_select_options( + cell_data: String, + type_option: &SingleSelectTypeOption, + field_rev: &FieldRevision, + expected: Vec, + ) { + let field_type: FieldType = field_rev.field_type_rev.into(); + assert_eq!( + expected, + type_option + .decode_cell_data(cell_data, &field_type, field_rev) + .unwrap() + .parse::() + .unwrap() + .select_options, + ); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs index 54449a9361..8b0579d6c9 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs @@ -32,9 +32,14 @@ pub struct RichTextTypeOption { } impl_type_option!(RichTextTypeOption, FieldType::RichText); -impl CellFilterOperation for RichTextTypeOption { - fn apply_filter(&self, _cell_data: TextCellData, _filter: &GridTextFilter) -> bool { - false +impl CellFilterOperation for RichTextTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult { + if !any_cell_data.is_text() { + return Ok(true); + } + + let text_cell_data: TextCellData = any_cell_data.try_into()?; + Ok(filter.apply(text_cell_data)) } } @@ -73,16 +78,25 @@ impl CellDataOperation for RichTextTypeOption { } } -pub struct TextCellData(String); -impl std::convert::From for TextCellData { - fn from(any_data: AnyCellData) -> Self { - TextCellData(any_data.cell_data) +pub struct TextCellData(pub String); +impl AsRef for TextCellData { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl std::convert::TryFrom for TextCellData { + type Error = FlowyError; + + fn try_from(value: AnyCellData) -> Result { + Ok(TextCellData(value.cell_data)) } } #[cfg(test)] mod tests { use crate::entities::FieldType; + use crate::services::field::select_option::*; use crate::services::field::FieldBuilder; use crate::services::field::*; use crate::services::row::CellDataOperation; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs index 9cd0c70426..09af0cda8d 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs @@ -1,6 +1,6 @@ use crate::entities::{FieldType, GridTextFilter}; use crate::impl_type_option; -use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; +use crate::services::field::{BoxTypeOptionBuilder, TextCellData, TypeOptionBuilder}; use crate::services::row::{ AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData, EncodedCellData, }; @@ -35,9 +35,14 @@ pub struct URLTypeOption { } impl_type_option!(URLTypeOption, FieldType::URL); -impl CellFilterOperation for URLTypeOption { - fn apply_filter(&self, _cell_data: URLCellData, _filter: &GridTextFilter) -> bool { - false +impl CellFilterOperation for URLTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult { + if !any_cell_data.is_url() { + return Ok(true); + } + + let text_cell_data: TextCellData = any_cell_data.try_into()?; + Ok(filter.apply(&text_cell_data)) } } @@ -121,9 +126,17 @@ impl FromStr for URLCellData { } } -impl std::convert::From for URLCellData { - fn from(any_cell_data: AnyCellData) -> Self { - URLCellData::from_str(&any_cell_data.cell_data).unwrap_or_default() +// impl std::convert::From for URLCellData { +// fn from(any_cell_data: AnyCellData) -> Self { +// URLCellData::from_str(&any_cell_data.cell_data).unwrap_or_default() +// } +// } + +impl std::convert::TryFrom for URLCellData { + type Error = (); + + fn try_from(_value: AnyCellData) -> Result { + todo!() } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/util.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs similarity index 100% rename from frontend/rust-lib/flowy-grid/src/services/field/type_options/util.rs rename to frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/mod.rs new file mode 100644 index 0000000000..c7aa386fe9 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/mod.rs @@ -0,0 +1,4 @@ +mod cell_data_util; + +pub use crate::services::field::select_option::*; +pub use cell_data_util::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs index b6342636f3..2e37287935 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs +++ b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs @@ -185,54 +185,61 @@ fn filter_cell( Some( field_rev .get_type_option_entry::(field_type_rev)? - .apply_filter(any_cell_data.into(), filter.value()), + .apply_filter(any_cell_data, filter.value()) + .ok(), ) }), FieldType::Number => filter_cache.number_filter.get(&filter_id).and_then(|filter| { Some( field_rev .get_type_option_entry::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()), + .apply_filter(any_cell_data, filter.value()) + .ok(), ) }), FieldType::DateTime => filter_cache.date_filter.get(&filter_id).and_then(|filter| { Some( field_rev .get_type_option_entry::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()), + .apply_filter(any_cell_data, filter.value()) + .ok(), ) }), FieldType::SingleSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| { Some( field_rev .get_type_option_entry::(field_type_rev)? - .apply_filter(any_cell_data.into(), filter.value()), + .apply_filter(any_cell_data, filter.value()) + .ok(), ) }), FieldType::MultiSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| { Some( field_rev .get_type_option_entry::(field_type_rev)? - .apply_filter(any_cell_data.into(), filter.value()), + .apply_filter(any_cell_data, filter.value()) + .ok(), ) }), FieldType::Checkbox => filter_cache.checkbox_filter.get(&filter_id).and_then(|filter| { Some( field_rev .get_type_option_entry::(field_type_rev)? - .apply_filter(any_cell_data.into(), filter.value()), + .apply_filter(any_cell_data, filter.value()) + .ok(), ) }), FieldType::URL => filter_cache.url_filter.get(&filter_id).and_then(|filter| { Some( field_rev .get_type_option_entry::(field_type_rev)? - .apply_filter(any_cell_data.into(), filter.value()), + .apply_filter(any_cell_data, filter.value()) + .ok(), ) }), }?; - let is_visible = !is_hidden; + let is_visible = !is_hidden.unwrap_or(false); match filter_result.visible_by_field_id.get(&filter_id) { None => { if is_visible { diff --git a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs index 2642d83264..15368d42c3 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs @@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize}; use std::fmt::Formatter; use std::str::FromStr; -pub trait CellFilterOperation> { - fn apply_filter(&self, cell_data: C, filter: &T) -> bool; +pub trait CellFilterOperation { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &T) -> FlowyResult; } pub trait CellDataOperation { @@ -83,6 +83,25 @@ impl std::convert::TryFrom<&CellRevision> for AnyCellData { } } +impl std::convert::TryFrom<&Option> for AnyCellData { + type Error = FlowyError; + + fn try_from(value: &Option) -> Result { + 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> for AnyCellData { + type Error = FlowyError; + + fn try_from(value: Option) -> Result { + Self::try_from(&value) + } +} + impl AnyCellData { pub fn new(content: String, field_type: FieldType) -> Self { AnyCellData { @@ -119,6 +138,10 @@ impl AnyCellData { 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 } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs index 568c8ed12a..99c6617c7e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs @@ -1,4 +1,4 @@ -use crate::services::field::SelectOptionCellContentChangeset; +use crate::services::field::select_option::SelectOptionCellContentChangeset; use crate::services::row::apply_cell_data_changeset; use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::revision::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT}; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/cell_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/cell_test.rs index 98b50436ab..803e17e2b5 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/cell_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/cell_test.rs @@ -2,7 +2,8 @@ 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::{MultiSelectTypeOption, SelectOptionCellContentChangeset, SingleSelectTypeOption}; +use flowy_grid::services::field::select_option::SelectOptionCellContentChangeset; +use flowy_grid::services::field::{MultiSelectTypeOption, SingleSelectTypeOption}; #[tokio::test] async fn grid_cell_update() { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/field_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/field_test.rs index c8eb1a541a..00428a8fc4 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/field_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/field_test.rs @@ -1,7 +1,8 @@ use crate::grid::field_util::*; use crate::grid::script::EditorScript::*; use crate::grid::script::*; -use flowy_grid::services::field::{SelectOption, SingleSelectTypeOption}; +use flowy_grid::services::field::select_option::SelectOption; +use flowy_grid::services::field::SingleSelectTypeOption; use flowy_grid_data_model::revision::TypeOptionDataEntry; use flowy_sync::entities::grid::FieldChangesetParams; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/field_util.rs b/frontend/rust-lib/flowy-grid/tests/grid/field_util.rs index ea16cde778..ca094651f5 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/field_util.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/field_util.rs @@ -1,6 +1,6 @@ -use flowy_grid::services::field::*; - use flowy_grid::entities::*; +use flowy_grid::services::field::select_option::SelectOption; +use flowy_grid::services::field::*; use flowy_grid_data_model::revision::*; pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/row_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/row_test.rs index 7aa7450120..c28c2a7537 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/row_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/row_test.rs @@ -4,9 +4,8 @@ use crate::grid::script::EditorScript::*; use crate::grid::script::*; use chrono::NaiveDateTime; use flowy_grid::entities::FieldType; -use flowy_grid::services::field::{ - DateCellData, MultiSelectTypeOption, SingleSelectTypeOption, SELECTION_IDS_SEPARATOR, -}; +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_data_model::revision::RowMetaChangeset; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/script.rs index e6c8b901a5..e0ec211ba3 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/script.rs @@ -18,6 +18,7 @@ use std::sync::Arc; use std::time::Duration; use strum::EnumCount; use tokio::time::sleep; +use flowy_grid::services::field::select_option::SelectOption; use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, FieldChangesetParams, GridSettingChangesetParams}; pub enum EditorScript {