diff --git a/collab-database/src/entity.rs b/collab-database/src/entity.rs index 543ffcaec..58377ed92 100644 --- a/collab-database/src/entity.rs +++ b/collab-database/src/entity.rs @@ -1,7 +1,19 @@ #![allow(clippy::upper_case_acronyms)] use crate::database::{gen_database_id, gen_database_view_id, gen_row_id, timestamp, DatabaseData}; use crate::error::DatabaseError; -use crate::fields::Field; +use crate::fields::checkbox_type_option::CheckboxTypeOption; +use crate::fields::checklist_type_option::ChecklistTypeOption; +use crate::fields::date_type_option::{DateTypeOption, TimeTypeOption}; +use crate::fields::media_type_option::MediaTypeOption; +use crate::fields::number_type_option::NumberTypeOption; +use crate::fields::relation_type_option::RelationTypeOption; +use crate::fields::select_type_option::{MultiSelectTypeOption, SingleSelectTypeOption}; +use crate::fields::summary_type_option::SummarizationTypeOption; +use crate::fields::text_type_option::RichTextTypeOption; +use crate::fields::timestamp_type_option::TimestampTypeOption; +use crate::fields::translate_type_option::TranslateTypeOption; +use crate::fields::url_type_option::URLTypeOption; +use crate::fields::{Field, TypeOptionData}; use crate::rows::CreateRowParams; use crate::views::{ DatabaseLayout, FieldOrder, FieldSettingsByFieldIdMap, FieldSettingsMap, FilterMap, @@ -13,6 +25,7 @@ use collab_entity::CollabType; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::collections::HashMap; +use std::fmt::{Display, Formatter}; use tracing::error; use yrs::{Any, Out}; @@ -254,7 +267,7 @@ impl CreateDatabaseParams { } } -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize_repr, Deserialize_repr)] +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum FieldType { RichText = 0, @@ -276,7 +289,7 @@ pub enum FieldType { impl FieldType { pub fn type_id(&self) -> String { - (self.clone() as i64).to_string() + (*self as i64).to_string() } } @@ -286,6 +299,12 @@ impl From for i64 { } } +impl From<&FieldType> for i64 { + fn from(field_type: &FieldType) -> Self { + *field_type as i64 + } +} + impl TryFrom for FieldType { type Error = yrs::Out; @@ -297,6 +316,120 @@ impl TryFrom for FieldType { } } +impl Display for FieldType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let value: i64 = (*self).into(); + f.write_fmt(format_args!("{}", value)) + } +} + +impl AsRef for FieldType { + fn as_ref(&self) -> &FieldType { + self + } +} + +impl From<&FieldType> for FieldType { + fn from(field_type: &FieldType) -> Self { + *field_type + } +} + +impl FieldType { + pub fn value(&self) -> i64 { + (*self).into() + } + + pub fn default_name(&self) -> String { + let s = match self { + FieldType::RichText => "Text", + FieldType::Number => "Number", + FieldType::DateTime => "Date", + FieldType::SingleSelect => "Single Select", + FieldType::MultiSelect => "Multi Select", + FieldType::Checkbox => "Checkbox", + FieldType::URL => "URL", + FieldType::Checklist => "Checklist", + FieldType::LastEditedTime => "Last modified", + FieldType::CreatedTime => "Created time", + FieldType::Relation => "Relation", + FieldType::Summary => "Summarize", + FieldType::Translate => "Translate", + FieldType::Time => "Time", + FieldType::Media => "Media", + }; + s.to_string() + } + + pub fn is_ai_field(&self) -> bool { + matches!(self, FieldType::Summary | FieldType::Translate) + } + + pub fn is_number(&self) -> bool { + matches!(self, FieldType::Number) + } + + pub fn is_text(&self) -> bool { + matches!(self, FieldType::RichText) + } + + pub fn is_checkbox(&self) -> bool { + matches!(self, FieldType::Checkbox) + } + + pub fn is_date(&self) -> bool { + matches!(self, FieldType::DateTime) + } + + pub fn is_single_select(&self) -> bool { + matches!(self, FieldType::SingleSelect) + } + + pub fn is_multi_select(&self) -> bool { + matches!(self, FieldType::MultiSelect) + } + + pub fn is_last_edited_time(&self) -> bool { + matches!(self, FieldType::LastEditedTime) + } + + pub fn is_created_time(&self) -> bool { + matches!(self, FieldType::CreatedTime) + } + + pub fn is_url(&self) -> bool { + matches!(self, FieldType::URL) + } + + pub fn is_select_option(&self) -> bool { + self.is_single_select() || self.is_multi_select() + } + + pub fn is_checklist(&self) -> bool { + matches!(self, FieldType::Checklist) + } + + pub fn is_relation(&self) -> bool { + matches!(self, FieldType::Relation) + } + + pub fn is_time(&self) -> bool { + matches!(self, FieldType::Time) + } + + pub fn is_media(&self) -> bool { + matches!(self, FieldType::Media) + } + + pub fn can_be_group(&self) -> bool { + self.is_select_option() || self.is_checkbox() || self.is_url() + } + + pub fn is_auto_update(&self) -> bool { + self.is_last_edited_time() + } +} + impl From for FieldType { fn from(index: i64) -> Self { match index { @@ -323,6 +456,29 @@ impl From for FieldType { } } +pub fn default_type_option_data_from_type(field_type: FieldType) -> TypeOptionData { + match field_type { + FieldType::RichText => RichTextTypeOption.into(), + FieldType::Number => NumberTypeOption::default().into(), + FieldType::DateTime => DateTypeOption::default().into(), + FieldType::LastEditedTime | FieldType::CreatedTime => TimestampTypeOption { + field_type: field_type.into(), + ..Default::default() + } + .into(), + FieldType::SingleSelect => SingleSelectTypeOption::default().into(), + FieldType::MultiSelect => MultiSelectTypeOption::default().into(), + FieldType::Checkbox => CheckboxTypeOption.into(), + FieldType::URL => URLTypeOption::default().into(), + FieldType::Time => TimeTypeOption.into(), + FieldType::Media => MediaTypeOption::default().into(), + FieldType::Checklist => ChecklistTypeOption.into(), + FieldType::Relation => RelationTypeOption::default().into(), + FieldType::Summary => SummarizationTypeOption::default().into(), + FieldType::Translate => TranslateTypeOption::default().into(), + } +} + #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum FileUploadType { diff --git a/collab-database/src/fields/field.rs b/collab-database/src/fields/field.rs index 674d9859d..481c64a2d 100644 --- a/collab-database/src/fields/field.rs +++ b/collab-database/src/fields/field.rs @@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize}; use collab::preclude::{Any, Map, MapExt, MapRef, ReadTxn, TransactionMut, YrsValue}; +use crate::database::gen_field_id; +use crate::entity::{default_type_option_data_from_type, FieldType}; use crate::fields::{TypeOptionData, TypeOptions, TypeOptionsUpdate}; use crate::{impl_bool_update, impl_i64_update, impl_str_update}; @@ -37,6 +39,17 @@ impl Field { self } + pub fn from_field_type(name: &str, field_type: FieldType, is_primary: bool) -> Self { + let new_field = Self { + id: gen_field_id(), + name: name.to_string(), + field_type: field_type.into(), + is_primary, + ..Default::default() + }; + new_field.with_type_option_data(field_type, default_type_option_data_from_type(field_type)) + } + pub fn get_type_option>(&self, type_id: impl ToString) -> Option { let type_option_data = self.type_options.get(&type_id.to_string())?.clone(); Some(T::from(type_option_data)) diff --git a/collab-database/src/fields/field_settings.rs b/collab-database/src/fields/field_settings.rs new file mode 100644 index 000000000..c58e7acdc --- /dev/null +++ b/collab-database/src/fields/field_settings.rs @@ -0,0 +1,183 @@ +use std::collections::HashMap; + +use collab::util::AnyMapExt; +use strum::IntoEnumIterator; +use yrs::Any; + +use crate::views::{ + DatabaseLayout, FieldSettingsByFieldIdMap, FieldSettingsMap, FieldSettingsMapBuilder, +}; + +use super::Field; + +#[repr(u8)] +#[derive(Debug, Default, Clone, Eq, PartialEq)] +pub enum FieldVisibility { + #[default] + AlwaysShown = 0, + HideWhenEmpty = 1, + AlwaysHidden = 2, +} + +macro_rules! impl_into_field_visibility { + ($target: ident) => { + impl std::convert::From<$target> for FieldVisibility { + fn from(ty: $target) -> Self { + match ty { + 0 => FieldVisibility::AlwaysShown, + 1 => FieldVisibility::HideWhenEmpty, + 2 => FieldVisibility::AlwaysHidden, + _ => { + tracing::error!("🔴Can't parse FieldVisibility from value: {}", ty); + FieldVisibility::AlwaysShown + }, + } + } + } + }; +} + +impl_into_field_visibility!(i64); +impl_into_field_visibility!(u8); + +impl From for i64 { + fn from(value: FieldVisibility) -> Self { + (value as u8) as i64 + } +} + +/// Stores the field settings for a single field +#[derive(Debug, Clone)] +pub struct FieldSettings { + pub field_id: String, + pub visibility: FieldVisibility, + pub width: i32, + pub wrap_cell_content: bool, +} + +/// Helper struct to create a new field setting +pub struct FieldSettingsBuilder { + inner: FieldSettings, +} + +impl FieldSettingsBuilder { + pub fn new(field_id: &str) -> Self { + let field_settings = FieldSettings { + field_id: field_id.to_string(), + visibility: FieldVisibility::AlwaysShown, + width: DEFAULT_WIDTH, + wrap_cell_content: true, + }; + + Self { + inner: field_settings, + } + } + + pub fn visibility(mut self, visibility: FieldVisibility) -> Self { + self.inner.visibility = visibility; + self + } + + pub fn width(mut self, width: i32) -> Self { + self.inner.width = width; + self + } + + pub fn build(self) -> FieldSettings { + self.inner + } +} + +pub const VISIBILITY: &str = "visibility"; +pub const WIDTH: &str = "width"; +pub const DEFAULT_WIDTH: i32 = 150; +pub const WRAP_CELL_CONTENT: &str = "wrap"; + +pub fn default_field_visibility(layout_type: DatabaseLayout) -> FieldVisibility { + match layout_type { + DatabaseLayout::Grid => FieldVisibility::AlwaysShown, + DatabaseLayout::Board => FieldVisibility::HideWhenEmpty, + DatabaseLayout::Calendar => FieldVisibility::HideWhenEmpty, + } +} + +pub fn default_field_settings_for_fields( + fields: &[Field], + layout_type: DatabaseLayout, +) -> FieldSettingsByFieldIdMap { + fields + .iter() + .map(|field| { + let field_settings = field_settings_for_field(layout_type, field); + (field.id.clone(), field_settings) + }) + .collect::>() + .into() +} + +pub fn field_settings_for_field( + database_layout: DatabaseLayout, + field: &Field, +) -> FieldSettingsMap { + let visibility = if field.is_primary { + FieldVisibility::AlwaysShown + } else { + default_field_visibility(database_layout) + }; + + FieldSettingsBuilder::new(&field.id) + .visibility(visibility) + .build() + .into() +} + +pub fn default_field_settings_by_layout_map() -> HashMap { + let mut map = HashMap::new(); + for layout_ty in DatabaseLayout::iter() { + let visibility = default_field_visibility(layout_ty); + let field_settings = + FieldSettingsMapBuilder::from([(VISIBILITY.into(), Any::BigInt(i64::from(visibility)))]); + map.insert(layout_ty, field_settings); + } + + map +} + +impl FieldSettings { + pub fn from_any_map( + field_id: &str, + layout_type: DatabaseLayout, + field_settings: &FieldSettingsMap, + ) -> Self { + let visibility = field_settings + .get_as::(VISIBILITY) + .map(Into::into) + .unwrap_or_else(|| default_field_visibility(layout_type)); + let width = field_settings.get_as::(WIDTH).unwrap_or(DEFAULT_WIDTH); + let wrap_cell_content: bool = field_settings.get_as(WRAP_CELL_CONTENT).unwrap_or(true); + + Self { + field_id: field_id.to_string(), + visibility, + width, + wrap_cell_content, + } + } +} + +impl From for FieldSettingsMap { + fn from(field_settings: FieldSettings) -> Self { + FieldSettingsMapBuilder::from([ + ( + VISIBILITY.into(), + Any::BigInt(i64::from(field_settings.visibility)), + ), + (WIDTH.into(), Any::BigInt(field_settings.width as i64)), + ( + WRAP_CELL_CONTENT.into(), + Any::Bool(field_settings.wrap_cell_content), + ), + ]) + } +} diff --git a/collab-database/src/fields/mod.rs b/collab-database/src/fields/mod.rs index a1eca5749..4edb29004 100644 --- a/collab-database/src/fields/mod.rs +++ b/collab-database/src/fields/mod.rs @@ -2,10 +2,12 @@ mod field; mod field_id; mod field_map; mod field_observer; +mod field_settings; mod type_option; pub use field::*; pub use field_id::*; pub use field_map::*; pub use field_observer::*; +pub use field_settings::*; pub use type_option::*; diff --git a/collab-database/src/fields/type_option/checklist_type_option.rs b/collab-database/src/fields/type_option/checklist_type_option.rs new file mode 100644 index 000000000..285455756 --- /dev/null +++ b/collab-database/src/fields/type_option/checklist_type_option.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +use super::{TypeOptionData, TypeOptionDataBuilder}; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct ChecklistTypeOption; + +impl From for ChecklistTypeOption { + fn from(_data: TypeOptionData) -> Self { + Self + } +} + +impl From for TypeOptionData { + fn from(_data: ChecklistTypeOption) -> Self { + TypeOptionDataBuilder::default() + } +} diff --git a/collab-database/src/fields/type_option/mod.rs b/collab-database/src/fields/type_option/mod.rs index f0ed87f5c..f7700bc8c 100644 --- a/collab-database/src/fields/type_option/mod.rs +++ b/collab-database/src/fields/type_option/mod.rs @@ -1,10 +1,14 @@ pub mod checkbox_type_option; +pub mod checklist_type_option; pub mod date_type_option; pub mod media_type_option; pub mod number_type_option; +pub mod relation_type_option; pub mod select_type_option; +pub mod summary_type_option; pub mod text_type_option; pub mod timestamp_type_option; +pub mod translate_type_option; pub mod url_type_option; use std::collections::HashMap; diff --git a/collab-database/src/fields/type_option/relation_type_option.rs b/collab-database/src/fields/type_option/relation_type_option.rs new file mode 100644 index 000000000..97ebb5e38 --- /dev/null +++ b/collab-database/src/fields/type_option/relation_type_option.rs @@ -0,0 +1,22 @@ +use collab::util::AnyMapExt; +use serde::{Deserialize, Serialize}; + +use super::{TypeOptionData, TypeOptionDataBuilder}; + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct RelationTypeOption { + pub database_id: String, +} + +impl From for RelationTypeOption { + fn from(data: TypeOptionData) -> Self { + let database_id: String = data.get_as("database_id").unwrap_or_default(); + Self { database_id } + } +} + +impl From for TypeOptionData { + fn from(data: RelationTypeOption) -> Self { + TypeOptionDataBuilder::from([("database_id".into(), data.database_id.into())]) + } +} diff --git a/collab-database/src/fields/type_option/summary_type_option.rs b/collab-database/src/fields/type_option/summary_type_option.rs new file mode 100644 index 000000000..90e76e164 --- /dev/null +++ b/collab-database/src/fields/type_option/summary_type_option.rs @@ -0,0 +1,22 @@ +use collab::util::AnyMapExt; +use serde::{Deserialize, Serialize}; + +use super::{TypeOptionData, TypeOptionDataBuilder}; + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct SummarizationTypeOption { + pub auto_fill: bool, +} + +impl From for SummarizationTypeOption { + fn from(data: TypeOptionData) -> Self { + let auto_fill: bool = data.get_as("auto_fill").unwrap_or_default(); + Self { auto_fill } + } +} + +impl From for TypeOptionData { + fn from(data: SummarizationTypeOption) -> Self { + TypeOptionDataBuilder::from([("auto_fill".into(), data.auto_fill.into())]) + } +} diff --git a/collab-database/src/fields/type_option/translate_type_option.rs b/collab-database/src/fields/type_option/translate_type_option.rs new file mode 100644 index 000000000..a53ddbab4 --- /dev/null +++ b/collab-database/src/fields/type_option/translate_type_option.rs @@ -0,0 +1,54 @@ +use serde::{Deserialize, Serialize}; +use yrs::{encoding::serde::from_any, Any}; + +use super::{TypeOptionData, TypeOptionDataBuilder}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TranslateTypeOption { + #[serde(default)] + pub auto_fill: bool, + /// Use [TranslateTypeOption::language_from_type] to get the language name + #[serde(default, rename = "language")] + pub language_type: i64, +} + +impl TranslateTypeOption { + pub fn language_from_type(language_type: i64) -> &'static str { + match language_type { + 0 => "Traditional Chinese", + 1 => "English", + 2 => "French", + 3 => "German", + 4 => "Hindi", + 5 => "Spanish", + 6 => "Portuguese", + 7 => "Standard Arabic", + 8 => "Simplified Chinese", + _ => "English", + } + } +} + +impl Default for TranslateTypeOption { + fn default() -> Self { + Self { + auto_fill: false, + language_type: 1, + } + } +} + +impl From for TranslateTypeOption { + fn from(data: TypeOptionData) -> Self { + from_any(&Any::from(data)).unwrap() + } +} + +impl From for TypeOptionData { + fn from(value: TranslateTypeOption) -> Self { + TypeOptionDataBuilder::from([ + ("auto_fill".into(), value.auto_fill.into()), + ("language".into(), Any::BigInt(value.language_type)), + ]) + } +} diff --git a/collab-database/src/template/builder.rs b/collab-database/src/template/builder.rs index 1a6a6ffb4..56283389e 100644 --- a/collab-database/src/template/builder.rs +++ b/collab-database/src/template/builder.rs @@ -176,7 +176,7 @@ impl FieldTemplateBuilder { database_id: &str, file_url_builder: &Option>, ) -> (FieldTemplate, Vec) { - let field_type = self.field_type.clone(); + let field_type = self.field_type; let mut field_template = FieldTemplate { field_id: self.field_id, name: self.name, @@ -196,7 +196,7 @@ impl FieldTemplateBuilder { replace_cells_with_options_id(self.cells, &type_option.options, SELECT_OPTION_SEPARATOR) .into_iter() .map(|id| { - let mut map = new_cell_builder(field_type.clone()); + let mut map = new_cell_builder(field_type); map.insert(CELL_DATA.to_string(), Any::from(id)); map }) @@ -211,7 +211,7 @@ impl FieldTemplateBuilder { let cell_template = replace_cells_with_timestamp(self.cells) .into_iter() .map(|id| { - let mut map = new_cell_builder(field_type.clone()); + let mut map = new_cell_builder(field_type); map.insert(CELL_DATA.to_string(), Any::from(id)); map }) @@ -229,12 +229,12 @@ impl FieldTemplateBuilder { let cell_template = replace_cells_with_timestamp(self.cells) .into_iter() .map(|id| { - let mut map = new_cell_builder(field_type.clone()); + let mut map = new_cell_builder(field_type); map.insert(CELL_DATA.to_string(), Any::from(id)); map }) .collect::>(); - let type_option = TimestampTypeOption::new(field_type.clone()); + let type_option = TimestampTypeOption::new(field_type); field_template .type_options .insert(field_type, type_option.into()); @@ -268,7 +268,7 @@ impl FieldTemplateBuilder { .await .into_iter() .map(|file| { - let mut cells = new_cell_builder(field_type.clone()); + let mut cells = new_cell_builder(field_type); if let Some(file) = file { cells.insert(CELL_DATA.to_string(), Any::from(file)); } @@ -293,7 +293,7 @@ fn string_cell_template(field_type: &FieldType, cell: Vec) -> Vec; @@ -15,3 +19,86 @@ pub type GroupSettingBuilder = HashMap; pub type GroupMap = HashMap; /// [GroupMapBuilder] is the builder for [GroupMap] pub type GroupMapBuilder = HashMap; + +#[derive(Debug, Clone, Default, Deserialize)] +pub struct GroupSetting { + pub id: String, + pub field_id: String, + #[serde(rename = "ty")] + pub field_type: i64, + #[serde(default)] + pub groups: Vec, + #[serde(default)] + pub content: String, +} + +impl GroupSetting { + pub fn new(field_id: String, field_type: i64, content: String) -> Self { + Self { + id: gen_database_group_id(), + field_id, + field_type, + groups: vec![], + content, + } + } +} + +const GROUP_ID: &str = "id"; +const FIELD_ID: &str = "field_id"; +const FIELD_TYPE: &str = "ty"; +const GROUPS: &str = "groups"; +const CONTENT: &str = "content"; + +impl TryFrom for GroupSetting { + type Error = anyhow::Error; + + fn try_from(value: GroupSettingMap) -> Result { + from_any(&Any::from(value)).map_err(|e| e.into()) + } +} + +impl From for GroupSettingMap { + fn from(setting: GroupSetting) -> Self { + let groups = to_any(&setting.groups).unwrap_or_else(|_| Any::Array(Arc::from([]))); + GroupSettingBuilder::from([ + (GROUP_ID.into(), setting.id.into()), + (FIELD_ID.into(), setting.field_id.into()), + (FIELD_TYPE.into(), Any::BigInt(setting.field_type)), + (GROUPS.into(), groups), + (CONTENT.into(), setting.content.into()), + ]) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct Group { + pub id: String, + #[serde(default = "GROUP_VISIBILITY")] + pub visible: bool, +} + +impl TryFrom for Group { + type Error = anyhow::Error; + + fn try_from(value: GroupMap) -> Result { + from_any(&Any::from(value)).map_err(|e| e.into()) + } +} + +impl From for GroupMap { + fn from(group: Group) -> Self { + GroupMapBuilder::from([ + ("id".into(), group.id.into()), + ("visible".into(), group.visible.into()), + ]) + } +} + +const GROUP_VISIBILITY: fn() -> bool = || true; + +impl Group { + pub fn new(id: String) -> Self { + Self { id, visible: true } + } +} diff --git a/collab-database/src/views/layout_settings.rs b/collab-database/src/views/layout_settings.rs new file mode 100644 index 000000000..64aeb3fd4 --- /dev/null +++ b/collab-database/src/views/layout_settings.rs @@ -0,0 +1,120 @@ +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use yrs::{encoding::serde::from_any, Any}; + +use super::{LayoutSetting, LayoutSettingBuilder}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CalendarLayoutSetting { + #[serde(default)] + pub layout_ty: CalendarLayout, + #[serde(default)] + pub first_day_of_week: i32, + #[serde(default)] + pub show_weekends: bool, + #[serde(default)] + pub show_week_numbers: bool, + #[serde(default)] + pub field_id: String, +} + +impl From for CalendarLayoutSetting { + fn from(setting: LayoutSetting) -> Self { + from_any(&Any::from(setting)).unwrap() + } +} + +impl From for LayoutSetting { + fn from(setting: CalendarLayoutSetting) -> Self { + LayoutSettingBuilder::from([ + ("layout_ty".into(), Any::BigInt(setting.layout_ty.value())), + ( + "first_day_of_week".into(), + Any::BigInt(setting.first_day_of_week as i64), + ), + ( + "show_week_numbers".into(), + Any::Bool(setting.show_week_numbers), + ), + ("show_weekends".into(), Any::Bool(setting.show_weekends)), + ("field_id".into(), setting.field_id.into()), + ]) + } +} + +impl CalendarLayoutSetting { + pub fn new(field_id: String) -> Self { + CalendarLayoutSetting { + layout_ty: CalendarLayout::default(), + first_day_of_week: DEFAULT_FIRST_DAY_OF_WEEK, + show_weekends: DEFAULT_SHOW_WEEKENDS, + show_week_numbers: DEFAULT_SHOW_WEEK_NUMBERS, + field_id, + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum CalendarLayout { + #[default] + Month = 0, + Week = 1, + Day = 2, +} + +impl From for CalendarLayout { + fn from(value: i64) -> Self { + match value { + 0 => CalendarLayout::Month, + 1 => CalendarLayout::Week, + 2 => CalendarLayout::Day, + _ => CalendarLayout::Month, + } + } +} + +impl CalendarLayout { + pub fn value(&self) -> i64 { + *self as i64 + } +} + +pub const DEFAULT_FIRST_DAY_OF_WEEK: i32 = 0; +pub const DEFAULT_SHOW_WEEKENDS: bool = true; +pub const DEFAULT_SHOW_WEEK_NUMBERS: bool = true; + +#[derive(Debug, Clone, Default, Deserialize)] +pub struct BoardLayoutSetting { + #[serde(default)] + pub hide_ungrouped_column: bool, + #[serde(default)] + pub collapse_hidden_groups: bool, +} + +impl BoardLayoutSetting { + pub fn new() -> Self { + Self::default() + } +} + +impl From for BoardLayoutSetting { + fn from(setting: LayoutSetting) -> Self { + from_any(&Any::from(setting)).unwrap() + } +} + +impl From for LayoutSetting { + fn from(setting: BoardLayoutSetting) -> Self { + LayoutSettingBuilder::from([ + ( + "hide_ungrouped_column".into(), + setting.hide_ungrouped_column.into(), + ), + ( + "collapse_hidden_groups".into(), + setting.collapse_hidden_groups.into(), + ), + ]) + } +} diff --git a/collab-database/src/views/mod.rs b/collab-database/src/views/mod.rs index 38679a24a..acf3375b6 100644 --- a/collab-database/src/views/mod.rs +++ b/collab-database/src/views/mod.rs @@ -5,6 +5,7 @@ mod field_settings; mod filter; mod group; mod layout; +mod layout_settings; mod row_order; mod sort; mod view; @@ -17,6 +18,7 @@ pub use field_settings::*; pub use filter::*; pub use group::*; pub use layout::*; +pub use layout_settings::*; pub use row_order::*; pub use sort::*; pub use view::*; diff --git a/collab-database/tests/template_test/create_template_test.rs b/collab-database/tests/template_test/create_template_test.rs index de2e7d440..0c5ad987a 100644 --- a/collab-database/tests/template_test/create_template_test.rs +++ b/collab-database/tests/template_test/create_template_test.rs @@ -124,7 +124,7 @@ async fn create_template_test() { let fields = database.get_fields_in_view(database.get_inline_view_id().as_str(), None); assert_eq!(fields.len(), 6); for (index, field) in fields.iter().enumerate() { - assert_eq!(field.field_type, expected_field_type[index].clone() as i64); + assert_eq!(field.field_type, expected_field_type[index] as i64); assert_eq!(field.name, expected_field_name[index]); } diff --git a/collab-importer/tests/notion_test/import_test.rs b/collab-importer/tests/notion_test/import_test.rs index 5048c5de9..2d3441608 100644 --- a/collab-importer/tests/notion_test/import_test.rs +++ b/collab-importer/tests/notion_test/import_test.rs @@ -529,7 +529,7 @@ async fn check_task_database(linked_view: &NotionPage) { assert_eq!(fields.len(), csv_file.columns.len()); assert_eq!(fields.len(), 13); - let expected_file_type = vec![ + let expected_file_type = [ RichText, SingleSelect, SingleSelect, @@ -571,7 +571,7 @@ async fn check_project_database(linked_view: &NotionPage, include_sub_dir: bool) assert_eq!(fields.len(), csv_file.columns.len()); assert_eq!(fields.len(), 13); - let expected_file_type = vec![ + let expected_file_type = [ RichText, SingleSelect, SingleSelect,