feat: checklist improve (#2653)

* feat: improve checklist

* feat: reimplement checklist

* test: fix
This commit is contained in:
Nathan.fooo
2023-05-30 09:41:33 +08:00
committed by GitHub
parent dc73df4203
commit 107662dceb
54 changed files with 897 additions and 501 deletions

View File

@ -592,7 +592,7 @@ impl FieldType {
self == &MULTI_SELECT_FIELD || self == &SINGLE_SELECT_FIELD
}
pub fn is_check_list(&self) -> bool {
pub fn is_checklist(&self) -> bool {
self == &CHECKLIST_FIELD
}

View File

@ -0,0 +1,98 @@
use crate::entities::parser::NotEmptyStr;
use crate::entities::SelectOptionPB;
use crate::services::field::checklist_type_option::ChecklistCellData;
use crate::services::field::SelectOption;
use collab_database::rows::RowId;
use flowy_derive::ProtoBuf;
use flowy_error::{ErrorCode, FlowyError};
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct ChecklistCellDataPB {
#[pb(index = 1)]
pub options: Vec<SelectOptionPB>,
#[pb(index = 2)]
pub selected_options: Vec<SelectOptionPB>,
#[pb(index = 3)]
pub(crate) percentage: f64,
}
impl From<ChecklistCellData> for ChecklistCellDataPB {
fn from(cell_data: ChecklistCellData) -> Self {
let selected_options = cell_data.selected_options();
let percentage = cell_data.percentage_complete();
Self {
options: cell_data
.options
.into_iter()
.map(|option| option.into())
.collect(),
selected_options: selected_options
.into_iter()
.map(|option| option.into())
.collect(),
percentage,
}
}
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct ChecklistCellDataChangesetPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2)]
pub field_id: String,
#[pb(index = 3)]
pub row_id: String,
#[pb(index = 4)]
pub insert_options: Vec<String>,
#[pb(index = 5)]
pub selected_option_ids: Vec<String>,
#[pb(index = 6)]
pub delete_option_ids: Vec<String>,
#[pb(index = 7)]
pub update_options: Vec<SelectOptionPB>,
}
#[derive(Debug)]
pub struct ChecklistCellDataChangesetParams {
pub view_id: String,
pub field_id: String,
pub row_id: RowId,
pub insert_options: Vec<String>,
pub selected_option_ids: Vec<String>,
pub delete_option_ids: Vec<String>,
pub update_options: Vec<SelectOption>,
}
impl TryInto<ChecklistCellDataChangesetParams> for ChecklistCellDataChangesetPB {
type Error = FlowyError;
fn try_into(self) -> Result<ChecklistCellDataChangesetParams, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
Ok(ChecklistCellDataChangesetParams {
view_id: view_id.0,
field_id: field_id.0,
row_id: RowId::from(row_id.0),
insert_options: self.insert_options,
selected_option_ids: self.selected_option_ids,
delete_option_ids: self.delete_option_ids,
update_options: self
.update_options
.into_iter()
.map(SelectOption::from)
.collect(),
})
}
}

View File

@ -1,4 +1,5 @@
mod checkbox_entities;
mod checklist_entities;
mod date_entities;
mod number_entities;
mod select_option;
@ -6,6 +7,7 @@ mod text_entities;
mod url_entities;
pub use checkbox_entities::*;
pub use checklist_entities::*;
pub use date_entities::*;
pub use number_entities::*;
pub use select_option::*;

View File

@ -1,8 +1,8 @@
use crate::entities::parser::NotEmptyStr;
use crate::entities::{CellIdPB, CellIdParams};
use crate::services::field::checklist_type_option::ChecklistTypeOption;
use crate::services::field::{
ChecklistTypeOption, MultiSelectTypeOption, SelectOption, SelectOptionColor,
SingleSelectTypeOption,
MultiSelectTypeOption, SelectOption, SelectOptionColor, SingleSelectTypeOption,
};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode;
@ -287,34 +287,19 @@ impl From<MultiSelectTypeOptionPB> for MultiSelectTypeOption {
#[derive(Clone, Debug, Default, ProtoBuf)]
pub struct ChecklistTypeOptionPB {
#[pb(index = 1)]
pub options: Vec<SelectOptionPB>,
#[pb(index = 2)]
pub disable_color: bool,
pub config: String,
}
impl From<ChecklistTypeOption> for ChecklistTypeOptionPB {
fn from(data: ChecklistTypeOption) -> Self {
fn from(_data: ChecklistTypeOption) -> Self {
Self {
options: data
.options
.into_iter()
.map(|option| option.into())
.collect(),
disable_color: data.disable_color,
config: "".to_string(),
}
}
}
impl From<ChecklistTypeOptionPB> for ChecklistTypeOption {
fn from(data: ChecklistTypeOptionPB) -> Self {
Self {
options: data
.options
.into_iter()
.map(|option| option.into())
.collect(),
disable_color: data.disable_color,
}
fn from(_data: ChecklistTypeOptionPB) -> Self {
Self
}
}

View File

@ -12,6 +12,7 @@ use crate::entities::*;
use crate::manager::DatabaseManager2;
use crate::services::cell::CellBuilder;
use crate::services::field::checklist_type_option::ChecklistCellChangeset;
use crate::services::field::{
type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
};
@ -469,6 +470,38 @@ pub(crate) async fn update_select_option_cell_handler(
Ok(())
}
#[tracing::instrument(level = "trace", skip_all, err)]
pub(crate) async fn get_checklist_cell_data_handler(
data: AFPluginData<CellIdPB>,
manager: AFPluginState<Arc<DatabaseManager2>>,
) -> DataResult<ChecklistCellDataPB, FlowyError> {
let params: CellIdParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
let data = database_editor
.get_checklist_option(params.row_id, &params.field_id)
.await;
data_result_ok(data)
}
#[tracing::instrument(level = "trace", skip_all, err)]
pub(crate) async fn update_checklist_cell_handler(
data: AFPluginData<ChecklistCellDataChangesetPB>,
manager: AFPluginState<Arc<DatabaseManager2>>,
) -> Result<(), FlowyError> {
let params: ChecklistCellDataChangesetParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
let changeset = ChecklistCellChangeset {
insert_options: params.insert_options,
selected_option_ids: params.selected_option_ids,
delete_option_ids: params.delete_option_ids,
update_options: params.update_options,
};
database_editor
.set_checklist_options(&params.view_id, params.row_id, &params.field_id, changeset)
.await?;
Ok(())
}
#[tracing::instrument(level = "trace", skip_all, err)]
pub(crate) async fn update_date_cell_handler(
data: AFPluginData<DateChangesetPB>,

View File

@ -44,6 +44,9 @@ pub fn init(database_manager: Arc<DatabaseManager2>) -> AFPlugin {
.event(DatabaseEvent::DeleteSelectOption, delete_select_option_handler)
.event(DatabaseEvent::GetSelectOptionCellData, get_select_option_handler)
.event(DatabaseEvent::UpdateSelectOptionCell, update_select_option_cell_handler)
// Checklist
.event(DatabaseEvent::GetChecklistCellData, get_checklist_cell_data_handler)
.event(DatabaseEvent::UpdateChecklistCell, update_checklist_cell_handler)
// Date
.event(DatabaseEvent::UpdateDateCell, update_date_cell_handler)
// Group
@ -227,6 +230,12 @@ pub enum DatabaseEvent {
#[event(input = "SelectOptionCellChangesetPB")]
UpdateSelectOptionCell = 72,
#[event(input = "CellIdPB", output = "ChecklistCellDataPB")]
GetChecklistCellData = 73,
#[event(input = "ChecklistCellDataChangesetPB")]
UpdateChecklistCell = 74,
/// [UpdateDateCell] event is used to update a date cell's data. [DateChangesetPB]
/// contains the date and the time string. It can be cast to [CellChangesetPB] that
/// will be used by the `update_cell` function.

View File

@ -9,43 +9,47 @@ use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use crate::entities::FieldType;
use crate::services::cell::{CellCache, CellProtobufBlob};
use crate::services::field::checklist_type_option::ChecklistCellChangeset;
use crate::services::field::*;
use crate::services::group::make_no_status_group;
/// Decode the opaque cell data into readable format content
pub trait CellDataDecoder: TypeOption {
///
/// Tries to decode the opaque cell string to `decoded_field_type`'s cell data. Sometimes, the `field_type`
/// of the `FieldRevision` is not equal to the `decoded_field_type`(This happened When switching
/// the field type of the `FieldRevision` to another field type). So the cell data is need to do
/// Tries to decode the [Cell] to `decoded_field_type`'s cell data. Sometimes, the `field_type`
/// of the `Field` is not equal to the `decoded_field_type`(This happened When switching
/// the field type of the `Field` to another field type). So the cell data is need to do
/// some transformation.
///
/// For example, the current field type of the `FieldRevision` is a checkbox. When switching the field
/// For example, the current field type of the `Field` is a checkbox. When switching the field
/// type from the checkbox to single select, it will create two new options,`Yes` and `No`, if they don't exist.
/// But the data of the cell doesn't change. We can't iterate all the rows to transform the cell
/// data that can be parsed by the current field type. One approach is to transform the cell data
/// when it get read. For the moment, the cell data is a string, `Yes` or `No`. It needs to compare
/// with the option's name, if match return the id of the option.
fn decode_cell_str(
/// when reading.
fn decode_cell(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
field: &Field,
) -> FlowyResult<<Self as TypeOption>::CellData>;
/// Same as `decode_cell_data` does but Decode the cell data to readable `String`
/// Decode the cell data to readable `String`
/// For example, The string of the Multi-Select cell will be a list of the option's name
/// separated by a comma.
fn decode_cell_data_to_str(&self, cell_data: <Self as TypeOption>::CellData) -> String;
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String;
fn decode_cell_to_str(&self, cell: &Cell) -> String;
/// Same as [CellDataDecoder::stringify_cell_data] but the input parameter is the [Cell]
fn stringify_cell(&self, cell: &Cell) -> String;
}
pub trait CellDataChangeset: TypeOption {
/// The changeset is able to parse into the concrete data struct if `TypeOption::CellChangeset`
/// implements the `FromCellChangesetString` trait.
/// For example,the SelectOptionCellChangeset,DateCellChangeset. etc.
/// # Arguments
///
/// * `changeset`: the cell changeset that represents the changes of the cell.
/// * `cell`: the data of the cell. It will be None if the cell does not contain any data.
fn apply_changeset(
&self,
changeset: <Self as TypeOption>::CellChangeset,
@ -109,13 +113,12 @@ pub fn get_cell_protobuf(
///
/// # Arguments
///
/// * `cell_str`: the opaque cell string that can be decoded by corresponding structs that implement the
/// `FromCellString` trait.
/// * `cell`: the opaque cell string that can be decoded by corresponding structs.
/// * `from_field_type`: the original field type of the passed-in cell data. Check the `TypeCellData`
/// that is used to save the origin field type of the cell data.
/// * `to_field_type`: decode the passed-in cell data to this field type. It will use the to_field_type's
/// TypeOption to decode this cell data.
/// * `field_rev`: used to get the corresponding TypeOption for the specified field type.
/// * `field`: used to get the corresponding TypeOption for the specified field type.
///
/// returns: CellBytes
///
@ -154,11 +157,10 @@ pub fn try_decode_cell_to_cell_data<T: Default + 'static>(
///
/// # Arguments
///
/// * `cell_str`: the opaque cell string that can be decoded by corresponding structs that implement the
/// `FromCellString` trait.
/// * `cell`: the opaque cell string that can be decoded by corresponding structs
/// * `to_field_type`: the cell will be decoded to this field type's cell data.
/// * `from_field_type`: the original field type of the passed-in cell data.
/// * `field_rev`: used to get the corresponding TypeOption for the specified field type.
/// * `field`: used to get the corresponding TypeOption for the specified field type.
///
/// returns: String
pub fn stringify_cell_data(
@ -223,6 +225,15 @@ pub fn insert_select_option_cell(option_ids: Vec<String>, field: &Field) -> Cell
apply_cell_changeset(changeset, None, field, None).unwrap()
}
pub fn insert_checklist_cell(insert_options: Vec<String>, field: &Field) -> Cell {
let changeset = ChecklistCellChangeset {
insert_options,
..Default::default()
}
.to_cell_changeset_str();
apply_cell_changeset(changeset, None, field, None).unwrap()
}
pub fn delete_select_option_cell(option_ids: Vec<String>, field: &Field) -> Cell {
let changeset =
SelectOptionCellChangeset::from_delete_options(option_ids).to_cell_changeset_str();
@ -434,4 +445,15 @@ impl<'a> CellBuilder<'a> {
},
}
}
pub fn insert_checklist_cell(&mut self, field_id: &str, option_names: Vec<String>) {
match self.field_maps.get(&field_id.to_owned()) {
None => tracing::warn!("Can't find the field with id: {}", field_id),
Some(field) => {
self.cells.insert(
field_id.to_owned(),
insert_checklist_cell(option_names, field),
);
},
}
}
}

View File

@ -15,8 +15,8 @@ use flowy_task::TaskDispatcher;
use lib_infra::future::{to_fut, Fut};
use crate::entities::{
CalendarEventPB, CellChangesetNotifyPB, CellPB, DatabaseFieldChangesetPB, DatabasePB,
DatabaseViewSettingPB, DeleteFilterParams, DeleteGroupParams, DeleteSortParams,
CalendarEventPB, CellChangesetNotifyPB, CellPB, ChecklistCellDataPB, DatabaseFieldChangesetPB,
DatabasePB, DatabaseViewSettingPB, DeleteFilterParams, DeleteGroupParams, DeleteSortParams,
FieldChangesetParams, FieldIdPB, FieldPB, FieldType, GroupPB, IndexFieldPB, InsertedRowPB,
LayoutSettingParams, RepeatedFilterPB, RepeatedGroupPB, RepeatedSortPB, RowPB, RowsChangePB,
SelectOptionCellDataPB, SelectOptionPB, UpdateFilterParams, UpdateSortParams,
@ -28,6 +28,7 @@ use crate::services::cell::{
};
use crate::services::database::util::database_view_setting_pb_from_view;
use crate::services::database_view::{DatabaseViewChanged, DatabaseViewData, DatabaseViews};
use crate::services::field::checklist_type_option::{ChecklistCellChangeset, ChecklistCellData};
use crate::services::field::{
default_type_option_data_from_type, select_type_option_from_field, transform_type_option,
type_option_data_from_pb_or_default, type_option_to_pb, SelectOptionCellChangeset,
@ -552,6 +553,7 @@ impl DatabaseEditor {
Ok(())
}
/// Just create an option for the field's type option. The option is save to the database.
pub async fn create_select_option(
&self,
field_id: &str,
@ -652,6 +654,33 @@ impl DatabaseEditor {
}
}
pub async fn get_checklist_option(&self, row_id: RowId, field_id: &str) -> ChecklistCellDataPB {
let row_cell = self.database.lock().get_cell(field_id, &row_id);
let cell_data = match row_cell {
None => ChecklistCellData::default(),
Some(row_cell) => ChecklistCellData::from(&row_cell.cell),
};
ChecklistCellDataPB::from(cell_data)
}
pub async fn set_checklist_options(
&self,
view_id: &str,
row_id: RowId,
field_id: &str,
changeset: ChecklistCellChangeset,
) -> FlowyResult<()> {
let field = self.database.lock().fields.get_field(field_id).ok_or(
FlowyError::record_not_found().context(format!("Field with id:{} not found", &field_id)),
)?;
debug_assert!(FieldType::from(field.field_type).is_checklist());
self
.update_cell_with_changeset(view_id, row_id, field_id, changeset)
.await?;
Ok(())
}
#[tracing::instrument(level = "trace", skip_all, err)]
pub async fn load_groups(&self, view_id: &str) -> FlowyResult<RepeatedGroupPB> {
let view = self.database_views.get_view_editor(view_id).await?;

View File

@ -41,7 +41,7 @@ mod tests {
) {
assert_eq!(
type_option
.decode_cell_str(
.decode_cell(
&CheckboxCellData::from_cell_str(input_str).unwrap().into(),
field_type,
field

View File

@ -67,20 +67,20 @@ impl From<CheckboxTypeOption> for TypeOptionData {
}
impl TypeOptionCellData for CheckboxTypeOption {
fn convert_to_protobuf(
fn protobuf_encode(
&self,
cell_data: <Self as TypeOption>::CellData,
) -> <Self as TypeOption>::CellProtobufType {
cell_data
}
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
Ok(CheckboxCellData::from(cell))
}
}
impl CellDataDecoder for CheckboxTypeOption {
fn decode_cell_str(
fn decode_cell(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
@ -90,14 +90,14 @@ impl CellDataDecoder for CheckboxTypeOption {
return Ok(Default::default());
}
self.decode_cell(cell)
self.parse_cell(cell)
}
fn decode_cell_data_to_str(&self, cell_data: <Self as TypeOption>::CellData) -> String {
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
cell_data.to_string()
}
fn decode_cell_to_str(&self, cell: &Cell) -> String {
fn stringify_cell(&self, cell: &Cell) -> String {
Self::CellData::from(cell).to_string()
}
}

View File

@ -0,0 +1,207 @@
use crate::entities::{ChecklistCellDataPB, ChecklistFilterPB, FieldType, SelectOptionPB};
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
use crate::services::field::checklist_type_option::{ChecklistCellChangeset, ChecklistCellData};
use crate::services::field::{
SelectOption, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
TypeOptionCellDataFilter, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
};
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
use collab_database::rows::Cell;
use flowy_error::FlowyResult;
use std::cmp::Ordering;
#[derive(Debug, Clone, Default)]
pub struct ChecklistTypeOption;
impl TypeOption for ChecklistTypeOption {
type CellData = ChecklistCellData;
type CellChangeset = ChecklistCellChangeset;
type CellProtobufType = ChecklistCellDataPB;
type CellFilter = ChecklistFilterPB;
}
impl From<TypeOptionData> for ChecklistTypeOption {
fn from(_data: TypeOptionData) -> Self {
Self
}
}
impl From<ChecklistTypeOption> for TypeOptionData {
fn from(_data: ChecklistTypeOption) -> Self {
TypeOptionDataBuilder::new().build()
}
}
impl TypeOptionCellData for ChecklistTypeOption {
fn protobuf_encode(
&self,
cell_data: <Self as TypeOption>::CellData,
) -> <Self as TypeOption>::CellProtobufType {
let percentage = cell_data.percentage_complete();
let selected_options = cell_data
.options
.iter()
.filter(|option| cell_data.selected_option_ids.contains(&option.id))
.map(|option| SelectOptionPB::from(option.clone()))
.collect();
let options = cell_data
.options
.into_iter()
.map(SelectOptionPB::from)
.collect();
ChecklistCellDataPB {
options,
selected_options,
percentage,
}
}
fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
Ok(ChecklistCellData::from(cell))
}
}
impl CellDataChangeset for ChecklistTypeOption {
fn apply_changeset(
&self,
changeset: <Self as TypeOption>::CellChangeset,
cell: Option<Cell>,
) -> FlowyResult<(Cell, <Self as TypeOption>::CellData)> {
match cell {
Some(cell) => {
let mut cell_data = self.parse_cell(&cell)?;
update_cell_data_with_changeset(&mut cell_data, changeset);
Ok((Cell::from(cell_data.clone()), cell_data))
},
None => {
let cell_data = ChecklistCellData::from_options(changeset.insert_options);
Ok((Cell::from(cell_data.clone()), cell_data))
},
}
}
}
#[inline]
fn update_cell_data_with_changeset(
cell_data: &mut ChecklistCellData,
mut changeset: ChecklistCellChangeset,
) {
// Delete the options
cell_data
.options
.retain(|option| !changeset.delete_option_ids.contains(&option.id));
cell_data
.selected_option_ids
.retain(|option_id| !changeset.delete_option_ids.contains(option_id));
// Insert new options
changeset.insert_options.retain(|option_name| {
!cell_data
.options
.iter()
.any(|option| option.name == *option_name)
});
changeset
.insert_options
.into_iter()
.for_each(|option_name| {
let option = SelectOption::new(&option_name);
cell_data.options.push(option.clone());
});
// Update options
changeset
.update_options
.into_iter()
.for_each(|updated_option| {
if let Some(option) = cell_data
.options
.iter_mut()
.find(|option| option.id == updated_option.id)
{
option.name = updated_option.name;
}
});
// Select the options
changeset
.selected_option_ids
.into_iter()
.for_each(|option_id| {
if let Some(index) = cell_data
.selected_option_ids
.iter()
.position(|id| **id == option_id)
{
cell_data.selected_option_ids.remove(index);
} else {
cell_data.selected_option_ids.push(option_id);
}
});
}
impl CellDataDecoder for ChecklistTypeOption {
fn decode_cell(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
_field: &Field,
) -> FlowyResult<<Self as TypeOption>::CellData> {
if !decoded_field_type.is_checklist() {
return Ok(Default::default());
}
self.parse_cell(cell)
}
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
cell_data
.selected_options()
.into_iter()
.map(|option| option.name)
.collect::<Vec<_>>()
.join(SELECTION_IDS_SEPARATOR)
}
fn stringify_cell(&self, cell: &Cell) -> String {
let cell_data = self.parse_cell(cell).unwrap_or_default();
self.stringify_cell_data(cell_data)
}
}
impl TypeOptionCellDataFilter for ChecklistTypeOption {
fn apply_filter(
&self,
filter: &<Self as TypeOption>::CellFilter,
field_type: &FieldType,
cell_data: &<Self as TypeOption>::CellData,
) -> bool {
if !field_type.is_checklist() {
return true;
}
let selected_options = cell_data.selected_options();
filter.is_visible(&cell_data.options, &selected_options)
}
}
impl TypeOptionCellDataCompare for ChecklistTypeOption {
fn apply_cmp(
&self,
cell_data: &<Self as TypeOption>::CellData,
other_cell_data: &<Self as TypeOption>::CellData,
) -> Ordering {
let left = cell_data.percentage_complete();
let right = other_cell_data.percentage_complete();
if left > right {
Ordering::Greater
} else if left < right {
Ordering::Less
} else {
Ordering::Equal
}
}
}
impl TypeOptionTransform for ChecklistTypeOption {}

View File

@ -0,0 +1,107 @@
use crate::entities::FieldType;
use crate::services::cell::{FromCellChangeset, ToCellChangeset};
use crate::services::field::{SelectOption, CELL_DATA};
use collab::core::any_map::AnyMapExtension;
use collab_database::rows::{new_cell_builder, Cell};
use flowy_error::{internal_error, FlowyResult};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct ChecklistCellData {
pub options: Vec<SelectOption>,
pub selected_option_ids: Vec<String>,
}
impl ToString for ChecklistCellData {
fn to_string(&self) -> String {
serde_json::to_string(self).unwrap_or_default()
}
}
impl ChecklistCellData {
pub fn selected_options(&self) -> Vec<SelectOption> {
self
.options
.iter()
.filter(|option| self.selected_option_ids.contains(&option.id))
.cloned()
.collect()
}
pub fn percentage_complete(&self) -> f64 {
let selected_options = self.selected_option_ids.len();
let total_options = self.options.len();
if total_options == 0 {
return 0.0;
}
(selected_options as f64) / (total_options as f64)
}
pub fn from_options(options: Vec<String>) -> Self {
let options = options
.into_iter()
.map(|option_name| SelectOption::new(&option_name))
.collect();
Self {
options,
..Default::default()
}
}
}
impl From<&Cell> for ChecklistCellData {
fn from(cell: &Cell) -> Self {
cell
.get_str_value(CELL_DATA)
.map(|data| serde_json::from_str::<ChecklistCellData>(&data).unwrap_or_default())
.unwrap_or_default()
}
}
impl From<ChecklistCellData> for Cell {
fn from(cell_data: ChecklistCellData) -> Self {
let data = serde_json::to_string(&cell_data).unwrap_or_default();
new_cell_builder(FieldType::Checklist)
.insert_str_value(CELL_DATA, data)
.build()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ChecklistCellChangeset {
/// List of option names that will be inserted
pub insert_options: Vec<String>,
pub selected_option_ids: Vec<String>,
pub delete_option_ids: Vec<String>,
pub update_options: Vec<SelectOption>,
}
impl FromCellChangeset for ChecklistCellChangeset {
fn from_changeset(changeset: String) -> FlowyResult<Self>
where
Self: Sized,
{
serde_json::from_str::<ChecklistCellChangeset>(&changeset).map_err(internal_error)
}
}
impl ToCellChangeset for ChecklistCellChangeset {
fn to_cell_changeset_str(&self) -> String {
serde_json::to_string(self).unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
#[test]
fn test() {
let a = 1;
let b = 2;
let c = (a as f32) / (b as f32);
println!("{}", c);
}
}

View File

@ -0,0 +1,5 @@
mod checklist;
mod checklist_entities;
pub use checklist::*;
pub use checklist_entities::*;

View File

@ -506,9 +506,9 @@ mod tests {
field: &Field,
) -> String {
let decoded_data = type_option
.decode_cell_str(cell, &FieldType::DateTime, field)
.decode_cell(cell, &FieldType::DateTime, field)
.unwrap();
let decoded_data = type_option.convert_to_protobuf(decoded_data);
let decoded_data = type_option.protobuf_encode(decoded_data);
if include_time {
format!("{} {}", decoded_data.date, decoded_data.time)
.trim_end()

View File

@ -66,14 +66,14 @@ impl From<DateTypeOption> for TypeOptionData {
}
impl TypeOptionCellData for DateTypeOption {
fn convert_to_protobuf(
fn protobuf_encode(
&self,
cell_data: <Self as TypeOption>::CellData,
) -> <Self as TypeOption>::CellProtobufType {
self.today_desc_from_timestamp(cell_data)
}
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
Ok(DateCellData::from(cell))
}
}
@ -156,7 +156,7 @@ impl DateTypeOption {
impl TypeOptionTransform for DateTypeOption {}
impl CellDataDecoder for DateTypeOption {
fn decode_cell_str(
fn decode_cell(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
@ -170,16 +170,16 @@ impl CellDataDecoder for DateTypeOption {
return Ok(Default::default());
}
self.decode_cell(cell)
self.parse_cell(cell)
}
fn decode_cell_data_to_str(&self, cell_data: <Self as TypeOption>::CellData) -> String {
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
self.today_desc_from_timestamp(cell_data).date
}
fn decode_cell_to_str(&self, cell: &Cell) -> String {
fn stringify_cell(&self, cell: &Cell) -> String {
let cell_data = Self::CellData::from(cell);
self.decode_cell_data_to_str(cell_data)
self.stringify_cell_data(cell_data)
}
}

View File

@ -1,4 +1,5 @@
pub mod checkbox_type_option;
pub mod checklist_type_option;
pub mod date_type_option;
pub mod number_type_option;
pub mod selection_type_option;

View File

@ -89,7 +89,7 @@ mod tests {
) {
assert_eq!(
type_option
.decode_cell_str(
.decode_cell(
&NumberCellData(input_str.to_owned()).into(),
field_type,
field

View File

@ -96,14 +96,14 @@ impl From<NumberTypeOption> for TypeOptionData {
}
impl TypeOptionCellData for NumberTypeOption {
fn convert_to_protobuf(
fn protobuf_encode(
&self,
cell_data: <Self as TypeOption>::CellData,
) -> <Self as TypeOption>::CellProtobufType {
ProtobufStr::from(cell_data.0)
}
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
Ok(NumberCellData::from(cell))
}
}
@ -171,7 +171,7 @@ impl NumberTypeOption {
impl TypeOptionTransform for NumberTypeOption {}
impl CellDataDecoder for NumberTypeOption {
fn decode_cell_str(
fn decode_cell(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
@ -181,22 +181,22 @@ impl CellDataDecoder for NumberTypeOption {
return Ok(Default::default());
}
let num_cell_data = self.decode_cell(cell)?;
let num_cell_data = self.parse_cell(cell)?;
Ok(NumberCellData::from(
self.format_cell_data(&num_cell_data)?.to_string(),
))
}
fn decode_cell_data_to_str(&self, cell_data: <Self as TypeOption>::CellData) -> String {
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
match self.format_cell_data(&cell_data) {
Ok(cell_data) => cell_data.to_string(),
Err(_) => "".to_string(),
}
}
fn decode_cell_to_str(&self, cell: &Cell) -> String {
fn stringify_cell(&self, cell: &Cell) -> String {
let cell_data = Self::CellData::from(cell);
self.decode_cell_data_to_str(cell_data)
self.stringify_cell_data(cell_data)
}
}

View File

@ -1,14 +1,13 @@
use crate::entities::{ChecklistFilterConditionPB, ChecklistFilterPB};
use crate::services::field::{SelectOption, SelectedSelectOptions};
use crate::services::field::SelectOption;
impl ChecklistFilterPB {
pub fn is_visible(
&self,
all_options: &[SelectOption],
selected_options: &SelectedSelectOptions,
selected_options: &[SelectOption],
) -> bool {
let selected_option_ids = selected_options
.options
.iter()
.map(|option| option.id.as_str())
.collect::<Vec<&str>>();

View File

@ -1,143 +0,0 @@
use crate::entities::{ChecklistFilterPB, FieldType, SelectOptionCellDataPB};
use crate::services::cell::CellDataChangeset;
use crate::services::field::{
SelectOption, SelectOptionCellChangeset, SelectOptionIds, SelectTypeOptionSharedAction,
SelectedSelectOptions, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
TypeOptionCellDataFilter,
};
use collab::core::any_map::AnyMapExtension;
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
use collab_database::rows::Cell;
use flowy_error::FlowyResult;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
// Multiple select
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ChecklistTypeOption {
pub options: Vec<SelectOption>,
pub disable_color: bool,
}
impl TypeOption for ChecklistTypeOption {
type CellData = SelectOptionIds;
type CellChangeset = SelectOptionCellChangeset;
type CellProtobufType = SelectOptionCellDataPB;
type CellFilter = ChecklistFilterPB;
}
impl From<TypeOptionData> for ChecklistTypeOption {
fn from(data: TypeOptionData) -> Self {
data
.get_str_value("content")
.map(|s| serde_json::from_str::<ChecklistTypeOption>(&s).unwrap_or_default())
.unwrap_or_default()
}
}
impl From<ChecklistTypeOption> for TypeOptionData {
fn from(data: ChecklistTypeOption) -> Self {
let content = serde_json::to_string(&data).unwrap_or_default();
TypeOptionDataBuilder::new()
.insert_str_value("content", content)
.build()
}
}
impl TypeOptionCellData for ChecklistTypeOption {
fn convert_to_protobuf(
&self,
cell_data: <Self as TypeOption>::CellData,
) -> <Self as TypeOption>::CellProtobufType {
self.get_selected_options(cell_data).into()
}
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
Ok(SelectOptionIds::from(cell))
}
}
impl SelectTypeOptionSharedAction for ChecklistTypeOption {
fn number_of_max_options(&self) -> Option<usize> {
None
}
fn to_type_option_data(&self) -> TypeOptionData {
self.clone().into()
}
fn options(&self) -> &Vec<SelectOption> {
&self.options
}
fn mut_options(&mut self) -> &mut Vec<SelectOption> {
&mut self.options
}
}
impl CellDataChangeset for ChecklistTypeOption {
fn apply_changeset(
&self,
changeset: <Self as TypeOption>::CellChangeset,
cell: Option<Cell>,
) -> FlowyResult<(Cell, <Self as TypeOption>::CellData)> {
let insert_option_ids = changeset
.insert_option_ids
.into_iter()
.filter(|insert_option_id| {
self
.options
.iter()
.any(|option| &option.id == insert_option_id)
})
.collect::<Vec<String>>();
let select_option_ids = match cell {
None => SelectOptionIds::from(insert_option_ids),
Some(cell) => {
let mut select_ids = SelectOptionIds::from(&cell);
for insert_option_id in insert_option_ids {
if !select_ids.contains(&insert_option_id) {
select_ids.push(insert_option_id);
}
}
for delete_option_id in changeset.delete_option_ids {
select_ids.retain(|id| id != &delete_option_id);
}
select_ids
},
};
Ok((
select_option_ids.to_cell_data(FieldType::Checklist),
select_option_ids,
))
}
}
impl TypeOptionCellDataFilter for ChecklistTypeOption {
fn apply_filter(
&self,
filter: &<Self as TypeOption>::CellFilter,
field_type: &FieldType,
cell_data: &<Self as TypeOption>::CellData,
) -> bool {
if !field_type.is_check_list() {
return true;
}
let selected_options =
SelectedSelectOptions::from(self.get_selected_options(cell_data.clone()));
filter.is_visible(&self.options, &selected_options)
}
}
impl TypeOptionCellDataCompare for ChecklistTypeOption {
fn apply_cmp(
&self,
cell_data: &<Self as TypeOption>::CellData,
other_cell_data: &<Self as TypeOption>::CellData,
) -> Ordering {
cell_data.len().cmp(&other_cell_data.len())
}
}

View File

@ -1,5 +1,4 @@
mod checklist_filter;
mod checklist_type_option;
mod multi_select_type_option;
mod select_filter;
mod select_ids;
@ -9,7 +8,6 @@ mod single_select_type_option;
mod type_option_transform;
pub use checklist_filter::*;
pub use checklist_type_option::*;
pub use multi_select_type_option::*;
pub use select_ids::*;
pub use select_option::*;

View File

@ -11,8 +11,8 @@ use crate::entities::{FieldType, SelectOptionCellDataPB, SelectOptionFilterPB};
use crate::services::cell::CellDataChangeset;
use crate::services::field::{
default_order, SelectOption, SelectOptionCellChangeset, SelectOptionIds,
SelectTypeOptionSharedAction, SelectedSelectOptions, TypeOption, TypeOptionCellData,
TypeOptionCellDataCompare, TypeOptionCellDataFilter,
SelectTypeOptionSharedAction, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
TypeOptionCellDataFilter,
};
// Multiple select
@ -48,14 +48,14 @@ impl From<MultiSelectTypeOption> for TypeOptionData {
}
impl TypeOptionCellData for MultiSelectTypeOption {
fn convert_to_protobuf(
fn protobuf_encode(
&self,
cell_data: <Self as TypeOption>::CellData,
) -> <Self as TypeOption>::CellProtobufType {
self.get_selected_options(cell_data).into()
}
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
Ok(SelectOptionIds::from(cell))
}
}
@ -130,8 +130,7 @@ impl TypeOptionCellDataFilter for MultiSelectTypeOption {
if !field_type.is_multi_select() {
return true;
}
let selected_options =
SelectedSelectOptions::from(self.get_selected_options(cell_data.clone()));
let selected_options = self.get_selected_options(cell_data.clone()).select_options;
filter.is_visible(&selected_options, FieldType::MultiSelect)
}
}

View File

@ -1,19 +1,12 @@
#![allow(clippy::needless_collect)]
use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
use crate::services::field::SelectedSelectOptions;
use crate::services::field::SelectOption;
impl SelectOptionFilterPB {
pub fn is_visible(
&self,
selected_options: &SelectedSelectOptions,
field_type: FieldType,
) -> bool {
let selected_option_ids: Vec<&String> = selected_options
.options
.iter()
.map(|option| &option.id)
.collect();
pub fn is_visible(&self, selected_options: &[SelectOption], field_type: FieldType) -> bool {
let selected_option_ids: Vec<&String> =
selected_options.iter().map(|option| &option.id).collect();
match self.condition {
SelectOptionConditionPB::OptionIs => match field_type {
FieldType::SingleSelect => {
@ -21,7 +14,7 @@ impl SelectOptionFilterPB {
return true;
}
if selected_options.options.is_empty() {
if selected_options.is_empty() {
return false;
}
@ -54,7 +47,7 @@ impl SelectOptionFilterPB {
return true;
}
if selected_options.options.is_empty() {
if selected_options.is_empty() {
return false;
}
@ -87,7 +80,6 @@ impl SelectOptionFilterPB {
mod tests {
#![allow(clippy::all)]
use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
use crate::services::field::selection_type_option::SelectedSelectOptions;
use crate::services::field::SelectOption;
#[test]
@ -98,37 +90,15 @@ mod tests {
option_ids: vec![],
};
assert_eq!(filter.is_visible(&vec![], FieldType::SingleSelect), true);
assert_eq!(
filter.is_visible(
&SelectedSelectOptions { options: vec![] },
FieldType::SingleSelect
),
true
);
assert_eq!(
filter.is_visible(
&SelectedSelectOptions {
options: vec![option.clone()]
},
FieldType::SingleSelect
),
filter.is_visible(&vec![option.clone()], FieldType::SingleSelect),
false,
);
assert_eq!(filter.is_visible(&vec![], FieldType::MultiSelect), true);
assert_eq!(
filter.is_visible(
&SelectedSelectOptions { options: vec![] },
FieldType::MultiSelect
),
true
);
assert_eq!(
filter.is_visible(
&SelectedSelectOptions {
options: vec![option]
},
FieldType::MultiSelect
),
filter.is_visible(&vec![option], FieldType::MultiSelect),
false,
);
}
@ -143,38 +113,16 @@ mod tests {
};
assert_eq!(
filter.is_visible(
&SelectedSelectOptions {
options: vec![option_1.clone()]
},
FieldType::SingleSelect
),
filter.is_visible(&vec![option_1.clone()], FieldType::SingleSelect),
true
);
assert_eq!(
filter.is_visible(
&SelectedSelectOptions { options: vec![] },
FieldType::SingleSelect
),
false,
);
assert_eq!(filter.is_visible(&vec![], FieldType::SingleSelect), false,);
assert_eq!(
filter.is_visible(
&SelectedSelectOptions {
options: vec![option_1.clone()]
},
FieldType::MultiSelect
),
filter.is_visible(&vec![option_1.clone()], FieldType::MultiSelect),
true
);
assert_eq!(
filter.is_visible(
&SelectedSelectOptions { options: vec![] },
FieldType::MultiSelect
),
false,
);
assert_eq!(filter.is_visible(&vec![], FieldType::MultiSelect), false,);
}
#[test]
@ -194,7 +142,7 @@ mod tests {
(vec![option_1.clone(), option_2.clone()], false),
] {
assert_eq!(
filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect),
filter.is_visible(&options, FieldType::SingleSelect),
is_visible
);
}
@ -217,7 +165,7 @@ mod tests {
(vec![option_1.clone(), option_2.clone()], true),
] {
assert_eq!(
filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect),
filter.is_visible(&options, FieldType::SingleSelect),
is_visible
);
}
@ -238,7 +186,7 @@ mod tests {
(vec![option_1.clone(), option_2.clone()], true),
] {
assert_eq!(
filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect),
filter.is_visible(&options, FieldType::SingleSelect),
is_visible
);
}
@ -266,7 +214,7 @@ mod tests {
(vec![], true),
] {
assert_eq!(
filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect),
filter.is_visible(&options, FieldType::MultiSelect),
is_visible
);
}
@ -292,7 +240,7 @@ mod tests {
(vec![option_3.clone()], false),
] {
assert_eq!(
filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect),
filter.is_visible(&options, FieldType::MultiSelect),
is_visible
);
}
@ -308,7 +256,7 @@ mod tests {
};
for (options, is_visible) in vec![(vec![option_1.clone()], true), (vec![], true)] {
assert_eq!(
filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect),
filter.is_visible(&options, FieldType::MultiSelect),
is_visible
);
}

View File

@ -83,15 +83,3 @@ pub fn make_selected_options(ids: SelectOptionIds, options: &[SelectOption]) ->
})
.collect()
}
pub struct SelectedSelectOptions {
pub(crate) options: Vec<SelectOption>,
}
impl std::convert::From<SelectOptionCellData> for SelectedSelectOptions {
fn from(data: SelectOptionCellData) -> Self {
Self {
options: data.select_options,
}
}
}

View File

@ -9,11 +9,12 @@ use crate::entities::{FieldType, SelectOptionCellDataPB};
use crate::services::cell::{
CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, ToCellChangeset,
};
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformHelper;
use crate::services::field::{
make_selected_options, CheckboxCellData, ChecklistTypeOption, MultiSelectTypeOption,
SelectOption, SelectOptionCellData, SelectOptionColor, SelectOptionIds, SingleSelectTypeOption,
TypeOption, TypeOptionCellData, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
make_selected_options, CheckboxCellData, MultiSelectTypeOption, SelectOption,
SelectOptionCellData, SelectOptionColor, SelectOptionIds, SingleSelectTypeOption, TypeOption,
TypeOptionCellData, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
};
/// Defines the shared actions used by SingleSelect or Multi-Select.
@ -124,16 +125,16 @@ impl<T> CellDataDecoder for T
where
T: SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds> + TypeOptionCellData,
{
fn decode_cell_str(
fn decode_cell(
&self,
cell: &Cell,
_decoded_field_type: &FieldType,
_field: &Field,
) -> FlowyResult<<Self as TypeOption>::CellData> {
self.decode_cell(cell)
self.parse_cell(cell)
}
fn decode_cell_data_to_str(&self, cell_data: <Self as TypeOption>::CellData) -> String {
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
self
.get_selected_options(cell_data)
.select_options
@ -143,35 +144,29 @@ where
.join(SELECTION_IDS_SEPARATOR)
}
fn decode_cell_to_str(&self, cell: &Cell) -> String {
fn stringify_cell(&self, cell: &Cell) -> String {
let cell_data = Self::CellData::from(cell);
self.decode_cell_data_to_str(cell_data)
self.stringify_cell_data(cell_data)
}
}
pub fn select_type_option_from_field(
field_rev: &Field,
field: &Field,
) -> FlowyResult<Box<dyn SelectTypeOptionSharedAction>> {
let field_type = FieldType::from(field_rev.field_type);
let field_type = FieldType::from(field.field_type);
match &field_type {
FieldType::SingleSelect => {
let type_option = field_rev
let type_option = field
.get_type_option::<SingleSelectTypeOption>(field_type)
.unwrap_or_default();
Ok(Box::new(type_option))
},
FieldType::MultiSelect => {
let type_option = field_rev
let type_option = field
.get_type_option::<MultiSelectTypeOption>(&field_type)
.unwrap_or_default();
Ok(Box::new(type_option))
},
FieldType::Checklist => {
let type_option = field_rev
.get_type_option::<ChecklistTypeOption>(&field_type)
.unwrap_or_default();
Ok(Box::new(type_option))
},
ty => {
tracing::error!("Unsupported field type: {:?} for this handler", ty);
Err(ErrorCode::FieldInvalidOperation.into())

View File

@ -1,8 +1,8 @@
use crate::entities::{FieldType, SelectOptionCellDataPB, SelectOptionFilterPB};
use crate::services::cell::CellDataChangeset;
use crate::services::field::{
default_order, SelectOption, SelectedSelectOptions, TypeOption, TypeOptionCellData,
TypeOptionCellDataCompare, TypeOptionCellDataFilter,
default_order, SelectOption, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
TypeOptionCellDataFilter,
};
use crate::services::field::{
SelectOptionCellChangeset, SelectOptionIds, SelectTypeOptionSharedAction,
@ -47,14 +47,14 @@ impl From<SingleSelectTypeOption> for TypeOptionData {
}
impl TypeOptionCellData for SingleSelectTypeOption {
fn convert_to_protobuf(
fn protobuf_encode(
&self,
cell_data: <Self as TypeOption>::CellData,
) -> <Self as TypeOption>::CellProtobufType {
self.get_selected_options(cell_data).into()
}
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
Ok(SelectOptionIds::from(cell))
}
}
@ -121,8 +121,7 @@ impl TypeOptionCellDataFilter for SingleSelectTypeOption {
if !field_type.is_single_select() {
return true;
}
let selected_options =
SelectedSelectOptions::from(self.get_selected_options(cell_data.clone()));
let selected_options = self.get_selected_options(cell_data.clone()).select_options;
filter.is_visible(&selected_options, FieldType::SingleSelect)
}
}

View File

@ -86,20 +86,20 @@ impl TypeOptionTransform for RichTextTypeOption {
}
impl TypeOptionCellData for RichTextTypeOption {
fn convert_to_protobuf(
fn protobuf_encode(
&self,
cell_data: <Self as TypeOption>::CellData,
) -> <Self as TypeOption>::CellProtobufType {
ProtobufStr::from(cell_data.0)
}
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
Ok(StrCellData::from(cell))
}
}
impl CellDataDecoder for RichTextTypeOption {
fn decode_cell_str(
fn decode_cell(
&self,
cell: &Cell,
_decoded_field_type: &FieldType,
@ -108,11 +108,11 @@ impl CellDataDecoder for RichTextTypeOption {
Ok(StrCellData::from(cell))
}
fn decode_cell_data_to_str(&self, cell_data: <Self as TypeOption>::CellData) -> String {
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
cell_data.to_string()
}
fn decode_cell_to_str(&self, cell: &Cell) -> String {
fn stringify_cell(&self, cell: &Cell) -> String {
Self::CellData::from(cell).to_string()
}
}

View File

@ -14,9 +14,10 @@ use crate::entities::{
URLTypeOptionPB,
};
use crate::services::cell::{CellDataDecoder, FromCellChangeset, ToCellChangeset};
use crate::services::field::checklist_type_option::ChecklistTypeOption;
use crate::services::field::{
CheckboxTypeOption, ChecklistTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption,
RichTextTypeOption, SingleSelectTypeOption, URLTypeOption,
CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RichTextTypeOption,
SingleSelectTypeOption, URLTypeOption,
};
use crate::services::filter::FromFilterString;
@ -53,20 +54,19 @@ pub trait TypeOption {
}
pub trait TypeOptionCellData: TypeOption {
/// Convert the decoded cell data into corresponding `Protobuf struct`.
/// Encode the cell data into corresponding `Protobuf struct`.
/// For example:
/// FieldType::URL => URLCellDataPB
/// FieldType::Date=> DateCellDataPB
fn convert_to_protobuf(
fn protobuf_encode(
&self,
cell_data: <Self as TypeOption>::CellData,
) -> <Self as TypeOption>::CellProtobufType;
/// Decodes the opaque cell string to corresponding data struct.
// For example, the cell data is timestamp if its field type is `FieldType::Date`. This cell
// data can not directly show to user. So it needs to be encode as the date string with custom
// format setting. Encode `1647251762` to `"Mar 14,2022`
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData>;
/// Parse the opaque [Cell] to corresponding data struct.
/// The [Cell] is a map that stores list of key/value data. Each [TypeOption::CellData]
/// should implement the From<&Cell> trait to parse the [Cell] to corresponding data struct.
fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData>;
}
pub trait TypeOptionTransform: TypeOption {

View File

@ -13,10 +13,11 @@ use crate::services::cell::{
CellCache, CellDataChangeset, CellDataDecoder, CellFilterCache, CellProtobufBlob,
FromCellChangeset,
};
use crate::services::field::checklist_type_option::ChecklistTypeOption;
use crate::services::field::{
CheckboxTypeOption, ChecklistTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption,
RichTextTypeOption, SingleSelectTypeOption, TypeOption, TypeOptionCellData,
TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, URLTypeOption,
CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RichTextTypeOption,
SingleSelectTypeOption, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
TypeOptionCellDataFilter, TypeOptionTransform, URLTypeOption,
};
pub const CELL_DATA: &str = "data";
@ -36,6 +37,7 @@ pub trait TypeOptionCellDataHandler: Send + Sync + 'static {
field_rev: &Field,
) -> FlowyResult<CellProtobufBlob>;
// TODO(nathan): replace cell_changeset with BoxAny to get rid of the serde process.
fn handle_cell_changeset(
&self,
cell_changeset: String,
@ -141,7 +143,7 @@ where
}
}
let cell_data = self.decode_cell_str(cell, decoded_field_type, field)?;
let cell_data = self.decode_cell(cell, decoded_field_type, field)?;
if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
// tracing::trace!(
// "Cell cache update: field_type:{}, cell: {:?}, cell_data: {:?}",
@ -217,7 +219,7 @@ where
.get_cell_data(cell, decoded_field_type, field_rev)?
.unbox_or_default::<<Self as TypeOption>::CellData>();
CellProtobufBlob::from(self.convert_to_protobuf(cell_data))
CellProtobufBlob::from(self.protobuf_encode(cell_data))
}
fn handle_cell_changeset(
@ -265,10 +267,10 @@ where
if self.transformable() {
let cell_data = self.transform_type_option_cell(cell, field_type, field);
if let Some(cell_data) = cell_data {
return self.decode_cell_data_to_str(cell_data);
return self.stringify_cell_data(cell_data);
}
}
self.decode_cell_to_str(cell)
self.stringify_cell(cell)
}
fn get_cell_data(

View File

@ -47,20 +47,20 @@ impl From<URLTypeOption> for TypeOptionData {
impl TypeOptionTransform for URLTypeOption {}
impl TypeOptionCellData for URLTypeOption {
fn convert_to_protobuf(
fn protobuf_encode(
&self,
cell_data: <Self as TypeOption>::CellData,
) -> <Self as TypeOption>::CellProtobufType {
cell_data.into()
}
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
Ok(URLCellData::from(cell))
}
}
impl CellDataDecoder for URLTypeOption {
fn decode_cell_str(
fn decode_cell(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
@ -70,16 +70,16 @@ impl CellDataDecoder for URLTypeOption {
return Ok(Default::default());
}
self.decode_cell(cell)
self.parse_cell(cell)
}
fn decode_cell_data_to_str(&self, cell_data: <Self as TypeOption>::CellData) -> String {
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
cell_data.data
}
fn decode_cell_to_str(&self, cell: &Cell) -> String {
fn stringify_cell(&self, cell: &Cell) -> String {
let cell_data = Self::CellData::from(cell);
self.decode_cell_data_to_str(cell_data)
self.stringify_cell_data(cell_data)
}
}