Merge pull request #1268 from AppFlowy-IO/fix/edit_no_status_card

Fix/edit no status card
This commit is contained in:
Nathan.fooo
2022-10-12 16:19:10 +08:00
committed by GitHub
29 changed files with 296 additions and 210 deletions

View File

@ -54,7 +54,7 @@ impl GridBlockManager {
pub(crate) async fn get_editor_from_row_id(&self, row_id: &str) -> FlowyResult<Arc<GridBlockRevisionEditor>> { pub(crate) async fn get_editor_from_row_id(&self, row_id: &str) -> FlowyResult<Arc<GridBlockRevisionEditor>> {
let block_id = self.persistence.get_block_id(row_id)?; let block_id = self.persistence.get_block_id(row_id)?;
Ok(self.get_block_editor(&block_id).await?) self.get_block_editor(&block_id).await
} }
#[tracing::instrument(level = "trace", skip(self, start_row_id), err)] #[tracing::instrument(level = "trace", skip(self, start_row_id), err)]

View File

@ -30,8 +30,7 @@ impl GridViewRowDelegate for Arc<GridBlockManager> {
let blocks = block_manager.get_block_snapshots(None).await.unwrap(); let blocks = block_manager.get_block_snapshots(None).await.unwrap();
blocks blocks
.into_iter() .into_iter()
.map(|block| block.row_revs) .flat_map(|block| block.row_revs)
.flatten()
.collect::<Vec<Arc<RowRevision>>>() .collect::<Vec<Arc<RowRevision>>>()
}) })
} }

View File

@ -113,11 +113,15 @@ impl AnyCellData {
/// * Use URLCellData to parse the data when the FieldType is URL. /// * Use URLCellData to parse the data when the FieldType is URL.
/// * Use String to parse the data when the FieldType is RichText, Number, or Checkbox. /// * Use String to parse the data when the FieldType is RichText, Number, or Checkbox.
/// * Check out the implementation of CellDataOperation trait for more information. /// * Check out the implementation of CellDataOperation trait for more information.
#[derive(Default)] #[derive(Default, Debug)]
pub struct CellBytes(pub Bytes); pub struct CellBytes(pub Bytes);
pub trait CellDataIsEmpty {
fn is_empty(&self) -> bool;
}
pub trait CellBytesParser { pub trait CellBytesParser {
type Object; type Object: CellDataIsEmpty;
fn parser(bytes: &Bytes) -> FlowyResult<Self::Object>; fn parser(bytes: &Bytes) -> FlowyResult<Self::Object>;
} }

View File

@ -1,4 +1,4 @@
use crate::services::cell::{CellBytesParser, FromCellString}; use crate::services::cell::{CellBytesParser, CellDataIsEmpty, FromCellString};
use bytes::Bytes; use bytes::Bytes;
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
use std::str::FromStr; use std::str::FromStr;
@ -61,6 +61,13 @@ impl ToString for CheckboxCellData {
self.0.clone() self.0.clone()
} }
} }
impl CellDataIsEmpty for CheckboxCellData {
fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
pub struct CheckboxCellDataParser(); pub struct CheckboxCellDataParser();
impl CellBytesParser for CheckboxCellDataParser { impl CellBytesParser for CheckboxCellDataParser {
type Object = CheckboxCellData; type Object = CheckboxCellData;

View File

@ -1,6 +1,6 @@
use crate::entities::CellChangesetPB; use crate::entities::CellChangesetPB;
use crate::entities::{GridCellIdPB, GridCellIdParams}; use crate::entities::{GridCellIdPB, GridCellIdParams};
use crate::services::cell::{CellBytesParser, FromCellChangeset, FromCellString}; use crate::services::cell::{CellBytesParser, CellDataIsEmpty, FromCellChangeset, FromCellString};
use bytes::Bytes; use bytes::Bytes;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
@ -200,6 +200,12 @@ impl std::default::Default for TimeFormat {
} }
} }
impl CellDataIsEmpty for DateCellDataPB {
fn is_empty(&self) -> bool {
self.date.is_empty()
}
}
pub struct DateCellDataParser(); pub struct DateCellDataParser();
impl CellBytesParser for DateCellDataParser { impl CellBytesParser for DateCellDataParser {
type Object = DateCellDataPB; type Object = DateCellDataPB;

View File

@ -1,4 +1,4 @@
use crate::services::cell::{CellBytesCustomParser, CellBytesParser}; use crate::services::cell::{CellBytesCustomParser, CellBytesParser, CellDataIsEmpty};
use crate::services::field::number_currency::Currency; use crate::services::field::number_currency::Currency;
use crate::services::field::{strip_currency_symbol, NumberFormat, STRIP_SYMBOL}; use crate::services::field::{strip_currency_symbol, NumberFormat, STRIP_SYMBOL};
use bytes::Bytes; use bytes::Bytes;
@ -93,6 +93,12 @@ impl ToString for NumberCellData {
} }
} }
} }
impl CellDataIsEmpty for NumberCellData {
fn is_empty(&self) -> bool {
self.decimal.is_none()
}
}
pub struct NumberCellDataParser(); pub struct NumberCellDataParser();
impl CellBytesParser for NumberCellDataParser { impl CellBytesParser for NumberCellDataParser {
type Object = NumberCellData; type Object = NumberCellData;

View File

@ -117,12 +117,12 @@ impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
match field_type { match field_type {
FieldType::Checkbox => { FieldType::Checkbox => {
//Add Yes and No options if it's not exist. //Add Yes and No options if it's not exist.
if self.0.options.iter().find(|option| option.name == CHECK).is_none() { if !self.0.options.iter().any(|option| option.name == CHECK) {
let check_option = SelectOptionPB::with_color(CHECK, SelectOptionColorPB::Green); let check_option = SelectOptionPB::with_color(CHECK, SelectOptionColorPB::Green);
self.0.options.push(check_option); self.0.options.push(check_option);
} }
if self.0.options.iter().find(|option| option.name == UNCHECK).is_none() { if !self.0.options.iter().any(|option| option.name == UNCHECK) {
let uncheck_option = SelectOptionPB::with_color(UNCHECK, SelectOptionColorPB::Yellow); let uncheck_option = SelectOptionPB::with_color(UNCHECK, SelectOptionColorPB::Yellow);
self.0.options.push(uncheck_option); self.0.options.push(uncheck_option);
} }

View File

@ -1,5 +1,7 @@
use crate::entities::{CellChangesetPB, FieldType, GridCellIdPB, GridCellIdParams}; use crate::entities::{CellChangesetPB, FieldType, GridCellIdPB, GridCellIdParams};
use crate::services::cell::{CellBytes, CellBytesParser, CellData, CellDisplayable, FromCellChangeset, FromCellString}; use crate::services::cell::{
CellBytes, CellBytesParser, CellData, CellDataIsEmpty, CellDisplayable, FromCellChangeset, FromCellString,
};
use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB}; use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
use bytes::Bytes; use bytes::Bytes;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
@ -233,7 +235,7 @@ impl ToString for SelectOptionIds {
impl std::convert::From<Option<String>> for SelectOptionIds { impl std::convert::From<Option<String>> for SelectOptionIds {
fn from(s: Option<String>) -> Self { fn from(s: Option<String>) -> Self {
match s { match s {
None => Self { 0: vec![] }, None => Self(vec![]),
Some(s) => Self::from(s), Some(s) => Self::from(s),
} }
} }
@ -252,6 +254,13 @@ impl std::ops::DerefMut for SelectOptionIds {
&mut self.0 &mut self.0
} }
} }
impl CellDataIsEmpty for SelectOptionIds {
fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
pub struct SelectOptionIdsParser(); pub struct SelectOptionIdsParser();
impl CellBytesParser for SelectOptionIdsParser { impl CellBytesParser for SelectOptionIdsParser {
type Object = SelectOptionIds; type Object = SelectOptionIds;
@ -263,6 +272,12 @@ impl CellBytesParser for SelectOptionIdsParser {
} }
} }
impl CellDataIsEmpty for SelectOptionCellDataPB {
fn is_empty(&self) -> bool {
self.select_options.is_empty()
}
}
pub struct SelectOptionCellDataParser(); pub struct SelectOptionCellDataParser();
impl CellBytesParser for SelectOptionCellDataParser { impl CellBytesParser for SelectOptionCellDataParser {
type Object = SelectOptionCellDataPB; type Object = SelectOptionCellDataPB;

View File

@ -61,7 +61,6 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for SingleSel
_cell_rev: Option<CellRevision>, _cell_rev: Option<CellRevision>,
) -> Result<String, FlowyError> { ) -> Result<String, FlowyError> {
let content_changeset = changeset.try_into_inner()?; let content_changeset = changeset.try_into_inner()?;
let new_cell_data: String;
let mut insert_option_ids = content_changeset let mut insert_option_ids = content_changeset
.insert_option_ids .insert_option_ids
@ -73,14 +72,12 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for SingleSel
// Sometimes, the insert_option_ids may contain list of option ids. For example, // Sometimes, the insert_option_ids may contain list of option ids. For example,
// copy/paste a ids string. // copy/paste a ids string.
if insert_option_ids.is_empty() { if insert_option_ids.is_empty() {
new_cell_data = "".to_string() Ok("".to_string())
} else { } else {
// Just take the first select option // Just take the first select option
let _ = insert_option_ids.drain(1..); let _ = insert_option_ids.drain(1..);
new_cell_data = insert_option_ids.pop().unwrap(); Ok(insert_option_ids.pop().unwrap())
} }
Ok(new_cell_data)
} }
} }
@ -109,12 +106,12 @@ impl TypeOptionBuilder for SingleSelectTypeOptionBuilder {
match field_type { match field_type {
FieldType::Checkbox => { FieldType::Checkbox => {
//add Yes and No options if it's not exist. //add Yes and No options if it's not exist.
if self.0.options.iter().find(|option| option.name == CHECK).is_none() { if !self.0.options.iter().any(|option| option.name == CHECK) {
let check_option = SelectOptionPB::with_color(CHECK, SelectOptionColorPB::Green); let check_option = SelectOptionPB::with_color(CHECK, SelectOptionColorPB::Green);
self.0.options.push(check_option); self.0.options.push(check_option);
} }
if self.0.options.iter().find(|option| option.name == UNCHECK).is_none() { if !self.0.options.iter().any(|option| option.name == UNCHECK) {
let uncheck_option = SelectOptionPB::with_color(UNCHECK, SelectOptionColorPB::Yellow); let uncheck_option = SelectOptionPB::with_color(UNCHECK, SelectOptionColorPB::Yellow);
self.0.options.push(uncheck_option); self.0.options.push(uncheck_option);
} }

View File

@ -1,8 +1,8 @@
use crate::entities::FieldType; use crate::entities::FieldType;
use crate::impl_type_option; use crate::impl_type_option;
use crate::services::cell::{ use crate::services::cell::{
decode_cell_data_to_string, CellBytes, CellBytesParser, CellData, CellDataChangeset, CellDataOperation, decode_cell_data_to_string, CellBytes, CellBytesParser, CellData, CellDataChangeset, CellDataIsEmpty,
CellDisplayable, FromCellString, CellDataOperation, CellDisplayable, FromCellString,
}; };
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use bytes::Bytes; use bytes::Bytes;
@ -125,6 +125,12 @@ impl ToString for TextCellData {
} }
} }
impl CellDataIsEmpty for TextCellData {
fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
pub struct TextCellDataParser(); pub struct TextCellDataParser();
impl CellBytesParser for TextCellDataParser { impl CellBytesParser for TextCellDataParser {
type Object = TextCellData; type Object = TextCellData;

View File

@ -1,4 +1,4 @@
use crate::services::cell::{CellBytesParser, FromCellString}; use crate::services::cell::{CellBytesParser, CellDataIsEmpty, FromCellString};
use bytes::Bytes; use bytes::Bytes;
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
use flowy_error::{internal_error, FlowyResult}; use flowy_error::{internal_error, FlowyResult};
@ -26,6 +26,12 @@ impl URLCellDataPB {
} }
} }
impl CellDataIsEmpty for URLCellDataPB {
fn is_empty(&self) -> bool {
self.content.is_empty()
}
}
pub struct URLCellDataParser(); pub struct URLCellDataParser();
impl CellBytesParser for URLCellDataParser { impl CellBytesParser for URLCellDataParser {
type Object = URLCellDataPB; type Object = URLCellDataPB;

View File

@ -410,7 +410,7 @@ impl GridRevisionEditor {
pub async fn update_row(&self, changeset: RowChangeset) -> FlowyResult<()> { pub async fn update_row(&self, changeset: RowChangeset) -> FlowyResult<()> {
let row_id = changeset.row_id.clone(); let row_id = changeset.row_id.clone();
let _ = self.block_manager.update_row(changeset).await?; let _ = self.block_manager.update_row(changeset).await?;
self.view_manager.did_update_row(&row_id).await; self.view_manager.did_update_cell(&row_id).await;
Ok(()) Ok(())
} }
@ -504,7 +504,7 @@ impl GridRevisionEditor {
content, content,
}; };
let _ = self.block_manager.update_cell(cell_changeset).await?; let _ = self.block_manager.update_cell(cell_changeset).await?;
self.view_manager.did_update_cell(&row_id, &field_id).await; self.view_manager.did_update_cell(&row_id).await;
Ok(()) Ok(())
} }
} }

View File

@ -123,15 +123,15 @@ impl GridViewRevisionEditor {
}) })
.await; .await;
tracing::trace!("Delete row in view changeset: {:?}", changesets);
if let Some(changesets) = changesets { if let Some(changesets) = changesets {
tracing::trace!("{:?}", changesets);
for changeset in changesets { for changeset in changesets {
self.notify_did_update_group(changeset).await; self.notify_did_update_group(changeset).await;
} }
} }
} }
pub(crate) async fn did_update_view_row(&self, row_rev: &RowRevision) { pub(crate) async fn did_update_view_cell(&self, row_rev: &RowRevision) {
let changesets = self let changesets = self
.mut_group_controller(|group_controller, field_rev| { .mut_group_controller(|group_controller, field_rev| {
group_controller.did_update_group_row(row_rev, &field_rev) group_controller.did_update_group_row(row_rev, &field_rev)
@ -539,11 +539,10 @@ pub fn make_grid_setting(view_pad: &GridViewRevisionPad, field_revs: &[Arc<Field
.map(|filters_by_field_id| { .map(|filters_by_field_id| {
filters_by_field_id filters_by_field_id
.into_iter() .into_iter()
.map(|(_, v)| { .flat_map(|(_, v)| {
let repeated_filter: RepeatedGridFilterConfigurationPB = v.into(); let repeated_filter: RepeatedGridFilterConfigurationPB = v.into();
repeated_filter.items repeated_filter.items
}) })
.flatten()
.collect::<Vec<GridFilterConfigurationPB>>() .collect::<Vec<GridFilterConfigurationPB>>()
}) })
.unwrap_or_default(); .unwrap_or_default();
@ -553,11 +552,10 @@ pub fn make_grid_setting(view_pad: &GridViewRevisionPad, field_revs: &[Arc<Field
.map(|groups_by_field_id| { .map(|groups_by_field_id| {
groups_by_field_id groups_by_field_id
.into_iter() .into_iter()
.map(|(_, v)| { .flat_map(|(_, v)| {
let repeated_group: RepeatedGridGroupConfigurationPB = v.into(); let repeated_group: RepeatedGridGroupConfigurationPB = v.into();
repeated_group.items repeated_group.items
}) })
.flatten()
.collect::<Vec<GridGroupConfigurationPB>>() .collect::<Vec<GridGroupConfigurationPB>>()
}) })
.unwrap_or_default(); .unwrap_or_default();

View File

@ -77,15 +77,15 @@ impl GridViewManager {
} }
} }
/// Insert/Delete the group's row if the corresponding data was changed. /// Insert/Delete the group's row if the corresponding cell data was changed.
pub(crate) async fn did_update_row(&self, row_id: &str) { pub(crate) async fn did_update_cell(&self, row_id: &str) {
match self.row_delegate.gv_get_row_rev(row_id).await { match self.row_delegate.gv_get_row_rev(row_id).await {
None => { None => {
tracing::warn!("Can not find the row in grid view"); tracing::warn!("Can not find the row in grid view");
} }
Some(row_rev) => { Some(row_rev) => {
for view_editor in self.view_editors.iter() { for view_editor in self.view_editors.iter() {
view_editor.did_update_view_row(&row_rev).await; view_editor.did_update_view_cell(&row_rev).await;
} }
} }
} }
@ -97,10 +97,6 @@ impl GridViewManager {
Ok(()) Ok(())
} }
pub(crate) async fn did_update_cell(&self, row_id: &str, _field_id: &str) {
self.did_update_row(row_id).await
}
pub(crate) async fn did_delete_row(&self, row_rev: Arc<RowRevision>) { pub(crate) async fn did_delete_row(&self, row_rev: Arc<RowRevision>) {
for view_editor in self.view_editors.iter() { for view_editor in self.view_editors.iter() {
view_editor.did_delete_view_row(&row_rev).await; view_editor.did_delete_view_row(&row_rev).await;

View File

@ -1,19 +1,79 @@
use crate::entities::GroupChangesetPB; use crate::entities::{GroupChangesetPB, GroupViewChangesetPB};
use crate::services::cell::CellDataIsEmpty;
use crate::services::group::controller::MoveGroupRowContext; use crate::services::group::controller::MoveGroupRowContext;
use flowy_grid_data_model::revision::{CellRevision, RowRevision}; use crate::services::group::Group;
use flowy_error::FlowyResult;
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, RowRevision};
use std::sync::Arc;
pub trait GroupAction: Send + Sync { /// Using polymorphism to provides the customs action for different group controller.
type CellDataType; ///
/// For example, the `CheckboxGroupController` implements this trait to provide custom behavior.
///
pub trait GroupControllerCustomActions: Send + Sync {
type CellDataType: CellDataIsEmpty;
/// Returns the a value of the cell, default value is None
///
/// Determine which group the row is placed in based on the data of the cell. If the cell data
/// is None. The row will be put in to the `No status` group
///
fn default_cell_rev(&self) -> Option<CellRevision> { fn default_cell_rev(&self) -> Option<CellRevision> {
None None
} }
fn use_default_group(&self) -> bool {
true /// Returns a bool value to determine whether the group should contain this cell or not.
}
fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool; fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool;
fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>;
fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>; /// Adds or removes a row if the cell data match the group filter.
// Move row from one group to another /// It gets called after editing the cell or row
///
fn add_or_remove_row_in_groups_if_match(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
) -> Vec<GroupChangesetPB>;
/// Deletes the row from the group
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>;
/// Move row from one group to another
fn move_row(&mut self, cell_data: &Self::CellDataType, context: MoveGroupRowContext) -> Vec<GroupChangesetPB>; fn move_row(&mut self, cell_data: &Self::CellDataType, context: MoveGroupRowContext) -> Vec<GroupChangesetPB>;
} }
/// Defines the shared actions any group controller can perform.
pub trait GroupControllerSharedActions: Send + Sync {
/// The field that is used for grouping the rows
fn field_id(&self) -> &str;
/// Returns number of groups the current field has
fn groups(&self) -> Vec<Group>;
/// Returns the index and the group data with group_id
fn get_group(&self, group_id: &str) -> Option<(usize, Group)>;
/// Separates the rows into different groups
fn fill_groups(&mut self, row_revs: &[Arc<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<()>;
/// Remove the group with from_group_id and insert it to the index with to_group_id
fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()>;
/// Insert/Remove the row to the group if the corresponding cell data is changed
fn did_update_group_row(
&mut self,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupChangesetPB>>;
/// Remove the row from the group if the row gets deleted
fn did_delete_delete_row(
&mut self,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupChangesetPB>>;
/// Move the row from one group to another group
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>>;
/// Update the group if the corresponding field is changed
fn did_update_group_field(&mut self, field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>>;
}

View File

@ -1,5 +1,5 @@
use crate::entities::{GroupPB, GroupViewChangesetPB}; use crate::entities::{GroupPB, GroupViewChangesetPB};
use crate::services::group::{default_group_configuration, make_no_status_group, GeneratedGroupConfig, Group}; use crate::services::group::{default_group_configuration, GeneratedGroupContext, Group};
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{ use flowy_grid_data_model::revision::{
FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRevision, FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRevision,
@ -118,8 +118,16 @@ where
} }
} }
/// Iterate mut the groups. The default group will be the last one that get mutated. /// Iterate mut the groups without `No status` group
pub(crate) fn iter_mut_all_groups(&mut self, mut each: impl FnMut(&mut Group)) { pub(crate) fn iter_mut_status_groups(&mut self, mut each: impl FnMut(&mut Group)) {
self.groups_map.iter_mut().for_each(|(_, group)| {
if group.id != self.field_rev.id {
each(group);
}
});
}
pub(crate) fn iter_mut_groups(&mut self, mut each: impl FnMut(&mut Group)) {
self.groups_map.iter_mut().for_each(|(_, group)| { self.groups_map.iter_mut().for_each(|(_, group)| {
each(group); each(group);
}); });
@ -172,14 +180,19 @@ where
/// [GroupConfigurationRevision] as old groups. The old groups and the new groups will be merged /// [GroupConfigurationRevision] as old groups. The old groups and the new groups will be merged
/// while keeping the order of the old groups. /// while keeping the order of the old groups.
/// ///
#[tracing::instrument(level = "trace", skip(self, generated_group_configs), err)] #[tracing::instrument(level = "trace", skip(self, generated_group_context), err)]
pub(crate) fn init_groups( pub(crate) fn init_groups(
&mut self, &mut self,
generated_group_configs: Vec<GeneratedGroupConfig>, generated_group_context: GeneratedGroupContext,
) -> FlowyResult<Option<GroupViewChangesetPB>> { ) -> FlowyResult<Option<GroupViewChangesetPB>> {
let GeneratedGroupContext {
no_status_group,
group_configs,
} = generated_group_context;
let mut new_groups = vec![]; let mut new_groups = vec![];
let mut filter_content_map = HashMap::new(); let mut filter_content_map = HashMap::new();
generated_group_configs.into_iter().for_each(|generate_group| { group_configs.into_iter().for_each(|generate_group| {
filter_content_map.insert(generate_group.group_rev.id.clone(), generate_group.filter_content); filter_content_map.insert(generate_group.group_rev.id.clone(), generate_group.filter_content);
new_groups.push(generate_group.group_rev); new_groups.push(generate_group.group_rev);
}); });
@ -190,12 +203,9 @@ where
old_groups.clear(); old_groups.clear();
} }
// When grouping by a new field, there is no `No status` group. So it needs
// to insert a `No status` group at index 0
// The `No status` group index is initialized to 0 // The `No status` group index is initialized to 0
// if let Some(no_status_group) = no_status_group {
if !old_groups.iter().any(|group| group.id == self.field_rev.id) { old_groups.insert(0, no_status_group);
old_groups.insert(0, make_no_status_group(&self.field_rev));
} }
// The `all_group_revs` is the combination of the new groups and old groups // The `all_group_revs` is the combination of the new groups and old groups
@ -236,9 +246,9 @@ where
Some(pos) => { Some(pos) => {
let mut old_group = configuration.groups.get_mut(pos).unwrap(); let mut old_group = configuration.groups.get_mut(pos).unwrap();
// Take the old group setting // Take the old group setting
group_rev.update_with_other(&old_group); group_rev.update_with_other(old_group);
if !is_changed { if !is_changed {
is_changed = is_group_changed(group_rev, &old_group); is_changed = is_group_changed(group_rev, old_group);
} }
// Consider the the name of the `group_rev` as the newest. // Consider the the name of the `group_rev` as the newest.
old_group.name = group_rev.name.clone(); old_group.name = group_rev.name.clone();

View File

@ -1,6 +1,6 @@
use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, InsertedRowPB, RowPB}; use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, InsertedRowPB, RowPB};
use crate::services::cell::{decode_any_cell_data, CellBytesParser}; use crate::services::cell::{decode_any_cell_data, CellBytesParser, CellDataIsEmpty};
use crate::services::group::action::GroupAction; use crate::services::group::action::{GroupControllerCustomActions, GroupControllerSharedActions};
use crate::services::group::configuration::GroupContext; use crate::services::group::configuration::GroupContext;
use crate::services::group::entities::Group; use crate::services::group::entities::Group;
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
@ -18,7 +18,7 @@ use std::sync::Arc;
/// If the [FieldType] doesn't implement its group controller, then the [DefaultGroupController] will /// If the [FieldType] doesn't implement its group controller, then the [DefaultGroupController] will
/// be used. /// be used.
/// ///
pub trait GroupController: GroupControllerActions + Send + Sync { pub trait GroupController: GroupControllerSharedActions + Send + Sync {
fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str); fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str);
fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str); fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str);
} }
@ -29,10 +29,15 @@ pub trait GroupGenerator {
type TypeOptionType; type TypeOptionType;
fn generate_groups( fn generate_groups(
field_id: &str, field_rev: &FieldRevision,
group_ctx: &Self::Context, group_ctx: &Self::Context,
type_option: &Option<Self::TypeOptionType>, type_option: &Option<Self::TypeOptionType>,
) -> Vec<GeneratedGroupConfig>; ) -> GeneratedGroupContext;
}
pub struct GeneratedGroupContext {
pub no_status_group: Option<GroupRevision>,
pub group_configs: Vec<GeneratedGroupConfig>,
} }
pub struct GeneratedGroupConfig { pub struct GeneratedGroupConfig {
@ -47,45 +52,6 @@ pub struct MoveGroupRowContext<'a> {
pub to_group_id: &'a str, pub to_group_id: &'a str,
pub to_row_id: Option<String>, pub to_row_id: Option<String>,
} }
/// Defines the shared actions each group controller can perform.
pub trait GroupControllerActions: Send + Sync {
/// The field that is used for grouping the rows
fn field_id(&self) -> &str;
/// Returns number of groups the current field has
fn groups(&self) -> Vec<Group>;
/// Returns the index and the group data with group_id
fn get_group(&self, group_id: &str) -> Option<(usize, Group)>;
/// Separates the rows into different groups
fn fill_groups(&mut self, row_revs: &[Arc<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<()>;
/// Remove the group with from_group_id and insert it to the index with to_group_id
fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()>;
/// Insert the row to the group if the corresponding cell data is changed
fn did_update_group_row(
&mut self,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupChangesetPB>>;
/// Remove the row from the group if the corresponding cell data is changed
fn did_delete_delete_row(
&mut self,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupChangesetPB>>;
/// Move the row from one group to another group
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>>;
/// Update the group if the corresponding field is changed
fn did_update_group_field(&mut self, field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>>;
}
/// C: represents the group configuration that impl [GroupConfigurationSerde] /// C: represents the group configuration that impl [GroupConfigurationSerde]
/// T: the type-option data deserializer that impl [TypeOptionDataDeserializer] /// T: the type-option data deserializer that impl [TypeOptionDataDeserializer]
/// G: the group generator, [GroupGenerator] /// G: the group generator, [GroupGenerator]
@ -106,8 +72,8 @@ where
{ {
pub async fn new(field_rev: &Arc<FieldRevision>, mut configuration: GroupContext<C>) -> FlowyResult<Self> { pub async fn new(field_rev: &Arc<FieldRevision>, mut configuration: GroupContext<C>) -> FlowyResult<Self> {
let type_option = field_rev.get_type_option::<T>(field_rev.ty); let type_option = field_rev.get_type_option::<T>(field_rev.ty);
let groups = G::generate_groups(&field_rev.id, &configuration, &type_option); let generated_group_context = G::generate_groups(field_rev, &configuration, &type_option);
let _ = configuration.init_groups(groups)?; let _ = configuration.init_groups(generated_group_context)?;
Ok(Self { Ok(Self {
field_id: field_rev.id.clone(), field_id: field_rev.id.clone(),
@ -186,30 +152,21 @@ where
} }
} }
impl<C, T, G, P> GroupControllerActions for GenericGroupController<C, T, G, P> impl<C, T, G, P> GroupControllerSharedActions for GenericGroupController<C, T, G, P>
where where
P: CellBytesParser, P: CellBytesParser,
C: GroupConfigurationContentSerde, C: GroupConfigurationContentSerde,
T: TypeOptionDataDeserializer, T: TypeOptionDataDeserializer,
G: GroupGenerator<Context = GroupContext<C>, TypeOptionType = T>, G: GroupGenerator<Context = GroupContext<C>, TypeOptionType = T>,
Self: GroupAction<CellDataType = P::Object>, Self: GroupControllerCustomActions<CellDataType = P::Object>,
{ {
fn field_id(&self) -> &str { fn field_id(&self) -> &str {
&self.field_id &self.field_id
} }
fn groups(&self) -> Vec<Group> { fn groups(&self) -> Vec<Group> {
if self.use_default_group() { self.group_ctx.groups().into_iter().cloned().collect()
self.group_ctx.groups().into_iter().cloned().collect()
} else {
self.group_ctx
.groups()
.into_iter()
.filter(|group| group.id != self.field_id)
.cloned()
.collect::<Vec<_>>()
}
} }
fn get_group(&self, group_id: &str) -> Option<(usize, Group)> { fn get_group(&self, group_id: &str) -> Option<(usize, Group)> {
@ -269,17 +226,14 @@ where
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1; let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1;
let cell_data = cell_bytes.parser::<P>()?; let cell_data = cell_bytes.parser::<P>()?;
let mut changesets = self.add_row_if_match(row_rev, &cell_data); let mut changesets = self.add_or_remove_row_in_groups_if_match(row_rev, &cell_data);
if self.use_default_group() { if let Some(default_group_changeset) = self.update_default_group(row_rev, &changesets) {
if let Some(default_group_changeset) = self.update_default_group(row_rev, &changesets) { tracing::trace!("default_group_changeset: {}", default_group_changeset);
tracing::trace!("default_group_changeset: {}", default_group_changeset); if !default_group_changeset.is_empty() {
if !default_group_changeset.is_empty() { changesets.push(default_group_changeset);
changesets.push(default_group_changeset);
}
} }
} }
Ok(changesets) Ok(changesets)
} else { } else {
Ok(vec![]) Ok(vec![])
@ -291,18 +245,30 @@ where
row_rev: &RowRevision, row_rev: &RowRevision,
field_rev: &FieldRevision, field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupChangesetPB>> { ) -> FlowyResult<Vec<GroupChangesetPB>> {
// if the cell_rev is none, then the row must be crated from the default group. // if the cell_rev is none, then the row must in the default group.
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1; let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1;
let cell_data = cell_bytes.parser::<P>()?; let cell_data = cell_bytes.parser::<P>()?;
Ok(self.remove_row_if_match(row_rev, &cell_data)) if !cell_data.is_empty() {
} else if let Some(group) = self.group_ctx.get_no_status_group() { tracing::error!("did_delete_delete_row {:?}", cell_rev.data);
Ok(vec![GroupChangesetPB::delete( return Ok(self.delete_row(row_rev, &cell_data));
group.id.clone(), }
vec![row_rev.id.clone()], }
)])
} else { match self.group_ctx.get_no_status_group() {
Ok(vec![]) None => {
tracing::error!("Unexpected None value. It should have the no status group");
Ok(vec![])
}
Some(no_status_group) => {
if !no_status_group.contains_row(&row_rev.id) {
tracing::error!("The row: {} should be in the no status group", row_rev.id);
}
Ok(vec![GroupChangesetPB::delete(
no_status_group.id.clone(),
vec![row_rev.id.clone()],
)])
}
} }
} }

View File

@ -1,13 +1,13 @@
use crate::entities::{GroupChangesetPB, InsertedRowPB, RowPB}; use crate::entities::{GroupChangesetPB, InsertedRowPB, RowPB};
use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK}; use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK};
use crate::services::group::action::GroupAction; use crate::services::group::action::GroupControllerCustomActions;
use crate::services::group::configuration::GroupContext; use crate::services::group::configuration::GroupContext;
use crate::services::group::controller::{ use crate::services::group::controller::{
GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
}; };
use crate::services::cell::insert_checkbox_cell; use crate::services::cell::insert_checkbox_cell;
use crate::services::group::{move_group_row, GeneratedGroupConfig}; use crate::services::group::{move_group_row, GeneratedGroupConfig, GeneratedGroupContext};
use flowy_grid_data_model::revision::{ use flowy_grid_data_model::revision::{
CellRevision, CheckboxGroupConfigurationRevision, FieldRevision, GroupRevision, RowRevision, CellRevision, CheckboxGroupConfigurationRevision, FieldRevision, GroupRevision, RowRevision,
}; };
@ -21,16 +21,12 @@ pub type CheckboxGroupController = GenericGroupController<
pub type CheckboxGroupContext = GroupContext<CheckboxGroupConfigurationRevision>; pub type CheckboxGroupContext = GroupContext<CheckboxGroupConfigurationRevision>;
impl GroupAction for CheckboxGroupController { impl GroupControllerCustomActions for CheckboxGroupController {
type CellDataType = CheckboxCellData; type CellDataType = CheckboxCellData;
fn default_cell_rev(&self) -> Option<CellRevision> { fn default_cell_rev(&self) -> Option<CellRevision> {
Some(CellRevision::new(UNCHECK.to_string())) Some(CellRevision::new(UNCHECK.to_string()))
} }
fn use_default_group(&self) -> bool {
false
}
fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool { fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool {
if cell_data.is_check() { if cell_data.is_check() {
content == CHECK content == CHECK
@ -39,9 +35,13 @@ impl GroupAction for CheckboxGroupController {
} }
} }
fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> { fn add_or_remove_row_in_groups_if_match(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
) -> Vec<GroupChangesetPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.group_ctx.iter_mut_all_groups(|group| { self.group_ctx.iter_mut_status_groups(|group| {
let mut changeset = GroupChangesetPB::new(group.id.clone()); let mut changeset = GroupChangesetPB::new(group.id.clone());
let is_not_contained = !group.contains_row(&row_rev.id); let is_not_contained = !group.contains_row(&row_rev.id);
if group.id == CHECK { if group.id == CHECK {
@ -81,9 +81,9 @@ impl GroupAction for CheckboxGroupController {
changesets changesets
} }
fn remove_row_if_match(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> { fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.group_ctx.iter_mut_all_groups(|group| { self.group_ctx.iter_mut_groups(|group| {
let mut changeset = GroupChangesetPB::new(group.id.clone()); let mut changeset = GroupChangesetPB::new(group.id.clone());
if group.contains_row(&row_rev.id) { if group.contains_row(&row_rev.id) {
changeset.deleted_rows.push(row_rev.id.clone()); changeset.deleted_rows.push(row_rev.id.clone());
@ -99,7 +99,7 @@ impl GroupAction for CheckboxGroupController {
fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> { fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
let mut group_changeset = vec![]; let mut group_changeset = vec![];
self.group_ctx.iter_mut_all_groups(|group| { self.group_ctx.iter_mut_groups(|group| {
if let Some(changeset) = move_group_row(group, &mut context) { if let Some(changeset) = move_group_row(group, &mut context) {
group_changeset.push(changeset); group_changeset.push(changeset);
} }
@ -133,10 +133,10 @@ impl GroupGenerator for CheckboxGroupGenerator {
type TypeOptionType = CheckboxTypeOptionPB; type TypeOptionType = CheckboxTypeOptionPB;
fn generate_groups( fn generate_groups(
_field_id: &str, _field_rev: &FieldRevision,
_group_ctx: &Self::Context, _group_ctx: &Self::Context,
_type_option: &Option<Self::TypeOptionType>, _type_option: &Option<Self::TypeOptionType>,
) -> Vec<GeneratedGroupConfig> { ) -> GeneratedGroupContext {
let check_group = GeneratedGroupConfig { let check_group = GeneratedGroupConfig {
group_rev: GroupRevision::new(CHECK.to_string(), "".to_string()), group_rev: GroupRevision::new(CHECK.to_string(), "".to_string()),
filter_content: CHECK.to_string(), filter_content: CHECK.to_string(),
@ -146,6 +146,10 @@ impl GroupGenerator for CheckboxGroupGenerator {
group_rev: GroupRevision::new(UNCHECK.to_string(), "".to_string()), group_rev: GroupRevision::new(UNCHECK.to_string(), "".to_string()),
filter_content: UNCHECK.to_string(), filter_content: UNCHECK.to_string(),
}; };
vec![check_group, uncheck_group]
GeneratedGroupContext {
no_status_group: None,
group_configs: vec![check_group, uncheck_group],
}
} }
} }

View File

@ -1,5 +1,6 @@
use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, RowPB}; use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, RowPB};
use crate::services::group::{Group, GroupController, GroupControllerActions, MoveGroupRowContext}; use crate::services::group::action::GroupControllerSharedActions;
use crate::services::group::{Group, GroupController, MoveGroupRowContext};
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; use flowy_grid_data_model::revision::{FieldRevision, RowRevision};
use std::sync::Arc; use std::sync::Arc;
@ -30,7 +31,7 @@ impl DefaultGroupController {
} }
} }
impl GroupControllerActions for DefaultGroupController { impl GroupControllerSharedActions for DefaultGroupController {
fn field_id(&self) -> &str { fn field_id(&self) -> &str {
&self.field_id &self.field_id
} }

View File

@ -1,14 +1,14 @@
use crate::entities::{GroupChangesetPB, RowPB}; use crate::entities::{GroupChangesetPB, RowPB};
use crate::services::cell::insert_select_option_cell; use crate::services::cell::insert_select_option_cell;
use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser}; use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser};
use crate::services::group::action::GroupAction; use crate::services::group::action::GroupControllerCustomActions;
use crate::services::group::controller::{ use crate::services::group::controller::{
GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
}; };
use crate::services::group::controller_impls::select_option_controller::util::*; use crate::services::group::controller_impls::select_option_controller::util::*;
use crate::services::group::GeneratedGroupConfig; use crate::services::group::{make_no_status_group, GeneratedGroupContext};
use flowy_grid_data_model::revision::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision}; use flowy_grid_data_model::revision::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision};
// MultiSelect // MultiSelect
@ -19,26 +19,30 @@ pub type MultiSelectGroupController = GenericGroupController<
SelectOptionCellDataParser, SelectOptionCellDataParser,
>; >;
impl GroupAction for MultiSelectGroupController { impl GroupControllerCustomActions for MultiSelectGroupController {
type CellDataType = SelectOptionCellDataPB; type CellDataType = SelectOptionCellDataPB;
fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool { fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool {
cell_data.select_options.iter().any(|option| option.id == content) cell_data.select_options.iter().any(|option| option.id == content)
} }
fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> { fn add_or_remove_row_in_groups_if_match(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
) -> Vec<GroupChangesetPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.group_ctx.iter_mut_all_groups(|group| { self.group_ctx.iter_mut_status_groups(|group| {
if let Some(changeset) = add_select_option_row(group, cell_data, row_rev) { if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) {
changesets.push(changeset); changesets.push(changeset);
} }
}); });
changesets changesets
} }
fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> { fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.group_ctx.iter_mut_all_groups(|group| { self.group_ctx.iter_mut_status_groups(|group| {
if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) { if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) {
changesets.push(changeset); changesets.push(changeset);
} }
@ -48,7 +52,7 @@ impl GroupAction for MultiSelectGroupController {
fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> { fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
let mut group_changeset = vec![]; let mut group_changeset = vec![];
self.group_ctx.iter_mut_all_groups(|group| { self.group_ctx.iter_mut_groups(|group| {
if let Some(changeset) = move_group_row(group, &mut context) { if let Some(changeset) = move_group_row(group, &mut context) {
group_changeset.push(changeset); group_changeset.push(changeset);
} }
@ -79,14 +83,20 @@ pub struct MultiSelectGroupGenerator();
impl GroupGenerator for MultiSelectGroupGenerator { impl GroupGenerator for MultiSelectGroupGenerator {
type Context = SelectOptionGroupContext; type Context = SelectOptionGroupContext;
type TypeOptionType = MultiSelectTypeOptionPB; type TypeOptionType = MultiSelectTypeOptionPB;
fn generate_groups( fn generate_groups(
field_id: &str, field_rev: &FieldRevision,
group_ctx: &Self::Context, group_ctx: &Self::Context,
type_option: &Option<Self::TypeOptionType>, type_option: &Option<Self::TypeOptionType>,
) -> Vec<GeneratedGroupConfig> { ) -> GeneratedGroupContext {
match type_option { let group_configs = match type_option {
None => vec![], None => vec![],
Some(type_option) => generate_select_option_groups(field_id, group_ctx, &type_option.options), Some(type_option) => generate_select_option_groups(&field_rev.id, group_ctx, &type_option.options),
};
GeneratedGroupContext {
no_status_group: Some(make_no_status_group(field_rev)),
group_configs,
} }
} }
} }

View File

@ -1,7 +1,7 @@
use crate::entities::{GroupChangesetPB, RowPB}; use crate::entities::{GroupChangesetPB, RowPB};
use crate::services::cell::insert_select_option_cell; use crate::services::cell::insert_select_option_cell;
use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB}; use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB};
use crate::services::group::action::GroupAction; use crate::services::group::action::GroupControllerCustomActions;
use crate::services::group::controller::{ use crate::services::group::controller::{
GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
@ -9,7 +9,7 @@ use crate::services::group::controller::{
use crate::services::group::controller_impls::select_option_controller::util::*; use crate::services::group::controller_impls::select_option_controller::util::*;
use crate::services::group::entities::Group; use crate::services::group::entities::Group;
use crate::services::group::GeneratedGroupConfig; use crate::services::group::{make_no_status_group, GeneratedGroupContext};
use flowy_grid_data_model::revision::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision}; use flowy_grid_data_model::revision::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision};
// SingleSelect // SingleSelect
@ -20,25 +20,29 @@ pub type SingleSelectGroupController = GenericGroupController<
SelectOptionCellDataParser, SelectOptionCellDataParser,
>; >;
impl GroupAction for SingleSelectGroupController { impl GroupControllerCustomActions for SingleSelectGroupController {
type CellDataType = SelectOptionCellDataPB; type CellDataType = SelectOptionCellDataPB;
fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool { fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool {
cell_data.select_options.iter().any(|option| option.id == content) cell_data.select_options.iter().any(|option| option.id == content)
} }
fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> { fn add_or_remove_row_in_groups_if_match(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
) -> Vec<GroupChangesetPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.group_ctx.iter_mut_all_groups(|group| { self.group_ctx.iter_mut_status_groups(|group| {
if let Some(changeset) = add_select_option_row(group, cell_data, row_rev) { if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) {
changesets.push(changeset); changesets.push(changeset);
} }
}); });
changesets changesets
} }
fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> { fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.group_ctx.iter_mut_all_groups(|group| { self.group_ctx.iter_mut_status_groups(|group| {
if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) { if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) {
changesets.push(changeset); changesets.push(changeset);
} }
@ -48,7 +52,7 @@ impl GroupAction for SingleSelectGroupController {
fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> { fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
let mut group_changeset = vec![]; let mut group_changeset = vec![];
self.group_ctx.iter_mut_all_groups(|group| { self.group_ctx.iter_mut_groups(|group| {
if let Some(changeset) = move_group_row(group, &mut context) { if let Some(changeset) = move_group_row(group, &mut context) {
group_changeset.push(changeset); group_changeset.push(changeset);
} }
@ -80,13 +84,18 @@ impl GroupGenerator for SingleSelectGroupGenerator {
type Context = SelectOptionGroupContext; type Context = SelectOptionGroupContext;
type TypeOptionType = SingleSelectTypeOptionPB; type TypeOptionType = SingleSelectTypeOptionPB;
fn generate_groups( fn generate_groups(
field_id: &str, field_rev: &FieldRevision,
group_ctx: &Self::Context, group_ctx: &Self::Context,
type_option: &Option<Self::TypeOptionType>, type_option: &Option<Self::TypeOptionType>,
) -> Vec<GeneratedGroupConfig> { ) -> GeneratedGroupContext {
match type_option { let group_configs = match type_option {
None => vec![], None => vec![],
Some(type_option) => generate_select_option_groups(field_id, group_ctx, &type_option.options), Some(type_option) => generate_select_option_groups(&field_rev.id, group_ctx, &type_option.options),
};
GeneratedGroupContext {
no_status_group: Some(make_no_status_group(field_rev)),
group_configs,
} }
} }
} }

View File

@ -10,7 +10,7 @@ use flowy_grid_data_model::revision::{
pub type SelectOptionGroupContext = GroupContext<SelectOptionGroupConfigurationRevision>; pub type SelectOptionGroupContext = GroupContext<SelectOptionGroupConfigurationRevision>;
pub fn add_select_option_row( pub fn add_or_remove_select_option_row(
group: &mut Group, group: &mut Group,
cell_data: &SelectOptionCellDataPB, cell_data: &SelectOptionCellDataPB,
row_rev: &RowRevision, row_rev: &RowRevision,

View File

@ -97,7 +97,7 @@ pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurat
let field_id = field_rev.id.clone(); let field_id = field_rev.id.clone();
let field_type_rev = field_rev.ty; let field_type_rev = field_rev.ty;
let field_type: FieldType = field_rev.ty.into(); let field_type: FieldType = field_rev.ty.into();
let mut group_configuration_rev = match field_type { match field_type {
FieldType::RichText => { FieldType::RichText => {
GroupConfigurationRevision::new(field_id, field_type_rev, TextGroupConfigurationRevision::default()) GroupConfigurationRevision::new(field_id, field_type_rev, TextGroupConfigurationRevision::default())
.unwrap() .unwrap()
@ -130,11 +130,7 @@ pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurat
FieldType::URL => { FieldType::URL => {
GroupConfigurationRevision::new(field_id, field_type_rev, UrlGroupConfigurationRevision::default()).unwrap() GroupConfigurationRevision::new(field_id, field_type_rev, UrlGroupConfigurationRevision::default()).unwrap()
} }
}; }
// Append the no `status` group
group_configuration_rev.groups.push(make_no_status_group(field_rev));
group_configuration_rev
} }
pub fn make_no_status_group(field_rev: &FieldRevision) -> GroupRevision { pub fn make_no_status_group(field_rev: &FieldRevision) -> GroupRevision {

View File

@ -42,7 +42,7 @@ impl TextEditorCloudService for BlockHttpCloudService {
pub async fn create_document_request(token: &str, params: CreateTextBlockParams, url: &str) -> Result<(), FlowyError> { pub async fn create_document_request(token: &str, params: CreateTextBlockParams, url: &str) -> Result<(), FlowyError> {
let _ = request_builder() let _ = request_builder()
.post(&url.to_owned()) .post(url)
.header(HEADER_TOKEN, token) .header(HEADER_TOKEN, token)
.protobuf(params)? .protobuf(params)?
.send() .send()
@ -56,7 +56,7 @@ pub async fn read_document_request(
url: &str, url: &str,
) -> Result<Option<DocumentPB>, FlowyError> { ) -> Result<Option<DocumentPB>, FlowyError> {
let doc = request_builder() let doc = request_builder()
.get(&url.to_owned()) .get(url)
.header(HEADER_TOKEN, token) .header(HEADER_TOKEN, token)
.protobuf(params)? .protobuf(params)?
.option_response() .option_response()
@ -67,7 +67,7 @@ pub async fn read_document_request(
pub async fn reset_doc_request(token: &str, params: ResetTextBlockParams, url: &str) -> Result<(), FlowyError> { pub async fn reset_doc_request(token: &str, params: ResetTextBlockParams, url: &str) -> Result<(), FlowyError> {
let _ = request_builder() let _ = request_builder()
.patch(&url.to_owned()) .patch(url)
.header(HEADER_TOKEN, token) .header(HEADER_TOKEN, token)
.protobuf(params)? .protobuf(params)?
.send() .send()

View File

@ -66,35 +66,23 @@ impl UserCloudService for UserHttpCloudService {
} }
pub async fn user_sign_up_request(params: SignUpParams, url: &str) -> Result<SignUpResponse, ServerError> { pub async fn user_sign_up_request(params: SignUpParams, url: &str) -> Result<SignUpResponse, ServerError> {
let response = request_builder() let response = request_builder().post(url).protobuf(params)?.response().await?;
.post(&url.to_owned())
.protobuf(params)?
.response()
.await?;
Ok(response) Ok(response)
} }
pub async fn user_sign_in_request(params: SignInParams, url: &str) -> Result<SignInResponse, ServerError> { pub async fn user_sign_in_request(params: SignInParams, url: &str) -> Result<SignInResponse, ServerError> {
let response = request_builder() let response = request_builder().post(url).protobuf(params)?.response().await?;
.post(&url.to_owned())
.protobuf(params)?
.response()
.await?;
Ok(response) Ok(response)
} }
pub async fn user_sign_out_request(token: &str, url: &str) -> Result<(), ServerError> { pub async fn user_sign_out_request(token: &str, url: &str) -> Result<(), ServerError> {
let _ = request_builder() let _ = request_builder().delete(url).header(HEADER_TOKEN, token).send().await?;
.delete(&url.to_owned())
.header(HEADER_TOKEN, token)
.send()
.await?;
Ok(()) Ok(())
} }
pub async fn get_user_profile_request(token: &str, url: &str) -> Result<UserProfilePB, ServerError> { pub async fn get_user_profile_request(token: &str, url: &str) -> Result<UserProfilePB, ServerError> {
let user_profile = request_builder() let user_profile = request_builder()
.get(&url.to_owned()) .get(url)
.header(HEADER_TOKEN, token) .header(HEADER_TOKEN, token)
.response() .response()
.await?; .await?;
@ -107,7 +95,7 @@ pub async fn update_user_profile_request(
url: &str, url: &str,
) -> Result<(), ServerError> { ) -> Result<(), ServerError> {
let _ = request_builder() let _ = request_builder()
.patch(&url.to_owned()) .patch(url)
.header(HEADER_TOKEN, token) .header(HEADER_TOKEN, token)
.protobuf(params)? .protobuf(params)?
.send() .send()

View File

@ -164,7 +164,7 @@ impl RevisionManager {
} }
pub async fn next_sync_revision(&self) -> FlowyResult<Option<Revision>> { pub async fn next_sync_revision(&self) -> FlowyResult<Option<Revision>> {
Ok(self.rev_persistence.next_sync_revision().await?) self.rev_persistence.next_sync_revision().await
} }
pub async fn get_revision(&self, rev_id: i64) -> Option<Revision> { pub async fn get_revision(&self, rev_id: i64) -> Option<Revision> {

View File

@ -130,7 +130,6 @@ impl RevisionPersistence {
#[tracing::instrument(level = "trace", skip(self, revisions), err)] #[tracing::instrument(level = "trace", skip(self, revisions), err)]
pub(crate) async fn reset(&self, revisions: Vec<Revision>) -> FlowyResult<()> { pub(crate) async fn reset(&self, revisions: Vec<Revision>) -> FlowyResult<()> {
let records = revisions let records = revisions
.to_vec()
.into_iter() .into_iter()
.map(|revision| RevisionRecord { .map(|revision| RevisionRecord {
revision, revision,

View File

@ -114,11 +114,10 @@ fn format_event_message<S: Subscriber + for<'a> tracing_subscriber::registry::Lo
let mut message = event_visitor let mut message = event_visitor
.values() .values()
.get("message") .get("message")
.map(|v| match v { .and_then(|v| match v {
Value::String(s) => Some(s.as_str()), Value::String(s) => Some(s.as_str()),
_ => None, _ => None,
}) })
.flatten()
.unwrap_or_else(|| event.metadata().target()) .unwrap_or_else(|| event.metadata().target())
.to_owned(); .to_owned();

View File

@ -73,4 +73,8 @@ impl CellRevision {
pub fn new(data: String) -> Self { pub fn new(data: String) -> Self {
Self { data } Self { data }
} }
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
} }