mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: checklist sort (#4659)
* refactor: use BoxAny for dynamically-typed cell changesets * fix: rust-lib tests and clippy * feat: enable sorting by checklist type option * test: checklist sort rust-lib tests * chore: update related tests * fix: clippy --------- Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com>
This commit is contained in:
@ -566,7 +566,11 @@ pub(crate) async fn update_checklist_cell_handler(
|
||||
let params: ChecklistCellDataChangesetParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||
let changeset = ChecklistCellChangeset {
|
||||
insert_options: params.insert_options,
|
||||
insert_options: params
|
||||
.insert_options
|
||||
.into_iter()
|
||||
.map(|name| (name, false))
|
||||
.collect(),
|
||||
selected_option_ids: params.selected_option_ids,
|
||||
delete_option_ids: params.delete_option_ids,
|
||||
update_options: params.update_options,
|
||||
|
@ -232,7 +232,7 @@ pub fn insert_select_option_cell(option_ids: Vec<String>, field: &Field) -> Cell
|
||||
apply_cell_changeset(BoxAny::new(changeset), None, field, None).unwrap()
|
||||
}
|
||||
|
||||
pub fn insert_checklist_cell(insert_options: Vec<String>, field: &Field) -> Cell {
|
||||
pub fn insert_checklist_cell(insert_options: Vec<(String, bool)>, field: &Field) -> Cell {
|
||||
let changeset = ChecklistCellChangeset {
|
||||
insert_options,
|
||||
..Default::default()
|
||||
@ -391,14 +391,13 @@ impl<'a> CellBuilder<'a> {
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn insert_checklist_cell(&mut self, field_id: &str, option_names: Vec<String>) {
|
||||
pub fn insert_checklist_cell(&mut self, field_id: &str, options: Vec<(String, bool)>) {
|
||||
match self.field_maps.get(&field_id.to_owned()) {
|
||||
None => tracing::warn!("Can't find the field with id: {}", field_id),
|
||||
Some(field) => {
|
||||
self.cells.insert(
|
||||
field_id.to_owned(),
|
||||
insert_checklist_cell(option_names, field),
|
||||
);
|
||||
self
|
||||
.cells
|
||||
.insert(field_id.to_owned(), insert_checklist_cell(options, field));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ use crate::entities::{ChecklistCellDataPB, ChecklistFilterPB, FieldType, SelectO
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
|
||||
use crate::services::field::checklist_type_option::{ChecklistCellChangeset, ChecklistCellData};
|
||||
use crate::services::field::{
|
||||
SelectOption, TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
TypeOptionCellDataSerde, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
|
||||
SelectOption, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
|
||||
TypeOptionCellDataFilter, TypeOptionCellDataSerde, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
|
||||
};
|
||||
use crate::services::sort::SortCondition;
|
||||
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
|
||||
@ -101,8 +101,11 @@ fn update_cell_data_with_changeset(
|
||||
changeset
|
||||
.insert_options
|
||||
.into_iter()
|
||||
.for_each(|option_name| {
|
||||
.for_each(|(option_name, is_selected)| {
|
||||
let option = SelectOption::new(&option_name);
|
||||
if is_selected {
|
||||
cell_data.selected_option_ids.push(option.id.clone())
|
||||
}
|
||||
cell_data.options.push(option);
|
||||
});
|
||||
|
||||
@ -153,7 +156,7 @@ impl CellDataDecoder for ChecklistTypeOption {
|
||||
|
||||
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
|
||||
cell_data
|
||||
.selected_options()
|
||||
.options
|
||||
.into_iter()
|
||||
.map(|option| option.name)
|
||||
.collect::<Vec<_>>()
|
||||
@ -191,16 +194,19 @@ impl TypeOptionCellDataCompare for ChecklistTypeOption {
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
_sort_condition: SortCondition,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
let left = cell_data.percentage_complete();
|
||||
let right = other_cell_data.percentage_complete();
|
||||
if left > right {
|
||||
Ordering::Greater
|
||||
} else if left < right {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Equal
|
||||
match (cell_data.is_cell_empty(), other_cell_data.is_cell_empty()) {
|
||||
(true, true) => Ordering::Equal,
|
||||
(true, false) => Ordering::Greater,
|
||||
(false, true) => Ordering::Less,
|
||||
(false, false) => {
|
||||
let left = cell_data.percentage_complete();
|
||||
let right = other_cell_data.percentage_complete();
|
||||
// safe to unwrap because the two floats won't be NaN
|
||||
let order = left.partial_cmp(&right).unwrap();
|
||||
sort_condition.evaluate_order(order)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ impl ToString for ChecklistCellData {
|
||||
|
||||
impl TypeOptionCellData for ChecklistCellData {
|
||||
fn is_cell_empty(&self) -> bool {
|
||||
self.selected_option_ids.is_empty()
|
||||
self.options.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,15 +43,20 @@ impl ChecklistCellData {
|
||||
((selected_options as f64) / (total_options as f64) * 100.0).round() / 100.0
|
||||
}
|
||||
|
||||
pub fn from_options(options: Vec<String>) -> Self {
|
||||
let options = options
|
||||
pub fn from_options(options: Vec<(String, bool)>) -> Self {
|
||||
let (options, selected_ids): (Vec<_>, Vec<_>) = options
|
||||
.into_iter()
|
||||
.map(|option_name| SelectOption::new(&option_name))
|
||||
.collect();
|
||||
.map(|(name, is_selected)| {
|
||||
let option = SelectOption::new(&name);
|
||||
let selected_id = is_selected.then(|| option.id.clone());
|
||||
(option, selected_id)
|
||||
})
|
||||
.unzip();
|
||||
let selected_option_ids = selected_ids.into_iter().flatten().collect();
|
||||
|
||||
Self {
|
||||
options,
|
||||
..Default::default()
|
||||
selected_option_ids,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,7 +82,7 @@ impl From<ChecklistCellData> for Cell {
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ChecklistCellChangeset {
|
||||
/// List of option names that will be inserted
|
||||
pub insert_options: Vec<String>,
|
||||
pub insert_options: Vec<(String, bool)>,
|
||||
pub selected_option_ids: Vec<String>,
|
||||
pub delete_option_ids: Vec<String>,
|
||||
pub update_options: Vec<SelectOption>,
|
||||
|
@ -27,9 +27,10 @@ pub trait TypeOption {
|
||||
/// `FromCellString` and `Default` trait. If the cell string can not be decoded into the specified
|
||||
/// cell data type then the default value will be returned.
|
||||
/// For example:
|
||||
/// FieldType::Checkbox => CheckboxCellData
|
||||
/// FieldType::Date => DateCellData
|
||||
/// FieldType::URL => URLCellData
|
||||
///
|
||||
/// - FieldType::Checkbox => CheckboxCellData
|
||||
/// - FieldType::Date => DateCellData
|
||||
/// - FieldType::URL => URLCellData
|
||||
///
|
||||
/// Uses `StrCellData` for any `TypeOption` if their cell data is pure `String`.
|
||||
///
|
||||
@ -57,7 +58,7 @@ pub trait TypeOption {
|
||||
///
|
||||
type CellProtobufType: TryInto<Bytes, Error = ProtobufError> + Debug;
|
||||
|
||||
/// Represents as the filter configuration for this type option.
|
||||
/// Represents the filter configuration for this type option.
|
||||
type CellFilter: FromFilterString + Send + Sync + 'static;
|
||||
}
|
||||
/// This trait providing serialization and deserialization methods for cell data.
|
||||
@ -81,12 +82,9 @@ pub trait TypeOptionCellDataSerde: TypeOption {
|
||||
}
|
||||
|
||||
/// This trait that provides methods to extend the [TypeOption::CellData] functionalities.
|
||||
///
|
||||
pub trait TypeOptionCellData {
|
||||
/// Checks if the cell content is considered empty.
|
||||
///
|
||||
/// Even if a cell is initialized, its content might still be considered empty
|
||||
/// based on certain criteria. e.g. empty text, date, select option, etc.
|
||||
/// Checks if the cell content is considered empty based on certain criteria. e.g. empty text,
|
||||
/// no date selected, no selected options
|
||||
fn is_cell_empty(&self) -> bool {
|
||||
false
|
||||
}
|
||||
@ -99,8 +97,8 @@ pub trait TypeOptionTransform: TypeOption {
|
||||
}
|
||||
|
||||
/// Transform the TypeOption from one field type to another
|
||||
/// For example, when switching from `checkbox` type-option to `single-select`
|
||||
/// type-option, adding the `Yes` option if the `single-select` type-option doesn't contain it.
|
||||
/// For example, when switching from `Checkbox` type option to `Single-Select`
|
||||
/// type option, adding the `Yes` option if the `Single-select` type-option doesn't contain it.
|
||||
/// But the cell content is a string, `Yes`, it's need to do the cell content transform.
|
||||
/// The `Yes` string will be transformed to the `Yes` option id.
|
||||
///
|
||||
@ -109,7 +107,6 @@ pub trait TypeOptionTransform: TypeOption {
|
||||
/// * `old_type_option_field_type`: the FieldType of the passed-in TypeOption
|
||||
/// * `old_type_option_data`: the data that can be parsed into corresponding `TypeOption`.
|
||||
///
|
||||
///
|
||||
fn transform_type_option(
|
||||
&mut self,
|
||||
_old_type_option_field_type: FieldType,
|
||||
|
Reference in New Issue
Block a user