diff --git a/collab-database/src/database.rs b/collab-database/src/database.rs index e8e3dfcda..ef30c99e8 100644 --- a/collab-database/src/database.rs +++ b/collab-database/src/database.rs @@ -507,7 +507,7 @@ impl Database { let field_type = FieldType::from(field.field_type); let type_option = field.get_any_type_option(field_type.type_id())?; - type_option_cell_reader(type_option, &field_type) + Some(type_option_cell_reader(type_option, &field_type)) } /// Return [TypeOptionCellWriter] for the given field id. @@ -518,7 +518,7 @@ impl Database { let field_type = FieldType::from(field.field_type); let type_option = field.get_any_type_option(field_type.type_id())?; - type_option_cell_writer(type_option, &field_type) + Some(type_option_cell_writer(type_option, &field_type)) } #[instrument(level = "debug", skip_all)] diff --git a/collab-database/src/fields/type_option/checkbox_type_option.rs b/collab-database/src/fields/type_option/checkbox_type_option.rs index 5fca9e37b..a06edaa45 100644 --- a/collab-database/src/fields/type_option/checkbox_type_option.rs +++ b/collab-database/src/fields/type_option/checkbox_type_option.rs @@ -41,7 +41,7 @@ impl TypeOptionCellReader for CheckboxTypeOption { } impl TypeOptionCellWriter for CheckboxTypeOption { - fn write_json(&self, value: Value) -> Cell { + fn convert_json_to_cell(&self, value: Value) -> Cell { let mut cell = new_cell_builder(FieldType::Checkbox); if let Some(data) = match value { Value::String(s) => Some(s), @@ -119,17 +119,17 @@ mod tests { // Write a string let value = Value::String("true".to_string()); - let cell = option.write_json(value); + let cell = option.convert_json_to_cell(value); assert_eq!(cell.get_as::(CELL_DATA).unwrap(), "true"); // Write a boolean let value = Value::Bool(true); - let cell = option.write_json(value); + let cell = option.convert_json_to_cell(value); assert_eq!(cell.get_as::(CELL_DATA).unwrap(), "true"); // Write a number let value = Value::Number(1.into()); - let cell = option.write_json(value); + let cell = option.convert_json_to_cell(value); assert_eq!(cell.get_as::(CELL_DATA).unwrap(), "true"); } diff --git a/collab-database/src/fields/type_option/checklist_type_option.rs b/collab-database/src/fields/type_option/checklist_type_option.rs index 407929455..c68fa28ca 100644 --- a/collab-database/src/fields/type_option/checklist_type_option.rs +++ b/collab-database/src/fields/type_option/checklist_type_option.rs @@ -1,11 +1,10 @@ use super::{TypeOptionData, TypeOptionDataBuilder}; -use crate::entity::FieldType; + use crate::fields::select_type_option::SELECTION_IDS_SEPARATOR; use crate::fields::{TypeOptionCellReader, TypeOptionCellWriter}; -use crate::rows::{new_cell_builder, Cell}; +use crate::rows::Cell; use crate::template::check_list_parse::ChecklistCellData; -use crate::template::entity::CELL_DATA; -use collab::util::AnyMapExt; + use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -46,29 +45,12 @@ impl TypeOptionCellReader for ChecklistTypeOption { } impl TypeOptionCellWriter for ChecklistTypeOption { - fn write_json(&self, json_value: Value) -> Cell { + fn convert_json_to_cell(&self, json_value: Value) -> Cell { let cell_data = serde_json::from_value::(json_value).unwrap_or_default(); cell_data.into() } } -impl From<&Cell> for ChecklistCellData { - fn from(cell: &Cell) -> Self { - cell - .get_as::(CELL_DATA) - .map(|data| serde_json::from_str::(&data).unwrap_or_default()) - .unwrap_or_default() - } -} - -impl From for Cell { - fn from(cell_data: ChecklistCellData) -> Self { - let data = serde_json::to_string(&cell_data).unwrap_or_default(); - let mut cell = new_cell_builder(FieldType::Checklist); - cell.insert(CELL_DATA.into(), data.into()); - cell - } -} #[cfg(test)] mod checklist_type_option_tests { use super::*; @@ -132,7 +114,7 @@ mod checklist_type_option_tests { "selected_option_ids": ["1"] }); - let cell = checklist_option.write_json(json_value); + let cell = checklist_option.convert_json_to_cell(json_value); let restored_data = ChecklistCellData::from(&cell); assert_eq!(restored_data.options.len(), 2); diff --git a/collab-database/src/fields/type_option/date_type_option.rs b/collab-database/src/fields/type_option/date_type_option.rs index a3be14283..1e2fd4f9e 100644 --- a/collab-database/src/fields/type_option/date_type_option.rs +++ b/collab-database/src/fields/type_option/date_type_option.rs @@ -14,6 +14,7 @@ use serde::{Deserialize, Serialize}; use std::fmt; use crate::template::time_parse::TimeCellData; +use crate::template::util::TypeOptionCellData; use serde_json::{json, Value}; use std::str::FromStr; pub use strum::IntoEnumIterator; @@ -41,7 +42,7 @@ impl TypeOptionCellReader for TimeTypeOption { } impl TypeOptionCellWriter for TimeTypeOption { - fn write_json(&self, json_value: Value) -> Cell { + fn convert_json_to_cell(&self, json_value: Value) -> Cell { let cell_data = serde_json::from_value::(json_value).unwrap_or_default(); Cell::from(&cell_data) } @@ -117,7 +118,7 @@ impl TypeOptionCellReader for DateTypeOption { } impl TypeOptionCellWriter for DateTypeOption { - fn write_json(&self, json_value: Value) -> Cell { + fn convert_json_to_cell(&self, json_value: Value) -> Cell { let cell_data = serde_json::from_value::(json_value).unwrap(); Cell::from(&cell_data) } @@ -351,6 +352,11 @@ pub struct DateCellData { pub is_range: bool, pub reminder_id: String, } +impl TypeOptionCellData for DateCellData { + fn is_empty(&self) -> bool { + self.timestamp.is_none() + } +} impl DateCellData { pub fn new(timestamp: i64, include_time: bool, is_range: bool, reminder_id: String) -> Self { @@ -629,7 +635,7 @@ mod tests { "reminder_id": "reminder123" }); - let cell = date_type_option.write_json(json_value); + let cell = date_type_option.convert_json_to_cell(json_value); assert_eq!( cell.get_as::(CELL_DATA), Some("1672531200".to_string()) diff --git a/collab-database/src/fields/type_option/media_type_option.rs b/collab-database/src/fields/type_option/media_type_option.rs index 58e9346f1..1056389e3 100644 --- a/collab-database/src/fields/type_option/media_type_option.rs +++ b/collab-database/src/fields/type_option/media_type_option.rs @@ -4,7 +4,9 @@ use crate::fields::{ TypeOptionCellReader, TypeOptionCellWriter, TypeOptionData, TypeOptionDataBuilder, }; use crate::rows::{new_cell_builder, Cell}; + use crate::template::entity::CELL_DATA; +use crate::template::util::TypeOptionCellData; use collab::util::AnyMapExt; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::{json, Value}; @@ -52,7 +54,7 @@ impl TypeOptionCellReader for MediaTypeOption { } impl TypeOptionCellWriter for MediaTypeOption { - fn write_json(&self, json_value: Value) -> Cell { + fn convert_json_to_cell(&self, json_value: Value) -> Cell { let cell_data = serde_json::from_value::(json_value).unwrap_or_default(); cell_data.into() } @@ -79,6 +81,12 @@ pub struct MediaCellData { pub files: Vec, } +impl TypeOptionCellData for MediaCellData { + fn is_empty(&self) -> bool { + self.files.is_empty() + } +} + impl From for Any { fn from(data: MediaCellData) -> Self { Any::Array(Arc::from( diff --git a/collab-database/src/fields/type_option/mod.rs b/collab-database/src/fields/type_option/mod.rs index a78927515..108cba9f5 100644 --- a/collab-database/src/fields/type_option/mod.rs +++ b/collab-database/src/fields/type_option/mod.rs @@ -152,54 +152,53 @@ pub trait TypeOptionCellReader { /// [TypeOptionCellWriter] is a trait that provides methods to write [serde_json::Value] into a cell. /// Different field types have their own implementation about how to convert [serde_json::Value] into [Cell]. pub trait TypeOptionCellWriter { - /// Write json value into a cell + /// Convert json value into a cell /// Different type option has its own implementation about how to convert [serde_json::Value] /// into [Cell] - fn write_json(&self, json_value: serde_json::Value) -> Cell; + fn convert_json_to_cell(&self, json_value: serde_json::Value) -> Cell; } - pub fn type_option_cell_writer( type_option_data: TypeOptionData, field_type: &FieldType, -) -> Option> { +) -> Box { match field_type { - FieldType::RichText => Some(Box::new(RichTextTypeOption::from(type_option_data))), - FieldType::Number => Some(Box::new(NumberTypeOption::from(type_option_data))), - FieldType::DateTime => Some(Box::new(DateTypeOption::from(type_option_data))), - FieldType::SingleSelect => Some(Box::new(SingleSelectTypeOption::from(type_option_data))), - FieldType::MultiSelect => Some(Box::new(MultiSelectTypeOption::from(type_option_data))), - FieldType::Checkbox => Some(Box::new(CheckboxTypeOption::from(type_option_data))), - FieldType::URL => Some(Box::new(URLTypeOption::from(type_option_data))), - FieldType::Time => Some(Box::new(TimeTypeOption::from(type_option_data))), - FieldType::Media => Some(Box::new(MediaTypeOption::from(type_option_data))), - FieldType::Checklist => Some(Box::new(ChecklistTypeOption::from(type_option_data))), - FieldType::LastEditedTime => Some(Box::new(TimestampTypeOption::from(type_option_data))), - FieldType::CreatedTime => Some(Box::new(TimestampTypeOption::from(type_option_data))), - FieldType::Relation => Some(Box::new(RelationTypeOption::from(type_option_data))), - FieldType::Summary => Some(Box::new(SummarizationTypeOption::from(type_option_data))), - FieldType::Translate => Some(Box::new(TranslateTypeOption::from(type_option_data))), + FieldType::RichText => Box::new(RichTextTypeOption::from(type_option_data)), + FieldType::Number => Box::new(NumberTypeOption::from(type_option_data)), + FieldType::DateTime => Box::new(DateTypeOption::from(type_option_data)), + FieldType::SingleSelect => Box::new(SingleSelectTypeOption::from(type_option_data)), + FieldType::MultiSelect => Box::new(MultiSelectTypeOption::from(type_option_data)), + FieldType::Checkbox => Box::new(CheckboxTypeOption::from(type_option_data)), + FieldType::URL => Box::new(URLTypeOption::from(type_option_data)), + FieldType::Time => Box::new(TimeTypeOption::from(type_option_data)), + FieldType::Media => Box::new(MediaTypeOption::from(type_option_data)), + FieldType::Checklist => Box::new(ChecklistTypeOption::from(type_option_data)), + FieldType::LastEditedTime => Box::new(TimestampTypeOption::from(type_option_data)), + FieldType::CreatedTime => Box::new(TimestampTypeOption::from(type_option_data)), + FieldType::Relation => Box::new(RelationTypeOption::from(type_option_data)), + FieldType::Summary => Box::new(SummarizationTypeOption::from(type_option_data)), + FieldType::Translate => Box::new(TranslateTypeOption::from(type_option_data)), } } pub fn type_option_cell_reader( type_option_data: TypeOptionData, field_type: &FieldType, -) -> Option> { +) -> Box { match field_type { - FieldType::RichText => Some(Box::new(RichTextTypeOption::from(type_option_data))), - FieldType::Number => Some(Box::new(NumberTypeOption::from(type_option_data))), - FieldType::DateTime => Some(Box::new(DateTypeOption::from(type_option_data))), - FieldType::SingleSelect => Some(Box::new(SingleSelectTypeOption::from(type_option_data))), - FieldType::MultiSelect => Some(Box::new(MultiSelectTypeOption::from(type_option_data))), - FieldType::Checkbox => Some(Box::new(CheckboxTypeOption::from(type_option_data))), - FieldType::URL => Some(Box::new(URLTypeOption::from(type_option_data))), - FieldType::Time => Some(Box::new(TimeTypeOption::from(type_option_data))), - FieldType::Media => Some(Box::new(MediaTypeOption::from(type_option_data))), - FieldType::Checklist => Some(Box::new(ChecklistTypeOption::from(type_option_data))), - FieldType::LastEditedTime => Some(Box::new(TimestampTypeOption::from(type_option_data))), - FieldType::CreatedTime => Some(Box::new(TimestampTypeOption::from(type_option_data))), - FieldType::Relation => Some(Box::new(RelationTypeOption::from(type_option_data))), - FieldType::Summary => Some(Box::new(SummarizationTypeOption::from(type_option_data))), - FieldType::Translate => Some(Box::new(TranslateTypeOption::from(type_option_data))), + FieldType::RichText => Box::new(RichTextTypeOption::from(type_option_data)), + FieldType::Number => Box::new(NumberTypeOption::from(type_option_data)), + FieldType::DateTime => Box::new(DateTypeOption::from(type_option_data)), + FieldType::SingleSelect => Box::new(SingleSelectTypeOption::from(type_option_data)), + FieldType::MultiSelect => Box::new(MultiSelectTypeOption::from(type_option_data)), + FieldType::Checkbox => Box::new(CheckboxTypeOption::from(type_option_data)), + FieldType::URL => Box::new(URLTypeOption::from(type_option_data)), + FieldType::Time => Box::new(TimeTypeOption::from(type_option_data)), + FieldType::Media => Box::new(MediaTypeOption::from(type_option_data)), + FieldType::Checklist => Box::new(ChecklistTypeOption::from(type_option_data)), + FieldType::LastEditedTime => Box::new(TimestampTypeOption::from(type_option_data)), + FieldType::CreatedTime => Box::new(TimestampTypeOption::from(type_option_data)), + FieldType::Relation => Box::new(RelationTypeOption::from(type_option_data)), + FieldType::Summary => Box::new(SummarizationTypeOption::from(type_option_data)), + FieldType::Translate => Box::new(TranslateTypeOption::from(type_option_data)), } } diff --git a/collab-database/src/fields/type_option/number_type_option.rs b/collab-database/src/fields/type_option/number_type_option.rs index 280cdbc37..e0018cff6 100644 --- a/collab-database/src/fields/type_option/number_type_option.rs +++ b/collab-database/src/fields/type_option/number_type_option.rs @@ -10,8 +10,8 @@ use collab::preclude::Any; use crate::entity::FieldType; use crate::rows::{new_cell_builder, Cell}; -use crate::template::entity::CELL_DATA; -use collab::util::AnyMapExt; +use crate::template::number_parse::NumberCellData; + use fancy_regex::Regex; use lazy_static::lazy_static; use rust_decimal::Decimal; @@ -72,8 +72,8 @@ impl TypeOptionCellReader for NumberTypeOption { } fn numeric_cell(&self, cell: &Cell) -> Option { - let cell_data = cell.get_as::(CELL_DATA)?; - cell_data.parse::().ok() + let cell_data = NumberCellData::from(cell); + cell_data.0.parse::().ok() } fn convert_raw_cell_data(&self, text: &str) -> String { @@ -85,16 +85,16 @@ impl TypeOptionCellReader for NumberTypeOption { } impl TypeOptionCellWriter for NumberTypeOption { - fn write_json(&self, json_value: Value) -> Cell { - let mut cell = new_cell_builder(FieldType::Number); + fn convert_json_to_cell(&self, json_value: Value) -> Cell { if let Some(data) = match json_value { Value::String(s) => Some(s), Value::Number(n) => Some(n.to_string()), _ => None, } { - cell.insert(CELL_DATA.into(), data.into()); + NumberCellData(data).into() + } else { + new_cell_builder(FieldType::Number) } - cell } } diff --git a/collab-database/src/fields/type_option/relation_type_option.rs b/collab-database/src/fields/type_option/relation_type_option.rs index 72bd32fb3..3d848b377 100644 --- a/collab-database/src/fields/type_option/relation_type_option.rs +++ b/collab-database/src/fields/type_option/relation_type_option.rs @@ -46,8 +46,8 @@ impl TypeOptionCellReader for RelationTypeOption { } impl TypeOptionCellWriter for RelationTypeOption { - fn write_json(&self, json_value: Value) -> Cell { + fn convert_json_to_cell(&self, json_value: Value) -> Cell { let cell_data = serde_json::from_value::(json_value).unwrap_or_default(); - Cell::from(&cell_data) + Cell::from(cell_data) } } diff --git a/collab-database/src/fields/type_option/select_type_option.rs b/collab-database/src/fields/type_option/select_type_option.rs index 4600531ec..f48c6fe0b 100644 --- a/collab-database/src/fields/type_option/select_type_option.rs +++ b/collab-database/src/fields/type_option/select_type_option.rs @@ -7,6 +7,8 @@ use crate::fields::{ }; use crate::rows::{new_cell_builder, Cell}; use crate::template::entity::CELL_DATA; + +use crate::template::util::TypeOptionCellData; use collab::util::AnyMapExt; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -194,7 +196,7 @@ impl TypeOptionCellReader for SingleSelectTypeOption { } impl TypeOptionCellWriter for SingleSelectTypeOption { - fn write_json(&self, value: Value) -> Cell { + fn convert_json_to_cell(&self, value: Value) -> Cell { cell_from_json_value(value, &self.options, FieldType::SingleSelect) } } @@ -243,7 +245,7 @@ impl TypeOptionCellReader for MultiSelectTypeOption { } impl TypeOptionCellWriter for MultiSelectTypeOption { - fn write_json(&self, value: Value) -> Cell { + fn convert_json_to_cell(&self, value: Value) -> Cell { cell_from_json_value(value, &self.options, FieldType::MultiSelect) } } @@ -290,6 +292,12 @@ impl SelectOptionIds { } } +impl TypeOptionCellData for SelectOptionIds { + fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + pub const SELECTION_IDS_SEPARATOR: &str = ","; impl std::convert::From> for SelectOptionIds { @@ -483,7 +491,7 @@ mod tests { }); let json_value = json!({ "name": "Option A" }); - let cell = single_select.write_json(json_value); + let cell = single_select.convert_json_to_cell(json_value); let cell_data: String = cell.get_as(CELL_DATA).unwrap(); assert!(!cell_data.is_empty()); @@ -502,7 +510,7 @@ mod tests { { "id": multi_select.options[1].id } ]); - let cell = multi_select.write_json(json_value); + let cell = multi_select.convert_json_to_cell(json_value); let cell_data: String = cell.get_as(CELL_DATA).unwrap(); assert!(cell_data.contains(&multi_select.options[0].id)); assert!(cell_data.contains(&multi_select.options[1].id)); diff --git a/collab-database/src/fields/type_option/summary_type_option.rs b/collab-database/src/fields/type_option/summary_type_option.rs index d1e546344..697d57f17 100644 --- a/collab-database/src/fields/type_option/summary_type_option.rs +++ b/collab-database/src/fields/type_option/summary_type_option.rs @@ -41,7 +41,7 @@ impl TypeOptionCellReader for SummarizationTypeOption { } impl TypeOptionCellWriter for SummarizationTypeOption { - fn write_json(&self, json_value: Value) -> Cell { + fn convert_json_to_cell(&self, json_value: Value) -> Cell { let cell_data = serde_json::from_value::(json_value).unwrap_or_default(); cell_data.into() } diff --git a/collab-database/src/fields/type_option/text_type_option.rs b/collab-database/src/fields/type_option/text_type_option.rs index 9c311b2b6..9db1f798b 100644 --- a/collab-database/src/fields/type_option/text_type_option.rs +++ b/collab-database/src/fields/type_option/text_type_option.rs @@ -25,7 +25,7 @@ impl TypeOptionCellReader for RichTextTypeOption { } impl TypeOptionCellWriter for RichTextTypeOption { - fn write_json(&self, json_value: Value) -> Cell { + fn convert_json_to_cell(&self, json_value: Value) -> Cell { let mut cell = new_cell_builder(FieldType::RichText); cell.insert(CELL_DATA.into(), json_value.to_string().into()); cell diff --git a/collab-database/src/fields/type_option/timestamp_type_option.rs b/collab-database/src/fields/type_option/timestamp_type_option.rs index 6ab9ff3a4..fb013e430 100644 --- a/collab-database/src/fields/type_option/timestamp_type_option.rs +++ b/collab-database/src/fields/type_option/timestamp_type_option.rs @@ -48,7 +48,7 @@ impl TypeOptionCellReader for TimestampTypeOption { } impl TypeOptionCellWriter for TimestampTypeOption { - fn write_json(&self, json_value: Value) -> Cell { + fn convert_json_to_cell(&self, json_value: Value) -> Cell { let filed_type = FieldType::from(self.field_type); let data = match json_value { Value::String(s) => s.parse::().ok(), @@ -258,7 +258,7 @@ mod tests { let option = TimestampTypeOption::default(); let json_value = json!("1672531200"); - let cell = option.write_json(json_value); + let cell = option.convert_json_to_cell(json_value); let data = cell.get_as::(CELL_DATA).unwrap(); assert_eq!(data, "1672531200"); @@ -269,7 +269,7 @@ mod tests { let option = TimestampTypeOption::default(); let json_value = json!("invalid"); - let cell = option.write_json(json_value); + let cell = option.convert_json_to_cell(json_value); let data: Option = cell.get_as(CELL_DATA); assert!(data.is_none()); diff --git a/collab-database/src/fields/type_option/translate_type_option.rs b/collab-database/src/fields/type_option/translate_type_option.rs index 5f9198883..3c3fe04f1 100644 --- a/collab-database/src/fields/type_option/translate_type_option.rs +++ b/collab-database/src/fields/type_option/translate_type_option.rs @@ -48,7 +48,7 @@ impl TypeOptionCellReader for TranslateTypeOption { } impl TypeOptionCellWriter for TranslateTypeOption { - fn write_json(&self, json_value: Value) -> Cell { + fn convert_json_to_cell(&self, json_value: Value) -> Cell { let cell = TranslateCellData(json_value.as_str().unwrap_or_default().to_string()); cell.into() } diff --git a/collab-database/src/fields/type_option/url_type_option.rs b/collab-database/src/fields/type_option/url_type_option.rs index a0243af10..4942a1640 100644 --- a/collab-database/src/fields/type_option/url_type_option.rs +++ b/collab-database/src/fields/type_option/url_type_option.rs @@ -5,6 +5,7 @@ use crate::fields::{ }; use crate::rows::{new_cell_builder, Cell}; use crate::template::entity::CELL_DATA; +use crate::template::util::TypeOptionCellData; use collab::preclude::Any; use collab::util::AnyMapExt; use serde::{Deserialize, Serialize}; @@ -35,7 +36,7 @@ impl TypeOptionCellReader for URLTypeOption { } impl TypeOptionCellWriter for URLTypeOption { - fn write_json(&self, json_value: Value) -> Cell { + fn convert_json_to_cell(&self, json_value: Value) -> Cell { let mut cell = new_cell_builder(FieldType::URL); cell.insert(CELL_DATA.into(), json_value.to_string().into()); cell @@ -62,6 +63,12 @@ pub struct URLCellData { pub data: String, } +impl TypeOptionCellData for URLCellData { + fn is_empty(&self) -> bool { + self.data.is_empty() + } +} + impl AsRef for URLCellData { fn as_ref(&self) -> &str { &self.data diff --git a/collab-database/src/template/check_list_parse.rs b/collab-database/src/template/check_list_parse.rs index d961a765a..c292970d5 100644 --- a/collab-database/src/template/check_list_parse.rs +++ b/collab-database/src/template/check_list_parse.rs @@ -1,14 +1,64 @@ use crate::database::gen_option_id; +use crate::entity::FieldType; use crate::fields::select_type_option::{SelectOption, SelectOptionColor}; +use crate::rows::{new_cell_builder, Cell}; +use crate::template::entity::CELL_DATA; +use crate::template::util::TypeOptionCellData; +use collab::util::AnyMapExt; use serde::{Deserialize, Serialize}; -#[derive(Default, Clone, Serialize, Deserialize)] +#[derive(Default, Clone, Debug, Serialize, Deserialize)] pub struct ChecklistCellData { pub options: Vec, #[serde(default)] pub selected_option_ids: Vec, } +impl ChecklistCellData { + pub fn selected_options(&self) -> Vec { + 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) * 100.0).round() / 100.0 + } +} + +impl From<&Cell> for ChecklistCellData { + fn from(cell: &Cell) -> Self { + cell + .get_as::(CELL_DATA) + .map(|data| serde_json::from_str::(&data).unwrap_or_default()) + .unwrap_or_default() + } +} + +impl From for Cell { + fn from(cell_data: ChecklistCellData) -> Self { + let data = serde_json::to_string(&cell_data).unwrap_or_default(); + let mut cell = new_cell_builder(FieldType::Checklist); + cell.insert(CELL_DATA.into(), data.into()); + cell + } +} + +impl TypeOptionCellData for ChecklistCellData { + fn is_empty(&self) -> bool { + self.options.is_empty() + } +} + impl From<(Vec, Vec)> for ChecklistCellData { fn from((names, selected_names): (Vec, Vec)) -> Self { let options: Vec = names @@ -38,6 +88,12 @@ impl From<(Vec, Vec)> for ChecklistCellData { } } +impl ToString for ChecklistCellData { + fn to_string(&self) -> String { + serde_json::to_string(self).unwrap_or_default() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/collab-database/src/template/checkbox_parse.rs b/collab-database/src/template/checkbox_parse.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/collab-database/src/template/checkbox_parse.rs @@ -0,0 +1 @@ + diff --git a/collab-database/src/template/mod.rs b/collab-database/src/template/mod.rs index e5aa2b579..0f4b9ac89 100644 --- a/collab-database/src/template/mod.rs +++ b/collab-database/src/template/mod.rs @@ -1,9 +1,11 @@ pub mod builder; pub mod check_list_parse; +pub mod checkbox_parse; pub mod csv; pub mod date_parse; pub mod entity; -mod media_parse; +pub mod media_parse; +pub mod number_parse; pub mod option_parse; pub mod relation_parse; pub mod summary_parse; diff --git a/collab-database/src/template/number_parse.rs b/collab-database/src/template/number_parse.rs new file mode 100644 index 000000000..d81e810ae --- /dev/null +++ b/collab-database/src/template/number_parse.rs @@ -0,0 +1,47 @@ +use crate::entity::FieldType; +use crate::rows::{new_cell_builder, Cell}; +use crate::template::entity::CELL_DATA; +use crate::template::util::TypeOptionCellData; +use collab::util::AnyMapExt; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct NumberCellData(pub String); + +impl TypeOptionCellData for NumberCellData { + fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl AsRef for NumberCellData { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl From<&Cell> for NumberCellData { + fn from(cell: &Cell) -> Self { + Self(cell.get_as(CELL_DATA).unwrap_or_default()) + } +} + +impl From for Cell { + fn from(data: NumberCellData) -> Self { + let mut cell = new_cell_builder(FieldType::Number); + cell.insert(CELL_DATA.into(), data.0.into()); + cell + } +} + +impl std::convert::From for NumberCellData { + fn from(s: String) -> Self { + Self(s) + } +} + +impl ToString for NumberCellData { + fn to_string(&self) -> String { + self.0.clone() + } +} diff --git a/collab-database/src/template/relation_parse.rs b/collab-database/src/template/relation_parse.rs index 5c2c798d2..53a459ac2 100644 --- a/collab-database/src/template/relation_parse.rs +++ b/collab-database/src/template/relation_parse.rs @@ -1,6 +1,8 @@ use crate::entity::FieldType; + use crate::rows::{new_cell_builder, Cell, RowId}; use crate::template::entity::CELL_DATA; +use crate::template::util::TypeOptionCellData; use serde::{Deserialize, Serialize}; use std::sync::Arc; use yrs::Any; @@ -10,6 +12,12 @@ pub struct RelationCellData { pub row_ids: Vec, } +impl TypeOptionCellData for RelationCellData { + fn is_empty(&self) -> bool { + self.row_ids.is_empty() + } +} + impl From<&Cell> for RelationCellData { fn from(value: &Cell) -> Self { let row_ids = match value.get(CELL_DATA) { @@ -29,12 +37,11 @@ impl From<&Cell> for RelationCellData { } } -impl From<&RelationCellData> for Cell { - fn from(value: &RelationCellData) -> Self { +impl From for Cell { + fn from(value: RelationCellData) -> Self { let data = Any::Array(Arc::from( value .row_ids - .clone() .into_iter() .map(|id| Any::String(Arc::from(id.to_string()))) .collect::>(), @@ -59,3 +66,20 @@ impl From<&str> for RelationCellData { RelationCellData { row_ids: ids } } } + +impl From for RelationCellData { + fn from(s: String) -> Self { + RelationCellData::from(s.as_str()) + } +} + +impl ToString for RelationCellData { + fn to_string(&self) -> String { + self + .row_ids + .iter() + .map(|id| id.to_string()) + .collect::>() + .join(", ") + } +} diff --git a/collab-database/src/template/summary_parse.rs b/collab-database/src/template/summary_parse.rs index ddf18097a..e5555a6fb 100644 --- a/collab-database/src/template/summary_parse.rs +++ b/collab-database/src/template/summary_parse.rs @@ -1,11 +1,19 @@ use crate::entity::FieldType; use crate::rows::{new_cell_builder, Cell}; use crate::template::entity::CELL_DATA; +use crate::template::util::TypeOptionCellData; use collab::util::AnyMapExt; use serde::{Deserialize, Serialize}; #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct SummaryCellData(pub String); + +impl TypeOptionCellData for SummaryCellData { + fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + impl std::ops::Deref for SummaryCellData { type Target = String; diff --git a/collab-database/src/template/time_parse.rs b/collab-database/src/template/time_parse.rs index 77453bd18..489f8e45d 100644 --- a/collab-database/src/template/time_parse.rs +++ b/collab-database/src/template/time_parse.rs @@ -1,12 +1,19 @@ use crate::entity::FieldType; use crate::rows::{new_cell_builder, Cell}; use crate::template::entity::CELL_DATA; +use crate::template::util::TypeOptionCellData; use collab::util::AnyMapExt; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct TimeCellData(pub Option); +impl TypeOptionCellData for TimeCellData { + fn is_empty(&self) -> bool { + self.0.is_none() + } +} + impl From<&Cell> for TimeCellData { fn from(cell: &Cell) -> Self { Self( diff --git a/collab-database/src/template/timestamp_parse.rs b/collab-database/src/template/timestamp_parse.rs index 179436c2e..4437afa05 100644 --- a/collab-database/src/template/timestamp_parse.rs +++ b/collab-database/src/template/timestamp_parse.rs @@ -1,6 +1,8 @@ use crate::entity::FieldType; use crate::rows::{new_cell_builder, Cell}; use crate::template::entity::CELL_DATA; + +use crate::template::util::TypeOptionCellData; use collab::util::AnyMapExt; use serde::{Deserialize, Serialize}; @@ -9,13 +11,21 @@ pub struct TimestampCellData { pub timestamp: Option, } +impl TypeOptionCellData for TimestampCellData { + fn is_empty(&self) -> bool { + self.timestamp.is_none() + } +} + impl TimestampCellData { - pub fn new(timestamp: Option) -> Self { - Self { timestamp } + pub fn new>>(timestamp: T) -> Self { + Self { + timestamp: timestamp.into(), + } } - pub fn to_cell(&self, field_type: FieldType) -> Cell { - let data: TimestampCellDataWrapper = (field_type, self.clone()).into(); + pub fn to_cell>(&self, field_type: T) -> Cell { + let data: TimestampCellDataWrapper = (field_type.into(), self.clone()).into(); data.into() } } diff --git a/collab-database/src/template/translate_parse.rs b/collab-database/src/template/translate_parse.rs index 34c4a8a8d..9cfe440f2 100644 --- a/collab-database/src/template/translate_parse.rs +++ b/collab-database/src/template/translate_parse.rs @@ -1,11 +1,19 @@ use crate::entity::FieldType; use crate::rows::{new_cell_builder, Cell}; use crate::template::entity::CELL_DATA; +use crate::template::util::TypeOptionCellData; use collab::util::AnyMapExt; use serde::{Deserialize, Serialize}; #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct TranslateCellData(pub String); + +impl TypeOptionCellData for TranslateCellData { + fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + impl std::ops::Deref for TranslateCellData { type Target = String; diff --git a/collab-database/src/template/util.rs b/collab-database/src/template/util.rs index 867a86390..c2364c7e6 100644 --- a/collab-database/src/template/util.rs +++ b/collab-database/src/template/util.rs @@ -7,6 +7,13 @@ use crate::template::entity::DatabaseTemplate; use crate::workspace_database::NoPersistenceDatabaseCollabService; use std::sync::Arc; +/// This trait that provides methods to extend the [TypeOption::CellData] functionalities. +pub trait TypeOptionCellData { + /// Checks if the cell content is considered empty based on certain criteria. e.g. empty text, + /// no date selected, no selected options + fn is_empty(&self) -> bool; +} + pub async fn database_from_template(template: DatabaseTemplate) -> Result { let params = create_database_params_from_template(template); let context = DatabaseContext {