mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
refactor: cell data transform logic (#5039)
* refactor: cell data transform logic * chore: remove redundant select option event * chore: adapt tests to changes
This commit is contained in:
parent
828f312294
commit
b7b4ea2da1
@ -164,6 +164,39 @@ class SelectOptionCellEditorBloc
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.addListener(
|
||||
onCellChanged: (_) {
|
||||
_loadOptions();
|
||||
},
|
||||
onCellFieldChanged: (field) {
|
||||
_loadOptions();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _loadOptions() {
|
||||
if (isClosed) {
|
||||
Log.warn("Unexpecteded closing the bloc");
|
||||
return;
|
||||
}
|
||||
|
||||
final cellData = cellController.getCellData();
|
||||
|
||||
if (cellData != null) {
|
||||
add(
|
||||
SelectOptionCellEditorEvent.didReceiveOptions(
|
||||
cellData.options,
|
||||
cellData.selectOptions,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
add(
|
||||
const SelectOptionCellEditorEvent.didReceiveOptions([], []),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _createOption({
|
||||
required String name,
|
||||
required SelectOptionColorPB color,
|
||||
@ -251,27 +284,6 @@ class SelectOptionCellEditorBloc
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadOptions() async {
|
||||
final result = await _selectOptionService.getCellData();
|
||||
if (isClosed) {
|
||||
Log.warn("Unexpecteded closing the bloc");
|
||||
return;
|
||||
}
|
||||
|
||||
return result.fold(
|
||||
(data) => add(
|
||||
SelectOptionCellEditorEvent.didReceiveOptions(
|
||||
data.options,
|
||||
data.selectOptions,
|
||||
),
|
||||
),
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return null;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_MakeOptionResult _getVisibleOptions(
|
||||
List<SelectOptionPB> allOptions,
|
||||
) {
|
||||
@ -331,17 +343,6 @@ class SelectOptionCellEditorBloc
|
||||
|
||||
emit(state.copyWith(focusedOptionId: optionIds[newIndex]));
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.addListener(
|
||||
onCellChanged: (selectOptionContext) {
|
||||
_loadOptions();
|
||||
},
|
||||
onCellFieldChanged: (field) {
|
||||
_loadOptions();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -60,15 +60,6 @@ class SelectOptionCellBackendService {
|
||||
return DatabaseEventDeleteSelectOption(payload).send();
|
||||
}
|
||||
|
||||
Future<FlowyResult<SelectOptionCellDataPB, FlowyError>> getCellData() {
|
||||
final payload = CellIdPB()
|
||||
..viewId = viewId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId;
|
||||
|
||||
return DatabaseEventGetSelectOptionCellData(payload).send();
|
||||
}
|
||||
|
||||
Future<FlowyResult<void, FlowyError>> select({
|
||||
required Iterable<String> optionIds,
|
||||
}) {
|
||||
|
@ -91,12 +91,19 @@ void main() {
|
||||
"Expect 3 but receive ${bloc.state.options.length}. Options: ${bloc.state.options}",
|
||||
);
|
||||
|
||||
bloc.add(SelectOptionCellEditorEvent.deleteOption(bloc.state.options[0]));
|
||||
await gridResponseFuture();
|
||||
assert(
|
||||
bloc.state.options.length == 2,
|
||||
"Expect 2 but receive ${bloc.state.options.length}. Options: ${bloc.state.options}",
|
||||
);
|
||||
|
||||
bloc.add(const SelectOptionCellEditorEvent.deleteAllOptions());
|
||||
await gridResponseFuture();
|
||||
|
||||
assert(
|
||||
bloc.state.options.isEmpty,
|
||||
"Expect empty but receive ${bloc.state.options.length}",
|
||||
"Expect empty but receive ${bloc.state.options.length}. Options: ${bloc.state.options}",
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -532,20 +532,6 @@ pub(crate) async fn delete_select_option_handler(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn get_select_option_handler(
|
||||
data: AFPluginData<CellIdPB>,
|
||||
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||
) -> DataResult<SelectOptionCellDataPB, FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let params: CellIdParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||
let options = database_editor
|
||||
.get_select_options(params.row_id, ¶ms.field_id)
|
||||
.await;
|
||||
data_result_ok(options)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub(crate) async fn update_select_option_cell_handler(
|
||||
data: AFPluginData<SelectOptionCellChangesetPB>,
|
||||
|
@ -47,7 +47,6 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
|
||||
.event(DatabaseEvent::CreateSelectOption, new_select_option_handler)
|
||||
.event(DatabaseEvent::InsertOrUpdateSelectOption, insert_or_update_select_option_handler)
|
||||
.event(DatabaseEvent::DeleteSelectOption, delete_select_option_handler)
|
||||
.event(DatabaseEvent::GetSelectOptionCellData, get_select_option_handler)
|
||||
.event(DatabaseEvent::UpdateSelectOptionCell, update_select_option_cell_handler)
|
||||
// Checklist
|
||||
.event(DatabaseEvent::UpdateChecklistCell, update_checklist_cell_handler)
|
||||
@ -201,12 +200,6 @@ pub enum DatabaseEvent {
|
||||
#[event(input = "CreateSelectOptionPayloadPB", output = "SelectOptionPB")]
|
||||
CreateSelectOption = 30,
|
||||
|
||||
/// [GetSelectOptionCellData] event is used to get the select option data for cell editing.
|
||||
/// [CellIdPB] locate which cell data that will be read from. The return value, [SelectOptionCellDataPB]
|
||||
/// contains the available options and the currently selected options.
|
||||
#[event(input = "CellIdPB", output = "SelectOptionCellDataPB")]
|
||||
GetSelectOptionCellData = 31,
|
||||
|
||||
/// [InsertOrUpdateSelectOption] event is used to update a FieldTypeOptionData whose field_type is
|
||||
/// FieldType::SingleSelect or FieldType::MultiSelect.
|
||||
///
|
||||
@ -214,10 +207,10 @@ pub enum DatabaseEvent {
|
||||
/// For example, DatabaseNotification::DidUpdateCell will be triggered if the [SelectOptionChangesetPB]
|
||||
/// carries a change that updates the name of the option.
|
||||
#[event(input = "RepeatedSelectOptionPayload")]
|
||||
InsertOrUpdateSelectOption = 32,
|
||||
InsertOrUpdateSelectOption = 31,
|
||||
|
||||
#[event(input = "RepeatedSelectOptionPayload")]
|
||||
DeleteSelectOption = 33,
|
||||
DeleteSelectOption = 32,
|
||||
|
||||
#[event(input = "CreateRowPayloadPB", output = "RowMetaPB")]
|
||||
CreateRow = 50,
|
||||
|
@ -1,9 +1,10 @@
|
||||
use crate::entities::{CalculationType, FieldType};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::services::field::TypeOptionCellExt;
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::rows::RowCell;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::entities::CalculationType;
|
||||
use crate::services::field::TypeOptionCellExt;
|
||||
|
||||
pub struct CalculationsService {}
|
||||
|
||||
@ -35,10 +36,7 @@ impl CalculationsService {
|
||||
fn calculate_average(&self, field: &Field, row_cells: Vec<Arc<RowCell>>) -> String {
|
||||
let mut sum = 0.0;
|
||||
let mut len = 0.0;
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
if let Some(handler) =
|
||||
TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
if let Some(handler) = TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler() {
|
||||
for row_cell in row_cells {
|
||||
if let Some(cell) = &row_cell.cell {
|
||||
if let Some(value) = handler.handle_numeric_cell(cell) {
|
||||
@ -130,50 +128,39 @@ impl CalculationsService {
|
||||
}
|
||||
|
||||
fn calculate_count_empty(&self, field: &Field, row_cells: Vec<Arc<RowCell>>) -> String {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
if let Some(handler) =
|
||||
TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
if !row_cells.is_empty() {
|
||||
return format!(
|
||||
"{}",
|
||||
row_cells
|
||||
.iter()
|
||||
.filter(|c| c.is_none()
|
||||
|| handler
|
||||
.handle_stringify_cell(&c.cell.clone().unwrap_or_default(), &field_type, field)
|
||||
.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.len()
|
||||
);
|
||||
}
|
||||
match TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler() {
|
||||
Some(handler) if !row_cells.is_empty() => row_cells
|
||||
.iter()
|
||||
.filter(|row_cell| {
|
||||
if let Some(cell) = &row_cell.cell {
|
||||
handler.handle_is_cell_empty(cell, field)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.len()
|
||||
.to_string(),
|
||||
_ => "".to_string(),
|
||||
}
|
||||
|
||||
String::new()
|
||||
}
|
||||
|
||||
fn calculate_count_non_empty(&self, field: &Field, row_cells: Vec<Arc<RowCell>>) -> String {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
if let Some(handler) =
|
||||
TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
if !row_cells.is_empty() {
|
||||
return format!(
|
||||
"{}",
|
||||
row_cells
|
||||
.iter()
|
||||
// Check the Cell has data and that the stringified version is not empty
|
||||
.filter(|c| c.is_some()
|
||||
&& !handler
|
||||
.handle_stringify_cell(&c.cell.clone().unwrap_or_default(), &field_type, field)
|
||||
.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.len()
|
||||
);
|
||||
}
|
||||
match TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler() {
|
||||
Some(handler) if !row_cells.is_empty() => row_cells
|
||||
.iter()
|
||||
.filter(|row_cell| {
|
||||
if let Some(cell) = &row_cell.cell {
|
||||
!handler.handle_is_cell_empty(cell, field)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.len()
|
||||
.to_string(),
|
||||
_ => "".to_string(),
|
||||
}
|
||||
|
||||
String::new()
|
||||
}
|
||||
|
||||
fn reduce_values_f64<F, T>(&self, field: &Field, row_cells: Vec<Arc<RowCell>>, f: F) -> T
|
||||
@ -182,10 +169,7 @@ impl CalculationsService {
|
||||
{
|
||||
let mut values = vec![];
|
||||
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
if let Some(handler) =
|
||||
TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
if let Some(handler) = TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler() {
|
||||
for row_cell in row_cells {
|
||||
if let Some(cell) = &row_cell.cell {
|
||||
if let Some(value) = handler.handle_numeric_cell(cell) {
|
||||
|
@ -14,7 +14,6 @@ use crate::services::group::make_no_status_group;
|
||||
|
||||
/// Decode the opaque cell data into readable format content
|
||||
pub trait CellDataDecoder: TypeOption {
|
||||
///
|
||||
/// Tries to decode the [Cell] to `decoded_field_type`'s cell data. Sometimes, the `field_type`
|
||||
/// of the `Field` is not equal to the `decoded_field_type`(This happened When switching
|
||||
/// the field type of the `Field` to another field type). So the cell data is need to do
|
||||
@ -25,21 +24,30 @@ pub trait CellDataDecoder: TypeOption {
|
||||
/// But the data of the cell doesn't change. We can't iterate all the rows to transform the cell
|
||||
/// data that can be parsed by the current field type. One approach is to transform the cell data
|
||||
/// when reading.
|
||||
fn decode_cell(
|
||||
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData>;
|
||||
|
||||
/// Transform the cell data from one field type to another
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cell`: the cell in the current field type
|
||||
/// * `transformed_field_type`: the cell will be transformed to the is field type's cell data.
|
||||
/// current `TypeOption` field type.
|
||||
///
|
||||
fn decode_cell_with_transform(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
decoded_field_type: &FieldType,
|
||||
field: &Field,
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData>;
|
||||
_cell: &Cell,
|
||||
_from_field_type: FieldType,
|
||||
_field: &Field,
|
||||
) -> Option<<Self as TypeOption>::CellData> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Decode the cell data to readable `String`
|
||||
/// For example, The string of the Multi-Select cell will be a list of the option's name
|
||||
/// separated by a comma.
|
||||
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String;
|
||||
|
||||
/// Same as [CellDataDecoder::stringify_cell_data] but the input parameter is the [Cell]
|
||||
fn stringify_cell(&self, cell: &Cell) -> String;
|
||||
|
||||
/// Decode the cell into f64
|
||||
/// Different field type has different way to decode the cell data into f64
|
||||
/// If the field type doesn't support to decode the cell data into f64, it will return None
|
||||
@ -74,10 +82,7 @@ pub fn apply_cell_changeset(
|
||||
field: &Field,
|
||||
cell_data_cache: Option<CellCache>,
|
||||
) -> Result<Cell, FlowyError> {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
match TypeOptionCellExt::new(field, cell_data_cache)
|
||||
.get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
match TypeOptionCellExt::new(field, cell_data_cache).get_type_option_cell_data_handler() {
|
||||
None => Ok(Cell::default()),
|
||||
Some(handler) => Ok(handler.handle_cell_changeset(changeset, cell, field)?),
|
||||
}
|
||||
@ -88,15 +93,7 @@ pub fn get_cell_protobuf(
|
||||
field: &Field,
|
||||
cell_cache: Option<CellCache>,
|
||||
) -> CellProtobufBlob {
|
||||
let from_field_type = get_field_type_from_cell(cell);
|
||||
if from_field_type.is_none() {
|
||||
return CellProtobufBlob::default();
|
||||
}
|
||||
|
||||
let from_field_type = from_field_type.unwrap();
|
||||
let to_field_type = FieldType::from(field.field_type);
|
||||
match try_decode_cell_to_cell_protobuf(cell, &from_field_type, &to_field_type, field, cell_cache)
|
||||
{
|
||||
match try_decode_cell_to_cell_protobuf(cell, field, cell_cache) {
|
||||
Ok(cell_bytes) => cell_bytes,
|
||||
Err(e) => {
|
||||
tracing::error!("Decode cell data failed, {:?}", e);
|
||||
@ -123,34 +120,15 @@ pub fn get_cell_protobuf(
|
||||
///
|
||||
pub fn try_decode_cell_to_cell_protobuf(
|
||||
cell: &Cell,
|
||||
from_field_type: &FieldType,
|
||||
to_field_type: &FieldType,
|
||||
field: &Field,
|
||||
cell_data_cache: Option<CellCache>,
|
||||
) -> FlowyResult<CellProtobufBlob> {
|
||||
match TypeOptionCellExt::new(field, cell_data_cache)
|
||||
.get_type_option_cell_data_handler(to_field_type)
|
||||
{
|
||||
match TypeOptionCellExt::new(field, cell_data_cache).get_type_option_cell_data_handler() {
|
||||
None => Ok(CellProtobufBlob::default()),
|
||||
Some(handler) => handler.handle_cell_protobuf(cell, from_field_type, field),
|
||||
Some(handler) => handler.handle_get_protobuf_cell_data(cell, field),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_decode_cell_to_cell_data<T: Default + 'static>(
|
||||
cell: &Cell,
|
||||
from_field_type: &FieldType,
|
||||
to_field_type: &FieldType,
|
||||
field: &Field,
|
||||
cell_data_cache: Option<CellCache>,
|
||||
) -> Option<T> {
|
||||
let handler = TypeOptionCellExt::new(field, cell_data_cache)
|
||||
.get_type_option_cell_data_handler(to_field_type)?;
|
||||
handler
|
||||
.get_cell_data(cell, from_field_type, field)
|
||||
.ok()?
|
||||
.unbox_or_none::<T>()
|
||||
}
|
||||
|
||||
/// Returns a string that represents the current field_type's cell data.
|
||||
/// For example, a Multi-Select cell will be represented by a list of the options' names
|
||||
/// separated by commas.
|
||||
@ -162,15 +140,14 @@ pub fn try_decode_cell_to_cell_data<T: Default + 'static>(
|
||||
/// * `from_field_type`: the original field type of the passed-in cell data.
|
||||
/// * `field`: used to get the corresponding TypeOption for the specified field type.
|
||||
///
|
||||
pub fn stringify_cell_data(
|
||||
cell: &Cell,
|
||||
to_field_type: &FieldType,
|
||||
from_field_type: &FieldType,
|
||||
field: &Field,
|
||||
) -> String {
|
||||
match TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(from_field_type) {
|
||||
None => "".to_string(),
|
||||
Some(handler) => handler.handle_stringify_cell(cell, to_field_type, field),
|
||||
pub fn stringify_cell(cell: &Cell, field: &Field) -> String {
|
||||
if let Some(field_type_of_cell) = get_field_type_from_cell::<FieldType>(cell) {
|
||||
TypeOptionCellExt::new(field, None)
|
||||
.get_type_option_cell_data_handler_with_field_type(field_type_of_cell)
|
||||
.map(|handler| handler.handle_stringify_cell(cell, field))
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ use crate::services::database_view::{
|
||||
use crate::services::field::{
|
||||
default_type_option_data_from_type, select_type_option_from_field, transform_type_option,
|
||||
type_option_data_from_pb, ChecklistCellChangeset, RelationTypeOption, SelectOptionCellChangeset,
|
||||
SelectOptionIds, StrCellData, TimestampCellData, TypeOptionCellDataHandler, TypeOptionCellExt,
|
||||
StrCellData, TimestampCellData, TypeOptionCellDataHandler, TypeOptionCellExt,
|
||||
};
|
||||
use crate::services::field_settings::{
|
||||
default_field_settings_by_layout_map, FieldSettings, FieldSettingsChangesetParams,
|
||||
@ -406,16 +406,16 @@ impl DatabaseEditor {
|
||||
}
|
||||
|
||||
let old_field_type = FieldType::from(field.field_type);
|
||||
let old_type_option = field.get_any_type_option(old_field_type);
|
||||
let new_type_option = field
|
||||
let old_type_option_data = field.get_any_type_option(old_field_type);
|
||||
let new_type_option_data = field
|
||||
.get_any_type_option(new_field_type)
|
||||
.unwrap_or_else(|| default_type_option_data_from_type(new_field_type));
|
||||
|
||||
let transformed_type_option = transform_type_option(
|
||||
&new_type_option,
|
||||
new_field_type,
|
||||
old_type_option,
|
||||
old_field_type,
|
||||
new_field_type,
|
||||
old_type_option_data,
|
||||
new_type_option_data,
|
||||
);
|
||||
self
|
||||
.database
|
||||
@ -1015,24 +1015,6 @@ impl DatabaseEditor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_select_options(&self, row_id: RowId, field_id: &str) -> SelectOptionCellDataPB {
|
||||
let field = self.database.lock().fields.get_field(field_id);
|
||||
match field {
|
||||
None => SelectOptionCellDataPB::default(),
|
||||
Some(field) => {
|
||||
let cell = self.database.lock().get_cell(field_id, &row_id).cell;
|
||||
let ids = match cell {
|
||||
None => SelectOptionIds::new(),
|
||||
Some(cell) => SelectOptionIds::from(&cell),
|
||||
};
|
||||
match select_type_option_from_field(&field) {
|
||||
Ok(type_option) => type_option.get_selected_options(ids).into(),
|
||||
Err(_) => SelectOptionCellDataPB::default(),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_checklist_options(
|
||||
&self,
|
||||
view_id: &str,
|
||||
@ -1349,7 +1331,7 @@ impl DatabaseEditor {
|
||||
) -> FlowyResult<Vec<RelatedRowDataPB>> {
|
||||
let primary_field = self.database.lock().fields.get_primary_field().unwrap();
|
||||
let handler = TypeOptionCellExt::new(&primary_field, Some(self.cell_cache.clone()))
|
||||
.get_type_option_cell_data_handler(&FieldType::RichText)
|
||||
.get_type_option_cell_data_handler_with_field_type(FieldType::RichText)
|
||||
.ok_or(FlowyError::internal())?;
|
||||
|
||||
let row_data = {
|
||||
@ -1364,11 +1346,7 @@ impl DatabaseEditor {
|
||||
let title = database
|
||||
.get_cell(&primary_field.id, &row.id)
|
||||
.cell
|
||||
.and_then(|cell| {
|
||||
handler
|
||||
.get_cell_data(&cell, &FieldType::RichText, &primary_field)
|
||||
.ok()
|
||||
})
|
||||
.and_then(|cell| handler.handle_get_boxed_cell_data(&cell, &primary_field))
|
||||
.and_then(|cell_data| cell_data.unbox_or_none())
|
||||
.unwrap_or_else(|| StrCellData("".to_string()));
|
||||
|
||||
@ -1715,10 +1693,8 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl {
|
||||
fn get_type_option_cell_handler(
|
||||
&self,
|
||||
field: &Field,
|
||||
field_type: &FieldType,
|
||||
) -> Option<Box<dyn TypeOptionCellDataHandler>> {
|
||||
TypeOptionCellExt::new(field, Some(self.cell_cache.clone()))
|
||||
.get_type_option_cell_data_handler(field_type)
|
||||
TypeOptionCellExt::new(field, Some(self.cell_cache.clone())).get_type_option_cell_data_handler()
|
||||
}
|
||||
|
||||
fn get_field_settings(
|
||||
|
@ -111,11 +111,11 @@ pub(crate) async fn get_cell_for_row(
|
||||
let field = delegate.get_field(field_id)?;
|
||||
let row_cell = delegate.get_cell_in_row(field_id, row_id).await;
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
let handler = delegate.get_type_option_cell_handler(&field, &field_type)?;
|
||||
let handler = delegate.get_type_option_cell_handler(&field)?;
|
||||
|
||||
let cell_data = match &row_cell.cell {
|
||||
None => None,
|
||||
Some(cell) => handler.get_cell_data(cell, &field_type, &field).ok(),
|
||||
Some(cell) => handler.handle_get_boxed_cell_data(cell, &field),
|
||||
};
|
||||
Some(RowSingleCellData {
|
||||
row_id: row_cell.row_id.clone(),
|
||||
@ -133,14 +133,14 @@ pub(crate) async fn get_cells_for_field(
|
||||
) -> Vec<RowSingleCellData> {
|
||||
if let Some(field) = delegate.get_field(field_id) {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
if let Some(handler) = delegate.get_type_option_cell_handler(&field, &field_type) {
|
||||
if let Some(handler) = delegate.get_type_option_cell_handler(&field) {
|
||||
let cells = delegate.get_cells_for_field(view_id, field_id).await;
|
||||
return cells
|
||||
.iter()
|
||||
.map(|row_cell| {
|
||||
let cell_data = match &row_cell.cell {
|
||||
None => None,
|
||||
Some(cell) => handler.get_cell_data(cell, &field_type, &field).ok(),
|
||||
Some(cell) => handler.handle_get_boxed_cell_data(cell, &field),
|
||||
};
|
||||
RowSingleCellData {
|
||||
row_id: row_cell.row_id.clone(),
|
||||
|
@ -118,7 +118,6 @@ pub trait DatabaseViewOperation: Send + Sync + 'static {
|
||||
fn get_type_option_cell_handler(
|
||||
&self,
|
||||
field: &Field,
|
||||
field_type: &FieldType,
|
||||
) -> Option<Box<dyn TypeOptionCellDataHandler>>;
|
||||
|
||||
fn get_field_settings(
|
||||
|
@ -1,14 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use collab_database::fields::TypeOptionData;
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::database::DatabaseEditor;
|
||||
use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
|
||||
use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption, TypeOption};
|
||||
|
||||
pub async fn edit_field_type_option<T: From<TypeOptionData> + Into<TypeOptionData>>(
|
||||
pub async fn edit_field_type_option<T: TypeOption>(
|
||||
field_id: &str,
|
||||
editor: Arc<DatabaseEditor>,
|
||||
action: impl FnOnce(&mut T),
|
||||
@ -22,7 +20,7 @@ pub async fn edit_field_type_option<T: From<TypeOptionData> + Into<TypeOptionDat
|
||||
if let Some(mut type_option) = get_type_option.await {
|
||||
if let Some(old_field) = editor.get_field(field_id) {
|
||||
action(&mut type_option);
|
||||
let type_option_data: TypeOptionData = type_option.into();
|
||||
let type_option_data = type_option.into();
|
||||
editor
|
||||
.update_field_type_option(field_id, type_option_data, old_field)
|
||||
.await?;
|
||||
|
@ -38,16 +38,12 @@ mod tests {
|
||||
type_option: &CheckboxTypeOption,
|
||||
input_str: &str,
|
||||
expected_str: &str,
|
||||
field_type: &FieldType,
|
||||
field: &Field,
|
||||
_field_type: &FieldType,
|
||||
_field: &Field,
|
||||
) {
|
||||
assert_eq!(
|
||||
type_option
|
||||
.decode_cell(
|
||||
&CheckboxCellDataPB::from_str(input_str).unwrap().into(),
|
||||
field_type,
|
||||
field
|
||||
)
|
||||
.decode_cell(&CheckboxCellDataPB::from_str(input_str).unwrap().into())
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
expected_str.to_owned()
|
||||
|
@ -25,31 +25,7 @@ impl TypeOption for CheckboxTypeOption {
|
||||
type CellFilter = CheckboxFilterPB;
|
||||
}
|
||||
|
||||
impl TypeOptionTransform for CheckboxTypeOption {
|
||||
fn transformable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn transform_type_option(
|
||||
&mut self,
|
||||
_old_type_option_field_type: FieldType,
|
||||
_old_type_option_data: TypeOptionData,
|
||||
) {
|
||||
}
|
||||
|
||||
fn transform_type_option_cell(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
transformed_field_type: &FieldType,
|
||||
_field: &Field,
|
||||
) -> Option<<Self as TypeOption>::CellData> {
|
||||
if transformed_field_type.is_text() {
|
||||
Some(CheckboxCellDataPB::from(cell))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TypeOptionTransform for CheckboxTypeOption {}
|
||||
|
||||
impl From<TypeOptionData> for CheckboxTypeOption {
|
||||
fn from(_data: TypeOptionData) -> Self {
|
||||
@ -77,26 +53,27 @@ impl TypeOptionCellDataSerde for CheckboxTypeOption {
|
||||
}
|
||||
|
||||
impl CellDataDecoder for CheckboxTypeOption {
|
||||
fn decode_cell(
|
||||
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
self.parse_cell(cell)
|
||||
}
|
||||
|
||||
fn decode_cell_with_transform(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
decoded_field_type: &FieldType,
|
||||
from_field_type: FieldType,
|
||||
_field: &Field,
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
if !decoded_field_type.is_checkbox() {
|
||||
return Ok(Default::default());
|
||||
) -> Option<<Self as TypeOption>::CellData> {
|
||||
if from_field_type.is_text() {
|
||||
Some(CheckboxCellDataPB::from(cell))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
self.parse_cell(cell)
|
||||
}
|
||||
|
||||
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
|
||||
cell_data.to_string()
|
||||
}
|
||||
|
||||
fn stringify_cell(&self, cell: &Cell) -> String {
|
||||
Self::CellData::from(cell).to_string()
|
||||
}
|
||||
|
||||
fn numeric_cell(&self, cell: &Cell) -> Option<f64> {
|
||||
let cell_data = self.parse_cell(cell).ok()?;
|
||||
if cell_data.is_checked {
|
||||
|
@ -1,4 +1,10 @@
|
||||
use crate::entities::{ChecklistCellDataPB, ChecklistFilterPB, FieldType, SelectOptionPB};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::rows::Cell;
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
use crate::entities::{ChecklistCellDataPB, ChecklistFilterPB, SelectOptionPB};
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
|
||||
use crate::services::field::checklist_type_option::{ChecklistCellChangeset, ChecklistCellData};
|
||||
use crate::services::field::{
|
||||
@ -6,10 +12,6 @@ use crate::services::field::{
|
||||
TypeOptionCellDataFilter, TypeOptionCellDataSerde, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
|
||||
};
|
||||
use crate::services::sort::SortCondition;
|
||||
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::rows::Cell;
|
||||
use flowy_error::FlowyResult;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ChecklistTypeOption;
|
||||
@ -141,16 +143,7 @@ fn update_cell_data_with_changeset(
|
||||
}
|
||||
|
||||
impl CellDataDecoder for ChecklistTypeOption {
|
||||
fn decode_cell(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
decoded_field_type: &FieldType,
|
||||
_field: &Field,
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
if !decoded_field_type.is_checklist() {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
self.parse_cell(cell)
|
||||
}
|
||||
|
||||
@ -163,11 +156,6 @@ impl CellDataDecoder for ChecklistTypeOption {
|
||||
.join(SELECTION_IDS_SEPARATOR)
|
||||
}
|
||||
|
||||
fn stringify_cell(&self, cell: &Cell) -> String {
|
||||
let cell_data = self.parse_cell(cell).unwrap_or_default();
|
||||
self.stringify_cell_data(cell_data)
|
||||
}
|
||||
|
||||
fn numeric_cell(&self, _cell: &Cell) -> Option<f64> {
|
||||
// return the percentage complete if needed
|
||||
None
|
||||
|
@ -546,10 +546,8 @@ mod tests {
|
||||
assert_eq!(decode_cell_data(&cell, type_option, field), expected_str,);
|
||||
}
|
||||
|
||||
fn decode_cell_data(cell: &Cell, type_option: &DateTypeOption, field: &Field) -> String {
|
||||
let decoded_data = type_option
|
||||
.decode_cell(cell, &FieldType::DateTime, field)
|
||||
.unwrap();
|
||||
fn decode_cell_data(cell: &Cell, type_option: &DateTypeOption, _field: &Field) -> String {
|
||||
let decoded_data = type_option.decode_cell(cell).unwrap();
|
||||
type_option.stringify_cell_data(decoded_data)
|
||||
}
|
||||
|
||||
|
@ -4,13 +4,13 @@ use std::str::FromStr;
|
||||
use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, NaiveTime, Offset, TimeZone};
|
||||
use chrono_tz::Tz;
|
||||
use collab::core::any_map::AnyMapExtension;
|
||||
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::rows::Cell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
|
||||
use crate::entities::{DateCellDataPB, DateFilterPB, FieldType};
|
||||
use crate::entities::{DateCellDataPB, DateFilterPB};
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
|
||||
use crate::services::field::{
|
||||
default_order, DateCellChangeset, DateCellData, DateFormat, TimeFormat, TypeOption,
|
||||
@ -199,20 +199,7 @@ impl DateTypeOption {
|
||||
impl TypeOptionTransform for DateTypeOption {}
|
||||
|
||||
impl CellDataDecoder for DateTypeOption {
|
||||
fn decode_cell(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
decoded_field_type: &FieldType,
|
||||
_field: &Field,
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
// Return default data if the type_option_cell_data is not FieldType::DateTime.
|
||||
// It happens when switching from one field to another.
|
||||
// For example:
|
||||
// FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen.
|
||||
if !decoded_field_type.is_date() {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
self.parse_cell(cell)
|
||||
}
|
||||
|
||||
@ -244,11 +231,6 @@ impl CellDataDecoder for DateTypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
fn stringify_cell(&self, cell: &Cell) -> String {
|
||||
let cell_data = Self::CellData::from(cell);
|
||||
self.stringify_cell_data(cell_data)
|
||||
}
|
||||
|
||||
fn numeric_cell(&self, _cell: &Cell) -> Option<f64> {
|
||||
None
|
||||
}
|
||||
|
@ -1,111 +1,77 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use collab_database::fields::Field;
|
||||
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::CellDataDecoder;
|
||||
use crate::services::field::{FieldBuilder, NumberCellData};
|
||||
use crate::services::field::NumberCellData;
|
||||
use crate::services::field::{NumberFormat, NumberTypeOption};
|
||||
|
||||
/// Testing when the input is not a number.
|
||||
#[test]
|
||||
fn number_type_option_input_test() {
|
||||
let type_option = NumberTypeOption::default();
|
||||
let field_type = FieldType::Number;
|
||||
let field = FieldBuilder::from_field_type(field_type).build();
|
||||
|
||||
// Input is empty String
|
||||
assert_number(&type_option, "", "", &field_type, &field);
|
||||
assert_number(&type_option, "abc", "", &field_type, &field);
|
||||
assert_number(&type_option, "-123", "-123", &field_type, &field);
|
||||
assert_number(&type_option, "abc-123", "-123", &field_type, &field);
|
||||
assert_number(&type_option, "+123", "123", &field_type, &field);
|
||||
assert_number(&type_option, "0.2", "0.2", &field_type, &field);
|
||||
assert_number(&type_option, "-0.2", "-0.2", &field_type, &field);
|
||||
assert_number(&type_option, "-$0.2", "0.2", &field_type, &field);
|
||||
assert_number(&type_option, ".2", "0.2", &field_type, &field);
|
||||
assert_number(&type_option, "", "");
|
||||
assert_number(&type_option, "abc", "");
|
||||
assert_number(&type_option, "-123", "-123");
|
||||
assert_number(&type_option, "abc-123", "-123");
|
||||
assert_number(&type_option, "+123", "123");
|
||||
assert_number(&type_option, "0.2", "0.2");
|
||||
assert_number(&type_option, "-0.2", "-0.2");
|
||||
assert_number(&type_option, "-$0.2", "0.2");
|
||||
assert_number(&type_option, ".2", "0.2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dollar_type_option_test() {
|
||||
let field_type = FieldType::Number;
|
||||
let mut type_option = NumberTypeOption::new();
|
||||
type_option.format = NumberFormat::USD;
|
||||
let field = FieldBuilder::new(field_type, type_option.clone()).build();
|
||||
|
||||
assert_number(&type_option, "", "", &field_type, &field);
|
||||
assert_number(&type_option, "abc", "", &field_type, &field);
|
||||
assert_number(&type_option, "-123", "-$123", &field_type, &field);
|
||||
assert_number(&type_option, "+123", "$123", &field_type, &field);
|
||||
assert_number(&type_option, "0.2", "$0.2", &field_type, &field);
|
||||
assert_number(&type_option, "-0.2", "-$0.2", &field_type, &field);
|
||||
assert_number(&type_option, "-$0.2", "-$0.2", &field_type, &field);
|
||||
assert_number(&type_option, "-€0.2", "-$0.2", &field_type, &field);
|
||||
assert_number(&type_option, ".2", "$0.2", &field_type, &field);
|
||||
assert_number(&type_option, "", "");
|
||||
assert_number(&type_option, "abc", "");
|
||||
assert_number(&type_option, "-123", "-$123");
|
||||
assert_number(&type_option, "+123", "$123");
|
||||
assert_number(&type_option, "0.2", "$0.2");
|
||||
assert_number(&type_option, "-0.2", "-$0.2");
|
||||
assert_number(&type_option, "-$0.2", "-$0.2");
|
||||
assert_number(&type_option, "-€0.2", "-$0.2");
|
||||
assert_number(&type_option, ".2", "$0.2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dollar_type_option_test2() {
|
||||
let field_type = FieldType::Number;
|
||||
let mut type_option = NumberTypeOption::new();
|
||||
type_option.format = NumberFormat::USD;
|
||||
let field = FieldBuilder::new(field_type, type_option.clone()).build();
|
||||
|
||||
assert_number(
|
||||
&type_option,
|
||||
"99999999999",
|
||||
"$99,999,999,999",
|
||||
&field_type,
|
||||
&field,
|
||||
);
|
||||
assert_number(
|
||||
&type_option,
|
||||
"$99,999,999,999",
|
||||
"$99,999,999,999",
|
||||
&field_type,
|
||||
&field,
|
||||
);
|
||||
assert_number(&type_option, "99999999999", "$99,999,999,999");
|
||||
assert_number(&type_option, "$99,999,999,999", "$99,999,999,999");
|
||||
}
|
||||
#[test]
|
||||
fn other_symbol_to_dollar_type_option_test() {
|
||||
let field_type = FieldType::Number;
|
||||
let mut type_option = NumberTypeOption::new();
|
||||
type_option.format = NumberFormat::USD;
|
||||
let field = FieldBuilder::new(field_type, type_option.clone()).build();
|
||||
|
||||
assert_number(&type_option, "€0.2", "$0.2", &field_type, &field);
|
||||
assert_number(&type_option, "-€0.2", "-$0.2", &field_type, &field);
|
||||
assert_number(&type_option, "-CN¥0.2", "-$0.2", &field_type, &field);
|
||||
assert_number(&type_option, "CN¥0.2", "$0.2", &field_type, &field);
|
||||
assert_number(&type_option, "0.2", "$0.2", &field_type, &field);
|
||||
assert_number(&type_option, "€0.2", "$0.2");
|
||||
assert_number(&type_option, "-€0.2", "-$0.2");
|
||||
assert_number(&type_option, "-CN¥0.2", "-$0.2");
|
||||
assert_number(&type_option, "CN¥0.2", "$0.2");
|
||||
assert_number(&type_option, "0.2", "$0.2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn euro_type_option_test() {
|
||||
let field_type = FieldType::Number;
|
||||
let mut type_option = NumberTypeOption::new();
|
||||
type_option.format = NumberFormat::EUR;
|
||||
let field = FieldBuilder::new(field_type, type_option.clone()).build();
|
||||
|
||||
assert_number(&type_option, "0.2", "€0,2", &field_type, &field);
|
||||
assert_number(&type_option, "1000", "€1.000", &field_type, &field);
|
||||
assert_number(&type_option, "1234.56", "€1.234,56", &field_type, &field);
|
||||
assert_number(&type_option, "0.2", "€0,2");
|
||||
assert_number(&type_option, "1000", "€1.000");
|
||||
assert_number(&type_option, "1234.56", "€1.234,56");
|
||||
}
|
||||
|
||||
fn assert_number(
|
||||
type_option: &NumberTypeOption,
|
||||
input_str: &str,
|
||||
expected_str: &str,
|
||||
field_type: &FieldType,
|
||||
field: &Field,
|
||||
) {
|
||||
fn assert_number(type_option: &NumberTypeOption, input_str: &str, expected_str: &str) {
|
||||
assert_eq!(
|
||||
type_option
|
||||
.decode_cell(
|
||||
&NumberCellData(input_str.to_owned()).into(),
|
||||
field_type,
|
||||
field
|
||||
)
|
||||
.decode_cell(&NumberCellData(input_str.to_owned()).into(),)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
expected_str.to_owned()
|
||||
|
@ -3,7 +3,7 @@ use std::default::Default;
|
||||
use std::str::FromStr;
|
||||
|
||||
use collab::core::any_map::AnyMapExtension;
|
||||
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::rows::{new_cell_builder, Cell};
|
||||
use fancy_regex::Regex;
|
||||
use lazy_static::lazy_static;
|
||||
@ -175,19 +175,7 @@ impl NumberTypeOption {
|
||||
impl TypeOptionTransform for NumberTypeOption {}
|
||||
|
||||
impl CellDataDecoder for NumberTypeOption {
|
||||
fn decode_cell(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
decoded_field_type: &FieldType,
|
||||
_field: &Field,
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
if decoded_field_type.is_date()
|
||||
|| decoded_field_type.is_created_time()
|
||||
|| decoded_field_type.is_last_edited_time()
|
||||
{
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
let num_cell_data = self.parse_cell(cell)?;
|
||||
Ok(NumberCellData::from(
|
||||
self.format_cell_data(&num_cell_data)?.to_string(),
|
||||
@ -201,11 +189,6 @@ impl CellDataDecoder for NumberTypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
fn stringify_cell(&self, cell: &Cell) -> String {
|
||||
let cell_data = Self::CellData::from(cell);
|
||||
self.stringify_cell_data(cell_data)
|
||||
}
|
||||
|
||||
fn numeric_cell(&self, cell: &Cell) -> Option<f64> {
|
||||
let num_cell_data = self.parse_cell(cell).ok()?;
|
||||
num_cell_data.0.parse::<f64>().ok()
|
||||
|
@ -1,12 +1,12 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use collab::core::any_map::AnyMapExtension;
|
||||
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::rows::Cell;
|
||||
use flowy_error::FlowyResult;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::entities::{FieldType, RelationCellDataPB, RelationFilterPB};
|
||||
use crate::entities::{RelationCellDataPB, RelationFilterPB};
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
|
||||
use crate::services::field::{
|
||||
default_order, TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
@ -77,16 +77,7 @@ impl CellDataChangeset for RelationTypeOption {
|
||||
}
|
||||
|
||||
impl CellDataDecoder for RelationTypeOption {
|
||||
fn decode_cell(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
decoded_field_type: &FieldType,
|
||||
_field: &Field,
|
||||
) -> FlowyResult<RelationCellData> {
|
||||
if !decoded_field_type.is_relation() {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
fn decode_cell(&self, cell: &Cell) -> FlowyResult<RelationCellData> {
|
||||
Ok(cell.into())
|
||||
}
|
||||
|
||||
@ -94,11 +85,6 @@ impl CellDataDecoder for RelationTypeOption {
|
||||
cell_data.to_string()
|
||||
}
|
||||
|
||||
fn stringify_cell(&self, cell: &Cell) -> String {
|
||||
let cell_data = RelationCellData::from(cell);
|
||||
cell_data.to_string()
|
||||
}
|
||||
|
||||
fn numeric_cell(&self, _cell: &Cell) -> Option<f64> {
|
||||
None
|
||||
}
|
||||
@ -121,27 +107,7 @@ impl TypeOptionCellDataFilter for RelationTypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionTransform for RelationTypeOption {
|
||||
fn transformable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn transform_type_option(
|
||||
&mut self,
|
||||
_old_type_option_field_type: FieldType,
|
||||
_old_type_option_data: TypeOptionData,
|
||||
) {
|
||||
}
|
||||
|
||||
fn transform_type_option_cell(
|
||||
&self,
|
||||
_cell: &Cell,
|
||||
_transformed_field_type: &FieldType,
|
||||
_field: &Field,
|
||||
) -> Option<RelationCellData> {
|
||||
None
|
||||
}
|
||||
}
|
||||
impl TypeOptionTransform for RelationTypeOption {}
|
||||
|
||||
impl TypeOptionCellDataSerde for RelationTypeOption {
|
||||
fn protobuf_encode(&self, cell_data: RelationCellData) -> RelationCellDataPB {
|
||||
|
@ -75,10 +75,6 @@ impl<T> TypeOptionTransform for T
|
||||
where
|
||||
T: SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds> + CellDataDecoder,
|
||||
{
|
||||
fn transformable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn transform_type_option(
|
||||
&mut self,
|
||||
_old_type_option_field_type: FieldType,
|
||||
@ -90,18 +86,24 @@ where
|
||||
_old_type_option_data,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_type_option_cell(
|
||||
impl<T> CellDataDecoder for T
|
||||
where
|
||||
T:
|
||||
SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds> + TypeOptionCellDataSerde,
|
||||
{
|
||||
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
self.parse_cell(cell)
|
||||
}
|
||||
|
||||
fn decode_cell_with_transform(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
transformed_field_type: &FieldType,
|
||||
from_field_type: FieldType,
|
||||
_field: &Field,
|
||||
) -> Option<<Self as TypeOption>::CellData> {
|
||||
match transformed_field_type {
|
||||
FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => {
|
||||
// If the transformed field type is SingleSelect, MultiSelect or Checklist, Do nothing.
|
||||
None
|
||||
},
|
||||
match from_field_type {
|
||||
FieldType::Checkbox => {
|
||||
let cell_content = CheckboxCellDataPB::from(cell).to_string();
|
||||
let mut transformed_ids = Vec::new();
|
||||
@ -112,28 +114,14 @@ where
|
||||
Some(SelectOptionIds::from(transformed_ids))
|
||||
},
|
||||
FieldType::RichText => Some(SelectOptionIds::from(cell)),
|
||||
_ => Some(SelectOptionIds::from(vec![])),
|
||||
FieldType::SingleSelect | FieldType::MultiSelect => Some(SelectOptionIds::from(cell)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C> CellDataDecoder for T
|
||||
where
|
||||
C: Into<SelectOptionIds> + for<'a> From<&'a Cell>,
|
||||
T: SelectTypeOptionSharedAction + TypeOption<CellData = C> + TypeOptionCellDataSerde,
|
||||
{
|
||||
fn decode_cell(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
_decoded_field_type: &FieldType,
|
||||
_field: &Field,
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
self.parse_cell(cell)
|
||||
}
|
||||
|
||||
fn stringify_cell_data(&self, cell_data: C) -> String {
|
||||
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
|
||||
self
|
||||
.get_selected_options(cell_data.into())
|
||||
.get_selected_options(cell_data)
|
||||
.select_options
|
||||
.into_iter()
|
||||
.map(|option| option.name)
|
||||
@ -141,11 +129,6 @@ where
|
||||
.join(SELECTION_IDS_SEPARATOR)
|
||||
}
|
||||
|
||||
fn stringify_cell(&self, cell: &Cell) -> String {
|
||||
let cell_data = C::from(cell);
|
||||
self.stringify_cell_data(cell_data)
|
||||
}
|
||||
|
||||
fn numeric_cell(&self, _cell: &Cell) -> Option<f64> {
|
||||
None
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ impl SelectOptionTypeOptionTransformHelper {
|
||||
{
|
||||
match old_field_type {
|
||||
FieldType::Checkbox => {
|
||||
//add Yes and No options if it does not exist.
|
||||
// add Yes and No options if it does not exist.
|
||||
if !shared.options().iter().any(|option| option.name == CHECK) {
|
||||
let check_option = SelectOption::with_color(CHECK, SelectOptionColor::Green);
|
||||
shared.mut_options().push(check_option);
|
||||
|
@ -1,9 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use collab_database::rows::Cell;
|
||||
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::stringify_cell_data;
|
||||
use crate::services::cell::{insert_select_option_cell, stringify_cell};
|
||||
use crate::services::field::FieldBuilder;
|
||||
use crate::services::field::*;
|
||||
|
||||
@ -14,15 +12,12 @@ mod tests {
|
||||
let field_type = FieldType::DateTime;
|
||||
let field = FieldBuilder::new(field_type, DateTypeOption::test()).build();
|
||||
|
||||
assert_eq!(
|
||||
stringify_cell_data(
|
||||
&to_text_cell(1647251762.to_string()),
|
||||
&FieldType::RichText,
|
||||
&field_type,
|
||||
&field
|
||||
),
|
||||
"Mar 14, 2022"
|
||||
);
|
||||
let data = DateCellData {
|
||||
timestamp: Some(1647251762),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(stringify_cell(&(&data).into(), &field), "Mar 14, 2022");
|
||||
|
||||
let data = DateCellData {
|
||||
timestamp: Some(1647251762),
|
||||
@ -33,7 +28,7 @@ mod tests {
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
stringify_cell_data(&(&data).into(), &FieldType::RichText, &field_type, &field),
|
||||
stringify_cell(&(&data).into(), &field),
|
||||
"Mar 14, 2022 09:56"
|
||||
);
|
||||
|
||||
@ -46,7 +41,7 @@ mod tests {
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
stringify_cell_data(&(&data).into(), &FieldType::RichText, &field_type, &field),
|
||||
stringify_cell(&(&data).into(), &field),
|
||||
"Mar 14, 2022 09:56"
|
||||
);
|
||||
|
||||
@ -59,15 +54,11 @@ mod tests {
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
stringify_cell_data(&(&data).into(), &FieldType::RichText, &field_type, &field),
|
||||
stringify_cell(&(&data).into(), &field),
|
||||
"Mar 14, 2022 09:56 → Mar 29, 2022 06:03"
|
||||
);
|
||||
}
|
||||
|
||||
fn to_text_cell(s: String) -> Cell {
|
||||
StrCellData(s).into()
|
||||
}
|
||||
|
||||
// Test parser the cell data which field's type is FieldType::SingleSelect to cell data
|
||||
// which field's type is FieldType::Text
|
||||
#[test]
|
||||
@ -82,20 +73,11 @@ mod tests {
|
||||
};
|
||||
let field = FieldBuilder::new(field_type, single_select).build();
|
||||
|
||||
assert_eq!(
|
||||
stringify_cell_data(
|
||||
&to_text_cell(option_id),
|
||||
&FieldType::RichText,
|
||||
&field_type,
|
||||
&field
|
||||
),
|
||||
done_option.name,
|
||||
);
|
||||
let cell = insert_select_option_cell(vec![option_id], &field);
|
||||
|
||||
assert_eq!(stringify_cell(&cell, &field), done_option.name,);
|
||||
}
|
||||
/*
|
||||
- [Unit Test] Testing the switching from Multi-selection type to Text type
|
||||
- Tracking : https://github.com/AppFlowy-IO/AppFlowy/issues/1183
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn multiselect_to_text_type() {
|
||||
let field_type = FieldType::MultiSelect;
|
||||
@ -110,15 +92,12 @@ mod tests {
|
||||
let france_option_id = france.id;
|
||||
let argentina_option_id = argentina.id;
|
||||
|
||||
let field_rev = FieldBuilder::new(field_type, multi_select).build();
|
||||
let field = FieldBuilder::new(field_type, multi_select).build();
|
||||
|
||||
let cell = insert_select_option_cell(vec![france_option_id, argentina_option_id], &field);
|
||||
|
||||
assert_eq!(
|
||||
stringify_cell_data(
|
||||
&to_text_cell(format!("{},{}", france_option_id, argentina_option_id)),
|
||||
&FieldType::RichText,
|
||||
&field_type,
|
||||
&field_rev
|
||||
),
|
||||
stringify_cell(&cell, &field),
|
||||
format!("{},{}", france.name, argentina.name)
|
||||
);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use flowy_error::{FlowyError, FlowyResult};
|
||||
|
||||
use crate::entities::{FieldType, TextFilterPB};
|
||||
use crate::services::cell::{
|
||||
stringify_cell_data, CellDataChangeset, CellDataDecoder, CellProtobufBlobParser,
|
||||
stringify_cell, CellDataChangeset, CellDataDecoder, CellProtobufBlobParser,
|
||||
};
|
||||
use crate::services::field::type_options::util::ProtobufStr;
|
||||
use crate::services::field::{
|
||||
@ -49,42 +49,7 @@ impl From<RichTextTypeOption> for TypeOptionData {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionTransform for RichTextTypeOption {
|
||||
fn transformable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn transform_type_option(
|
||||
&mut self,
|
||||
_old_type_option_field_type: FieldType,
|
||||
_old_type_option_data: TypeOptionData,
|
||||
) {
|
||||
}
|
||||
|
||||
fn transform_type_option_cell(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
transformed_field_type: &FieldType,
|
||||
_field: &Field,
|
||||
) -> Option<<Self as TypeOption>::CellData> {
|
||||
if transformed_field_type.is_date()
|
||||
|| transformed_field_type.is_single_select()
|
||||
|| transformed_field_type.is_multi_select()
|
||||
|| transformed_field_type.is_number()
|
||||
|| transformed_field_type.is_url()
|
||||
|| transformed_field_type.is_checklist()
|
||||
{
|
||||
Some(StrCellData::from(stringify_cell_data(
|
||||
cell,
|
||||
transformed_field_type,
|
||||
transformed_field_type,
|
||||
_field,
|
||||
)))
|
||||
} else {
|
||||
Some(StrCellData::from(cell))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TypeOptionTransform for RichTextTypeOption {}
|
||||
|
||||
impl TypeOptionCellDataSerde for RichTextTypeOption {
|
||||
fn protobuf_encode(
|
||||
@ -100,23 +65,35 @@ impl TypeOptionCellDataSerde for RichTextTypeOption {
|
||||
}
|
||||
|
||||
impl CellDataDecoder for RichTextTypeOption {
|
||||
fn decode_cell(
|
||||
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
Ok(StrCellData::from(cell))
|
||||
}
|
||||
|
||||
fn decode_cell_with_transform(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
_decoded_field_type: &FieldType,
|
||||
_field: &Field,
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
Ok(StrCellData::from(cell))
|
||||
from_field_type: FieldType,
|
||||
field: &Field,
|
||||
) -> Option<<Self as TypeOption>::CellData> {
|
||||
match from_field_type {
|
||||
FieldType::RichText
|
||||
| FieldType::Number
|
||||
| FieldType::DateTime
|
||||
| FieldType::SingleSelect
|
||||
| FieldType::MultiSelect
|
||||
| FieldType::Checkbox
|
||||
| FieldType::URL => Some(StrCellData::from(stringify_cell(cell, field))),
|
||||
FieldType::Checklist
|
||||
| FieldType::LastEditedTime
|
||||
| FieldType::CreatedTime
|
||||
| FieldType::Relation => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
|
||||
cell_data.to_string()
|
||||
}
|
||||
|
||||
fn stringify_cell(&self, cell: &Cell) -> String {
|
||||
Self::CellData::from(cell).to_string()
|
||||
}
|
||||
|
||||
fn numeric_cell(&self, cell: &Cell) -> Option<f64> {
|
||||
StrCellData::from(cell).0.parse::<f64>().ok()
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use std::cmp::Ordering;
|
||||
|
||||
use chrono::{DateTime, Local, Offset};
|
||||
use collab::core::any_map::AnyMapExtension;
|
||||
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::rows::Cell;
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -124,17 +124,7 @@ impl TimestampTypeOption {
|
||||
impl TypeOptionTransform for TimestampTypeOption {}
|
||||
|
||||
impl CellDataDecoder for TimestampTypeOption {
|
||||
fn decode_cell(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
decoded_field_type: &FieldType,
|
||||
_field: &Field,
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
// Return default data if the type_option_cell_data is not FieldType::CreatedTime nor FieldType::LastEditedTime
|
||||
if !decoded_field_type.is_last_edited_time() && !decoded_field_type.is_created_time() {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
self.parse_cell(cell)
|
||||
}
|
||||
|
||||
@ -148,11 +138,6 @@ impl CellDataDecoder for TimestampTypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
fn stringify_cell(&self, cell: &Cell) -> String {
|
||||
let cell_data = Self::CellData::from(cell);
|
||||
self.stringify_cell_data(cell_data)
|
||||
}
|
||||
|
||||
fn numeric_cell(&self, _cell: &Cell) -> Option<f64> {
|
||||
None
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use std::cmp::Ordering;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use bytes::Bytes;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::fields::TypeOptionData;
|
||||
use collab_database::rows::Cell;
|
||||
use protobuf::ProtobufError;
|
||||
|
||||
@ -90,11 +90,6 @@ pub trait TypeOptionCellData {
|
||||
}
|
||||
|
||||
pub trait TypeOptionTransform: TypeOption {
|
||||
/// Returns true if the current `TypeOption` provides custom type option transformation
|
||||
fn transformable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@ -112,23 +107,6 @@ pub trait TypeOptionTransform: TypeOption {
|
||||
_old_type_option_data: TypeOptionData,
|
||||
) {
|
||||
}
|
||||
|
||||
/// Transform the cell data from one field type to another
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cell`: the cell in the current field type
|
||||
/// * `transformed_field_type`: the cell will be transformed to the is field type's cell data.
|
||||
/// current `TypeOption` field type.
|
||||
///
|
||||
fn transform_type_option_cell(
|
||||
&self,
|
||||
_cell: &Cell,
|
||||
_transformed_field_type: &FieldType,
|
||||
_field: &Field,
|
||||
) -> Option<<Self as TypeOption>::CellData> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TypeOptionCellDataFilter: TypeOption + CellDataDecoder {
|
||||
|
@ -3,18 +3,17 @@ use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{Cell, RowId};
|
||||
use collab_database::rows::{get_field_type_from_cell, Cell, RowId};
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::{CellCache, CellDataChangeset, CellDataDecoder, CellProtobufBlob};
|
||||
use crate::services::field::checklist_type_option::ChecklistTypeOption;
|
||||
use crate::services::field::{
|
||||
CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RelationTypeOption,
|
||||
RichTextTypeOption, SingleSelectTypeOption, TimestampTypeOption, TypeOption,
|
||||
TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionCellDataSerde,
|
||||
CheckboxTypeOption, ChecklistTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption,
|
||||
RelationTypeOption, RichTextTypeOption, SingleSelectTypeOption, TimestampTypeOption, TypeOption,
|
||||
TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionCellDataSerde,
|
||||
TypeOptionTransform, URLTypeOption,
|
||||
};
|
||||
use crate::services::sort::SortCondition;
|
||||
@ -31,10 +30,13 @@ pub const CELL_DATA: &str = "data";
|
||||
/// 2. there are no generic types parameters.
|
||||
///
|
||||
pub trait TypeOptionCellDataHandler: Send + Sync + 'static {
|
||||
fn handle_cell_protobuf(
|
||||
/// Format the cell to [BoxCellData] using the passed-in [FieldType] and [Field].
|
||||
/// The caller can get the cell data by calling [BoxCellData::unbox_or_none].
|
||||
fn handle_get_boxed_cell_data(&self, cell: &Cell, field: &Field) -> Option<BoxCellData>;
|
||||
|
||||
fn handle_get_protobuf_cell_data(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
decoded_field_type: &FieldType,
|
||||
field_rev: &Field,
|
||||
) -> FlowyResult<CellProtobufBlob>;
|
||||
|
||||
@ -45,190 +47,6 @@ pub trait TypeOptionCellDataHandler: Send + Sync + 'static {
|
||||
field: &Field,
|
||||
) -> FlowyResult<Cell>;
|
||||
|
||||
fn handle_cell_compare(
|
||||
&self,
|
||||
left_cell: Option<&Cell>,
|
||||
right_cell: Option<&Cell>,
|
||||
field: &Field,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering;
|
||||
|
||||
fn handle_cell_filter(&self, field: &Field, cell: &Cell, filter: &BoxAny) -> bool;
|
||||
|
||||
/// Format the cell to string using the passed-in [FieldType] and [Field].
|
||||
/// The [Cell] is generic, so we need to know the [FieldType] and [Field] to format the cell.
|
||||
///
|
||||
/// For example, the field type of the [TypeOptionCellDataHandler] is [FieldType::Date], and
|
||||
/// the if field_type is [FieldType::RichText], then the string would be something like "Mar 14, 2022".
|
||||
///
|
||||
fn handle_stringify_cell(&self, cell: &Cell, field_type: &FieldType, field: &Field) -> String;
|
||||
|
||||
fn handle_numeric_cell(&self, cell: &Cell) -> Option<f64>;
|
||||
|
||||
/// Format the cell to [BoxCellData] using the passed-in [FieldType] and [Field].
|
||||
/// The caller can get the cell data by calling [BoxCellData::unbox_or_none].
|
||||
fn get_cell_data(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
field_type: &FieldType,
|
||||
field: &Field,
|
||||
) -> FlowyResult<BoxCellData>;
|
||||
}
|
||||
|
||||
struct CellDataCacheKey(u64);
|
||||
impl CellDataCacheKey {
|
||||
pub fn new(field_rev: &Field, decoded_field_type: FieldType, cell: &Cell) -> Self {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
if let Some(type_option_data) = field_rev.get_any_type_option(decoded_field_type) {
|
||||
type_option_data.hash(&mut hasher);
|
||||
}
|
||||
hasher.write(field_rev.id.as_bytes());
|
||||
hasher.write_u8(decoded_field_type as u8);
|
||||
cell.hash(&mut hasher);
|
||||
Self(hasher.finish())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<u64> for CellDataCacheKey {
|
||||
fn as_ref(&self) -> &u64 {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
struct TypeOptionCellDataHandlerImpl<T> {
|
||||
inner: T,
|
||||
cell_data_cache: Option<CellCache>,
|
||||
}
|
||||
|
||||
impl<T> TypeOptionCellDataHandlerImpl<T>
|
||||
where
|
||||
T: TypeOption
|
||||
+ CellDataDecoder
|
||||
+ CellDataChangeset
|
||||
+ TypeOptionCellDataSerde
|
||||
+ TypeOptionTransform
|
||||
+ TypeOptionCellDataFilter
|
||||
+ TypeOptionCellDataCompare
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
{
|
||||
pub fn into_boxed(self) -> Box<dyn TypeOptionCellDataHandler> {
|
||||
Box::new(self) as Box<dyn TypeOptionCellDataHandler>
|
||||
}
|
||||
|
||||
pub fn new_with_boxed(
|
||||
inner: T,
|
||||
cell_data_cache: Option<CellCache>,
|
||||
) -> Box<dyn TypeOptionCellDataHandler> {
|
||||
Self {
|
||||
inner,
|
||||
cell_data_cache,
|
||||
}
|
||||
.into_boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TypeOptionCellDataHandlerImpl<T>
|
||||
where
|
||||
T: TypeOption + CellDataDecoder + Send + Sync,
|
||||
{
|
||||
fn get_decoded_cell_data(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
decoded_field_type: &FieldType,
|
||||
field: &Field,
|
||||
) -> FlowyResult<T::CellData> {
|
||||
let key = CellDataCacheKey::new(field, *decoded_field_type, cell);
|
||||
if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
|
||||
let read_guard = cell_data_cache.read();
|
||||
if let Some(cell_data) = read_guard.get(key.as_ref()).cloned() {
|
||||
// tracing::trace!(
|
||||
// "Cell cache hit: field_type:{}, cell: {:?}, cell_data: {:?}",
|
||||
// decoded_field_type,
|
||||
// cell,
|
||||
// cell_data
|
||||
// );
|
||||
return Ok(cell_data);
|
||||
}
|
||||
}
|
||||
|
||||
let cell_data = self.decode_cell(cell, decoded_field_type, field)?;
|
||||
if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
|
||||
// tracing::trace!(
|
||||
// "Cell cache update: field_type:{}, cell: {:?}, cell_data: {:?}",
|
||||
// decoded_field_type,
|
||||
// cell,
|
||||
// cell_data
|
||||
// );
|
||||
cell_data_cache
|
||||
.write()
|
||||
.insert(key.as_ref(), cell_data.clone());
|
||||
}
|
||||
Ok(cell_data)
|
||||
}
|
||||
|
||||
fn set_decoded_cell_data(&self, cell: &Cell, cell_data: T::CellData, field: &Field) {
|
||||
if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
let key = CellDataCacheKey::new(field, field_type, cell);
|
||||
// tracing::trace!(
|
||||
// "Cell cache update: field_type:{}, cell: {:?}, cell_data: {:?}",
|
||||
// field_type,
|
||||
// cell,
|
||||
// cell_data
|
||||
// );
|
||||
cell_data_cache.write().insert(key.as_ref(), cell_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for TypeOptionCellDataHandlerImpl<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TypeOptionCellDataHandler for TypeOptionCellDataHandlerImpl<T>
|
||||
where
|
||||
T: TypeOption
|
||||
+ CellDataDecoder
|
||||
+ CellDataChangeset
|
||||
+ TypeOptionCellDataSerde
|
||||
+ TypeOptionTransform
|
||||
+ TypeOptionCellDataFilter
|
||||
+ TypeOptionCellDataCompare
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
{
|
||||
fn handle_cell_protobuf(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
decoded_field_type: &FieldType,
|
||||
field_rev: &Field,
|
||||
) -> FlowyResult<CellProtobufBlob> {
|
||||
let cell_data = self
|
||||
.get_cell_data(cell, decoded_field_type, field_rev)?
|
||||
.unbox_or_default::<T::CellData>();
|
||||
|
||||
CellProtobufBlob::from(self.protobuf_encode(cell_data))
|
||||
}
|
||||
|
||||
fn handle_cell_changeset(
|
||||
&self,
|
||||
cell_changeset: BoxAny,
|
||||
old_cell: Option<Cell>,
|
||||
field: &Field,
|
||||
) -> FlowyResult<Cell> {
|
||||
let changeset = cell_changeset.unbox_or_error::<T::CellChangeset>()?;
|
||||
let (cell, cell_data) = self.apply_changeset(changeset, old_cell)?;
|
||||
self.set_decoded_cell_data(&cell, cell_data, field);
|
||||
Ok(cell)
|
||||
}
|
||||
|
||||
/// Compares two cell data values given their optional references, field information, and sorting condition.
|
||||
///
|
||||
/// This function is designed to handle the comparison of cells that might not be initialized. The cells are
|
||||
@ -256,32 +74,207 @@ where
|
||||
right_cell: Option<&Cell>,
|
||||
field: &Field,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
) -> Ordering;
|
||||
|
||||
fn handle_cell_filter(&self, field: &Field, cell: &Cell, filter: &BoxAny) -> bool;
|
||||
|
||||
/// Format the cell to string using the passed-in [FieldType] and [Field].
|
||||
/// The [Cell] is generic, so we need to know the [FieldType] and [Field] to format the cell.
|
||||
///
|
||||
/// For example, the field type of the [TypeOptionCellDataHandler] is [FieldType::Date], and
|
||||
/// the if field_type is [FieldType::RichText], then the string would be something like "Mar 14, 2022".
|
||||
///
|
||||
fn handle_stringify_cell(&self, cell: &Cell, field: &Field) -> String;
|
||||
|
||||
fn handle_numeric_cell(&self, cell: &Cell) -> Option<f64>;
|
||||
|
||||
fn handle_is_cell_empty(&self, cell: &Cell, field: &Field) -> bool;
|
||||
}
|
||||
|
||||
struct CellDataCacheKey(u64);
|
||||
impl CellDataCacheKey {
|
||||
pub fn new(field_rev: &Field, decoded_field_type: FieldType, cell: &Cell) -> Self {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
if let Some(type_option_data) = field_rev.get_any_type_option(decoded_field_type) {
|
||||
type_option_data.hash(&mut hasher);
|
||||
}
|
||||
hasher.write(field_rev.id.as_bytes());
|
||||
hasher.write_u8(decoded_field_type as u8);
|
||||
cell.hash(&mut hasher);
|
||||
Self(hasher.finish())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<u64> for CellDataCacheKey {
|
||||
fn as_ref(&self) -> &u64 {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
struct TypeOptionCellDataHandlerImpl<T> {
|
||||
inner: T,
|
||||
field_type: FieldType,
|
||||
cell_data_cache: Option<CellCache>,
|
||||
}
|
||||
|
||||
impl<T> TypeOptionCellDataHandlerImpl<T>
|
||||
where
|
||||
T: TypeOption
|
||||
+ CellDataDecoder
|
||||
+ CellDataChangeset
|
||||
+ TypeOptionCellDataSerde
|
||||
+ TypeOptionTransform
|
||||
+ TypeOptionCellDataFilter
|
||||
+ TypeOptionCellDataCompare
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
{
|
||||
pub fn into_boxed(self) -> Box<dyn TypeOptionCellDataHandler> {
|
||||
Box::new(self) as Box<dyn TypeOptionCellDataHandler>
|
||||
}
|
||||
|
||||
pub fn new_with_boxed(
|
||||
inner: T,
|
||||
field_type: FieldType,
|
||||
cell_data_cache: Option<CellCache>,
|
||||
) -> Box<dyn TypeOptionCellDataHandler> {
|
||||
Self {
|
||||
inner,
|
||||
field_type,
|
||||
cell_data_cache,
|
||||
}
|
||||
.into_boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TypeOptionCellDataHandlerImpl<T>
|
||||
where
|
||||
T: TypeOption + CellDataDecoder + Send + Sync,
|
||||
{
|
||||
fn get_cell_data_cache_key(&self, cell: &Cell, field: &Field) -> CellDataCacheKey {
|
||||
CellDataCacheKey::new(field, self.field_type, cell)
|
||||
}
|
||||
|
||||
fn get_cell_data_from_cache(&self, cell: &Cell, field: &Field) -> Option<T::CellData> {
|
||||
let key = self.get_cell_data_cache_key(cell, field);
|
||||
|
||||
let cell_data_cache = self.cell_data_cache.as_ref()?.read();
|
||||
|
||||
cell_data_cache.get(key.as_ref()).cloned()
|
||||
}
|
||||
|
||||
fn set_cell_data_in_cache(&self, cell: &Cell, cell_data: T::CellData, field: &Field) {
|
||||
if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
let key = CellDataCacheKey::new(field, field_type, cell);
|
||||
// tracing::trace!(
|
||||
// "Cell cache update: field_type:{}, cell: {:?}, cell_data: {:?}",
|
||||
// field_type,
|
||||
// cell,
|
||||
// cell_data
|
||||
// );
|
||||
cell_data_cache.write().insert(key.as_ref(), cell_data);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cell_data(&self, cell: &Cell, field: &Field) -> Option<T::CellData> {
|
||||
let field_type_of_cell = get_field_type_from_cell(cell)?;
|
||||
|
||||
if let Some(cell_data) = self.get_cell_data_from_cache(cell, field) {
|
||||
return Some(cell_data);
|
||||
}
|
||||
|
||||
let cell_data = if field_type_of_cell == self.field_type {
|
||||
Some(self.decode_cell(cell).unwrap_or_default())
|
||||
} else if is_type_option_cell_transformable(field_type_of_cell, self.field_type) {
|
||||
Some(
|
||||
self
|
||||
.decode_cell_with_transform(cell, field_type_of_cell, field)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(data) = &cell_data {
|
||||
self.set_cell_data_in_cache(cell, data.clone(), field);
|
||||
}
|
||||
|
||||
cell_data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for TypeOptionCellDataHandlerImpl<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TypeOptionCellDataHandler for TypeOptionCellDataHandlerImpl<T>
|
||||
where
|
||||
T: TypeOption
|
||||
+ CellDataDecoder
|
||||
+ CellDataChangeset
|
||||
+ TypeOptionCellDataSerde
|
||||
+ TypeOptionTransform
|
||||
+ TypeOptionCellDataFilter
|
||||
+ TypeOptionCellDataCompare
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
{
|
||||
fn handle_get_boxed_cell_data(&self, cell: &Cell, field: &Field) -> Option<BoxCellData> {
|
||||
let cell_data = self.get_cell_data(cell, field)?;
|
||||
Some(BoxCellData::new(cell_data))
|
||||
}
|
||||
|
||||
fn handle_get_protobuf_cell_data(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
field_rev: &Field,
|
||||
) -> FlowyResult<CellProtobufBlob> {
|
||||
let cell_data = self.get_cell_data(cell, field_rev).unwrap_or_default();
|
||||
|
||||
CellProtobufBlob::from(self.protobuf_encode(cell_data))
|
||||
}
|
||||
|
||||
fn handle_cell_changeset(
|
||||
&self,
|
||||
cell_changeset: BoxAny,
|
||||
old_cell: Option<Cell>,
|
||||
field: &Field,
|
||||
) -> FlowyResult<Cell> {
|
||||
let changeset = cell_changeset.unbox_or_error::<T::CellChangeset>()?;
|
||||
let (cell, cell_data) = self.apply_changeset(changeset, old_cell)?;
|
||||
self.set_cell_data_in_cache(&cell, cell_data, field);
|
||||
Ok(cell)
|
||||
}
|
||||
|
||||
fn handle_cell_compare(
|
||||
&self,
|
||||
left_cell: Option<&Cell>,
|
||||
right_cell: Option<&Cell>,
|
||||
field: &Field,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
match (left_cell, right_cell) {
|
||||
(None, None) => Ordering::Equal,
|
||||
(None, Some(right_cell)) => {
|
||||
let right_cell_data = self
|
||||
.get_decoded_cell_data(right_cell, &field_type, field)
|
||||
.unwrap_or_default();
|
||||
let right_cell_data = self.get_cell_data(right_cell, field).unwrap_or_default();
|
||||
|
||||
self.apply_cmp_with_uninitialized(None, Some(right_cell_data).as_ref(), sort_condition)
|
||||
},
|
||||
(Some(left_cell), None) => {
|
||||
let left_cell_data = self
|
||||
.get_decoded_cell_data(left_cell, &field_type, field)
|
||||
.unwrap_or_default();
|
||||
let left_cell_data = self.get_cell_data(left_cell, field).unwrap_or_default();
|
||||
|
||||
self.apply_cmp_with_uninitialized(Some(left_cell_data).as_ref(), None, sort_condition)
|
||||
},
|
||||
(Some(left_cell), Some(right_cell)) => {
|
||||
let left_cell_data: <T as TypeOption>::CellData = self
|
||||
.get_decoded_cell_data(left_cell, &field_type, field)
|
||||
.unwrap_or_default();
|
||||
let right_cell_data = self
|
||||
.get_decoded_cell_data(right_cell, &field_type, field)
|
||||
.unwrap_or_default();
|
||||
let left_cell_data = self.get_cell_data(left_cell, field).unwrap_or_default();
|
||||
let right_cell_data = self.get_cell_data(right_cell, field).unwrap_or_default();
|
||||
|
||||
self.apply_cmp(&left_cell_data, &right_cell_data, sort_condition)
|
||||
},
|
||||
@ -290,9 +283,8 @@ where
|
||||
|
||||
fn handle_cell_filter(&self, field: &Field, cell: &Cell, filter: &BoxAny) -> bool {
|
||||
let perform_filter = || {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
let cell_filter = filter.downcast_ref::<T::CellFilter>()?;
|
||||
let cell_data = self.get_decoded_cell_data(cell, &field_type, field).ok()?;
|
||||
let cell_data = self.get_cell_data(cell, field).unwrap_or_default();
|
||||
Some(self.apply_filter(cell_filter, &cell_data))
|
||||
};
|
||||
|
||||
@ -306,36 +298,24 @@ where
|
||||
/// is [FieldType::RichText], then the string will be transformed to a string that separated by comma with the
|
||||
/// option's name.
|
||||
///
|
||||
fn handle_stringify_cell(&self, cell: &Cell, field_type: &FieldType, field: &Field) -> String {
|
||||
if self.transformable() {
|
||||
let cell_data = self.transform_type_option_cell(cell, field_type, field);
|
||||
fn handle_stringify_cell(&self, cell: &Cell, field: &Field) -> String {
|
||||
if is_type_option_cell_transformable(self.field_type, FieldType::RichText) {
|
||||
let cell_data = self.get_cell_data(cell, field);
|
||||
if let Some(cell_data) = cell_data {
|
||||
return self.stringify_cell_data(cell_data);
|
||||
}
|
||||
}
|
||||
self.stringify_cell(cell)
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
fn handle_numeric_cell(&self, cell: &Cell) -> Option<f64> {
|
||||
self.numeric_cell(cell)
|
||||
}
|
||||
|
||||
fn get_cell_data(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
field_type: &FieldType,
|
||||
field: &Field,
|
||||
) -> FlowyResult<BoxCellData> {
|
||||
// tracing::debug!("get_cell_data: {:?}", std::any::type_name::<Self>());
|
||||
let cell_data = if self.transformable() {
|
||||
match self.transform_type_option_cell(cell, field_type, field) {
|
||||
None => self.get_decoded_cell_data(cell, field_type, field)?,
|
||||
Some(cell_data) => cell_data,
|
||||
}
|
||||
} else {
|
||||
self.get_decoded_cell_data(cell, field_type, field)?
|
||||
};
|
||||
Ok(BoxCellData::new(cell_data))
|
||||
fn handle_is_cell_empty(&self, cell: &Cell, field: &Field) -> bool {
|
||||
let cell_data = self.get_cell_data(cell, field).unwrap_or_default();
|
||||
|
||||
cell_data.is_cell_empty()
|
||||
}
|
||||
}
|
||||
|
||||
@ -352,98 +332,150 @@ impl<'a> TypeOptionCellExt<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cells<T>(&self) -> Vec<T> {
|
||||
let field_type = FieldType::from(self.field.field_type);
|
||||
match self.get_type_option_cell_data_handler(&field_type) {
|
||||
None => vec![],
|
||||
Some(_handler) => {
|
||||
todo!()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type_option_cell_data_handler(
|
||||
pub fn get_type_option_cell_data_handler_with_field_type(
|
||||
&self,
|
||||
field_type: &FieldType,
|
||||
field_type: FieldType,
|
||||
) -> Option<Box<dyn TypeOptionCellDataHandler>> {
|
||||
match field_type {
|
||||
FieldType::RichText => self
|
||||
.field
|
||||
.get_type_option::<RichTextTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
field_type,
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
}),
|
||||
FieldType::Number => self
|
||||
.field
|
||||
.get_type_option::<NumberTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
field_type,
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
}),
|
||||
FieldType::DateTime => self
|
||||
.field
|
||||
.get_type_option::<DateTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
field_type,
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
}),
|
||||
FieldType::LastEditedTime | FieldType::CreatedTime => self
|
||||
.field
|
||||
.get_type_option::<TimestampTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
field_type,
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
}),
|
||||
FieldType::SingleSelect => self
|
||||
.field
|
||||
.get_type_option::<SingleSelectTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
field_type,
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
}),
|
||||
FieldType::MultiSelect => self
|
||||
.field
|
||||
.get_type_option::<MultiSelectTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
field_type,
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
}),
|
||||
FieldType::Checkbox => self
|
||||
.field
|
||||
.get_type_option::<CheckboxTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
field_type,
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
}),
|
||||
FieldType::URL => {
|
||||
self
|
||||
.field
|
||||
.get_type_option::<URLTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
field_type,
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
})
|
||||
},
|
||||
FieldType::Checklist => self
|
||||
.field
|
||||
.get_type_option::<ChecklistTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
field_type,
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
}),
|
||||
FieldType::Relation => self
|
||||
.field
|
||||
.get_type_option::<RelationTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
field_type,
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type_option_cell_data_handler(&self) -> Option<Box<dyn TypeOptionCellDataHandler>> {
|
||||
let field_type = FieldType::from(self.field.field_type);
|
||||
self.get_type_option_cell_data_handler_with_field_type(field_type)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_type_option_cell_transformable(
|
||||
from_field_type: FieldType,
|
||||
to_field_type: FieldType,
|
||||
) -> bool {
|
||||
matches!(
|
||||
(from_field_type, to_field_type),
|
||||
(FieldType::Checkbox, FieldType::SingleSelect)
|
||||
| (FieldType::Checkbox, FieldType::MultiSelect)
|
||||
| (FieldType::SingleSelect, FieldType::MultiSelect)
|
||||
| (FieldType::MultiSelect, FieldType::SingleSelect)
|
||||
| (_, FieldType::RichText)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn transform_type_option(
|
||||
type_option_data: &TypeOptionData,
|
||||
old_field_type: FieldType,
|
||||
new_field_type: FieldType,
|
||||
old_type_option_data: Option<TypeOptionData>,
|
||||
old_field_type: FieldType,
|
||||
new_type_option_data: TypeOptionData,
|
||||
) -> TypeOptionData {
|
||||
let mut transform_handler = get_type_option_transform_handler(type_option_data, new_field_type);
|
||||
if let Some(old_type_option_data) = old_type_option_data {
|
||||
let mut transform_handler =
|
||||
get_type_option_transform_handler(new_type_option_data, new_field_type);
|
||||
transform_handler.transform(old_field_type, old_type_option_data);
|
||||
transform_handler.to_type_option_data()
|
||||
} else {
|
||||
new_type_option_data
|
||||
}
|
||||
transform_handler.to_type_option_data()
|
||||
}
|
||||
|
||||
/// A helper trait that used to erase the `Self` of `TypeOption` trait to make it become a Object-safe trait.
|
||||
@ -459,27 +491,25 @@ pub trait TypeOptionTransformHandler {
|
||||
|
||||
impl<T> TypeOptionTransformHandler for T
|
||||
where
|
||||
T: TypeOptionTransform + Into<TypeOptionData> + Clone,
|
||||
T: TypeOptionTransform + Clone,
|
||||
{
|
||||
fn transform(
|
||||
&mut self,
|
||||
old_type_option_field_type: FieldType,
|
||||
old_type_option_data: TypeOptionData,
|
||||
) {
|
||||
if self.transformable() {
|
||||
self.transform_type_option(old_type_option_field_type, old_type_option_data)
|
||||
}
|
||||
self.transform_type_option(old_type_option_field_type, old_type_option_data)
|
||||
}
|
||||
|
||||
fn to_type_option_data(&self) -> TypeOptionData {
|
||||
self.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_type_option_transform_handler(
|
||||
type_option_data: &TypeOptionData,
|
||||
type_option_data: TypeOptionData,
|
||||
field_type: FieldType,
|
||||
) -> Box<dyn TypeOptionTransformHandler> {
|
||||
let type_option_data = type_option_data.clone();
|
||||
match field_type {
|
||||
FieldType::RichText => {
|
||||
Box::new(RichTextTypeOption::from(type_option_data)) as Box<dyn TypeOptionTransformHandler>
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::entities::{FieldType, TextFilterPB, URLCellDataPB};
|
||||
use crate::entities::{TextFilterPB, URLCellDataPB};
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
|
||||
use crate::services::field::{
|
||||
TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionCellDataSerde,
|
||||
@ -7,7 +7,7 @@ use crate::services::field::{
|
||||
use crate::services::sort::SortCondition;
|
||||
|
||||
use collab::core::any_map::AnyMapExtension;
|
||||
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::rows::Cell;
|
||||
use fancy_regex::Regex;
|
||||
use flowy_error::FlowyResult;
|
||||
@ -61,16 +61,7 @@ impl TypeOptionCellDataSerde for URLTypeOption {
|
||||
}
|
||||
|
||||
impl CellDataDecoder for URLTypeOption {
|
||||
fn decode_cell(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
decoded_field_type: &FieldType,
|
||||
_field: &Field,
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
if !decoded_field_type.is_url() {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
self.parse_cell(cell)
|
||||
}
|
||||
|
||||
@ -78,11 +69,6 @@ impl CellDataDecoder for URLTypeOption {
|
||||
cell_data.data
|
||||
}
|
||||
|
||||
fn stringify_cell(&self, cell: &Cell) -> String {
|
||||
let cell_data = Self::CellData::from(cell);
|
||||
self.stringify_cell_data(cell_data)
|
||||
}
|
||||
|
||||
fn numeric_cell(&self, _cell: &Cell) -> Option<f64> {
|
||||
None
|
||||
}
|
||||
|
@ -528,9 +528,8 @@ fn apply_filter(
|
||||
return Some(false);
|
||||
}
|
||||
let cell = row.cells.get(field_id).cloned();
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
if let Some(handler) = TypeOptionCellExt::new(field, Some(cell_data_cache.clone()))
|
||||
.get_type_option_cell_data_handler(&field_type)
|
||||
.get_type_option_cell_data_handler()
|
||||
{
|
||||
Some(handler.handle_cell_filter(field, &cell.unwrap_or_default(), condition_and_content))
|
||||
} else {
|
||||
|
@ -3,8 +3,7 @@ use indexmap::IndexMap;
|
||||
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::stringify_cell_data;
|
||||
use crate::services::cell::stringify_cell;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum CSVFormat {
|
||||
@ -46,12 +45,9 @@ impl CSVExport {
|
||||
.iter()
|
||||
.map(|(field_id, field)| match row.cells.get(field_id) {
|
||||
None => "".to_string(),
|
||||
Some(cell) => {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
match style {
|
||||
CSVFormat::Original => stringify_cell_data(cell, &field_type, &field_type, field),
|
||||
CSVFormat::META => serde_json::to_string(cell).unwrap_or_else(|_| "".to_string()),
|
||||
}
|
||||
Some(cell) => match style {
|
||||
CSVFormat::Original => stringify_cell(cell, field),
|
||||
CSVFormat::META => serde_json::to_string(cell).unwrap_or_else(|_| "".to_string()),
|
||||
},
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -336,7 +336,6 @@ fn cmp_row(
|
||||
.as_ref()
|
||||
.map_or_else(|| right.cells.get(&sort.field_id), |cell| cell.1.as_ref()),
|
||||
field_rev,
|
||||
field_type,
|
||||
cell_data_cache,
|
||||
sort.condition,
|
||||
)
|
||||
@ -348,12 +347,11 @@ fn cmp_cell(
|
||||
left_cell: Option<&Cell>,
|
||||
right_cell: Option<&Cell>,
|
||||
field: &Field,
|
||||
field_type: FieldType,
|
||||
cell_data_cache: &CellCache,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
match TypeOptionCellExt::new(field, Some(cell_data_cache.clone()))
|
||||
.get_type_option_cell_data_handler(&field_type)
|
||||
.get_type_option_cell_data_handler()
|
||||
{
|
||||
None => default_order(),
|
||||
Some(handler) => handler.handle_cell_compare(left_cell, right_cell, field, sort_condition),
|
||||
|
@ -1,7 +1,7 @@
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
|
||||
use flowy_database2::entities::{CreateFieldParams, FieldChangesetParams, FieldType};
|
||||
use flowy_database2::services::cell::stringify_cell_data;
|
||||
use flowy_database2::services::cell::stringify_cell;
|
||||
|
||||
use crate::database::database_editor::DatabaseEditorTest;
|
||||
|
||||
@ -31,7 +31,6 @@ pub enum FieldScript {
|
||||
AssertCellContent {
|
||||
field_id: String,
|
||||
row_index: usize,
|
||||
from_field_type: FieldType,
|
||||
expected_content: String,
|
||||
},
|
||||
}
|
||||
@ -118,17 +117,15 @@ impl DatabaseFieldTest {
|
||||
FieldScript::AssertCellContent {
|
||||
field_id,
|
||||
row_index,
|
||||
from_field_type,
|
||||
expected_content,
|
||||
} => {
|
||||
let field = self.editor.get_field(&field_id).unwrap();
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
|
||||
let rows = self.editor.get_rows(&self.view_id()).await.unwrap();
|
||||
let row_detail = rows.get(row_index).unwrap();
|
||||
|
||||
let cell = row_detail.row.cells.get(&field_id).unwrap().clone();
|
||||
let content = stringify_cell_data(&cell, &from_field_type, &field_type, &field);
|
||||
let content = stringify_cell(&cell, &field);
|
||||
assert_eq!(content, expected_content);
|
||||
},
|
||||
}
|
||||
|
@ -155,8 +155,6 @@ async fn grid_switch_from_checkbox_to_select_option_test() {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
// the mock data of the checkbox with row_index one is "true"
|
||||
row_index: 1,
|
||||
// the from_field_type represents as the current field type
|
||||
from_field_type: FieldType::Checkbox,
|
||||
// The content of the checkbox should transform to the corresponding option name.
|
||||
expected_content: CHECK.to_string(),
|
||||
},
|
||||
@ -190,7 +188,6 @@ async fn grid_switch_from_multi_select_to_text_test() {
|
||||
let script_assert_field = vec![AssertCellContent {
|
||||
field_id: field_rev.id.clone(),
|
||||
row_index: 0,
|
||||
from_field_type: FieldType::MultiSelect,
|
||||
expected_content: format!(
|
||||
"{},{}",
|
||||
multi_select_type_option.first().unwrap().name,
|
||||
@ -218,13 +215,11 @@ async fn grid_switch_from_checkbox_to_text_test() {
|
||||
AssertCellContent {
|
||||
field_id: field_rev.id.clone(),
|
||||
row_index: 1,
|
||||
from_field_type: FieldType::Checkbox,
|
||||
expected_content: "Yes".to_string(),
|
||||
},
|
||||
AssertCellContent {
|
||||
field_id: field_rev.id.clone(),
|
||||
row_index: 2,
|
||||
from_field_type: FieldType::Checkbox,
|
||||
expected_content: "No".to_string(),
|
||||
},
|
||||
];
|
||||
@ -246,13 +241,11 @@ async fn grid_switch_from_date_to_text_test() {
|
||||
AssertCellContent {
|
||||
field_id: field.id.clone(),
|
||||
row_index: 2,
|
||||
from_field_type: FieldType::DateTime,
|
||||
expected_content: "2022/03/14".to_string(),
|
||||
},
|
||||
AssertCellContent {
|
||||
field_id: field.id.clone(),
|
||||
row_index: 3,
|
||||
from_field_type: FieldType::DateTime,
|
||||
expected_content: "2022/11/17".to_string(),
|
||||
},
|
||||
];
|
||||
@ -275,13 +268,11 @@ async fn grid_switch_from_number_to_text_test() {
|
||||
AssertCellContent {
|
||||
field_id: field.id.clone(),
|
||||
row_index: 0,
|
||||
from_field_type: FieldType::Number,
|
||||
expected_content: "$1".to_string(),
|
||||
},
|
||||
AssertCellContent {
|
||||
field_id: field.id.clone(),
|
||||
row_index: 4,
|
||||
from_field_type: FieldType::Number,
|
||||
expected_content: "".to_string(),
|
||||
},
|
||||
];
|
||||
@ -303,7 +294,6 @@ async fn grid_switch_from_checklist_to_text_test() {
|
||||
AssertCellContent {
|
||||
field_id: field_rev.id.clone(),
|
||||
row_index: 0,
|
||||
from_field_type: FieldType::Checklist,
|
||||
expected_content: "First thing".to_string(),
|
||||
},
|
||||
];
|
||||
|
@ -48,7 +48,7 @@ async fn according_to_text_contains_filter_test() {
|
||||
AssertCellContent {
|
||||
field_id: text_field.id,
|
||||
row_index: test.row_details.len() - 1,
|
||||
from_field_type: FieldType::RichText,
|
||||
|
||||
expected_content: "sample".to_string(),
|
||||
},
|
||||
];
|
||||
@ -195,7 +195,7 @@ async fn according_to_checkbox_is_checked_filter_test() {
|
||||
AssertCellContent {
|
||||
field_id: checkbox_field.id,
|
||||
row_index: 3,
|
||||
from_field_type: FieldType::Checkbox,
|
||||
|
||||
expected_content: "Yes".to_string(),
|
||||
},
|
||||
];
|
||||
@ -242,7 +242,7 @@ async fn according_to_date_time_is_filter_test() {
|
||||
AssertCellContent {
|
||||
field_id: datetime_field.id,
|
||||
row_index: 0,
|
||||
from_field_type: FieldType::DateTime,
|
||||
|
||||
expected_content: "2024/03/15".to_string(),
|
||||
},
|
||||
];
|
||||
@ -331,7 +331,7 @@ async fn according_to_select_option_is_filter_test() {
|
||||
AssertCellContent {
|
||||
field_id: multi_select_field.id,
|
||||
row_index: 1,
|
||||
from_field_type: FieldType::MultiSelect,
|
||||
|
||||
expected_content: stringified_expected,
|
||||
},
|
||||
];
|
||||
@ -380,7 +380,7 @@ async fn according_to_select_option_contains_filter_test() {
|
||||
AssertCellContent {
|
||||
field_id: multi_select_field.id,
|
||||
row_index: 5,
|
||||
from_field_type: FieldType::MultiSelect,
|
||||
|
||||
expected_content: stringified_expected,
|
||||
},
|
||||
];
|
||||
@ -424,7 +424,7 @@ async fn according_to_select_option_is_not_empty_filter_test() {
|
||||
AssertCellContent {
|
||||
field_id: multi_select_field.id,
|
||||
row_index: 5,
|
||||
from_field_type: FieldType::MultiSelect,
|
||||
|
||||
expected_content: stringified_expected,
|
||||
},
|
||||
];
|
||||
|
@ -35,7 +35,7 @@ async fn row_data_payload_with_empty_hashmap_test() {
|
||||
AssertCellContent {
|
||||
field_id: text_field.id,
|
||||
row_index: test.row_details.len(),
|
||||
from_field_type: FieldType::RichText,
|
||||
|
||||
expected_content: "".to_string(),
|
||||
},
|
||||
];
|
||||
@ -70,7 +70,7 @@ async fn row_data_payload_with_unknown_field_id_test() {
|
||||
AssertCellContent {
|
||||
field_id: text_field.id.clone(),
|
||||
row_index: test.row_details.len(),
|
||||
from_field_type: FieldType::RichText,
|
||||
|
||||
expected_content: "".to_string(),
|
||||
},
|
||||
AssertCellExistence {
|
||||
@ -107,7 +107,7 @@ async fn row_data_payload_with_empty_string_text_data_test() {
|
||||
AssertCellContent {
|
||||
field_id: text_field.id,
|
||||
row_index: test.row_details.len(),
|
||||
from_field_type: FieldType::RichText,
|
||||
|
||||
expected_content: cell_data.to_string(),
|
||||
},
|
||||
];
|
||||
@ -139,7 +139,7 @@ async fn row_data_payload_with_text_data_test() {
|
||||
AssertCellContent {
|
||||
field_id: text_field.id.clone(),
|
||||
row_index: test.row_details.len(),
|
||||
from_field_type: FieldType::RichText,
|
||||
|
||||
expected_content: cell_data.to_string(),
|
||||
},
|
||||
];
|
||||
@ -180,7 +180,7 @@ async fn row_data_payload_with_multi_text_data_test() {
|
||||
AssertCellContent {
|
||||
field_id: text_field.id,
|
||||
row_index: test.row_details.len(),
|
||||
from_field_type: FieldType::RichText,
|
||||
|
||||
expected_content: text_cell_data.to_string(),
|
||||
},
|
||||
AssertCellExistence {
|
||||
@ -191,7 +191,7 @@ async fn row_data_payload_with_multi_text_data_test() {
|
||||
AssertCellContent {
|
||||
field_id: number_field.id,
|
||||
row_index: test.row_details.len(),
|
||||
from_field_type: FieldType::RichText,
|
||||
|
||||
expected_content: "$1,234".to_string(),
|
||||
},
|
||||
AssertCellExistence {
|
||||
@ -202,7 +202,7 @@ async fn row_data_payload_with_multi_text_data_test() {
|
||||
AssertCellContent {
|
||||
field_id: url_field.id,
|
||||
row_index: test.row_details.len(),
|
||||
from_field_type: FieldType::RichText,
|
||||
|
||||
expected_content: url_cell_data.to_string(),
|
||||
},
|
||||
];
|
||||
@ -234,7 +234,7 @@ async fn row_data_payload_with_date_time_test() {
|
||||
AssertCellContent {
|
||||
field_id: date_field.id.clone(),
|
||||
row_index: test.row_details.len(),
|
||||
from_field_type: FieldType::RichText,
|
||||
|
||||
expected_content: "2024/03/15".to_string(),
|
||||
},
|
||||
];
|
||||
@ -296,7 +296,7 @@ async fn row_data_payload_with_checkbox_test() {
|
||||
AssertCellContent {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
row_index: test.row_details.len(),
|
||||
from_field_type: FieldType::Checkbox,
|
||||
|
||||
expected_content: cell_data.to_string(),
|
||||
},
|
||||
];
|
||||
@ -340,7 +340,7 @@ async fn row_data_payload_with_select_option_test() {
|
||||
AssertCellContent {
|
||||
field_id: multi_select_field.id.clone(),
|
||||
row_index: test.row_details.len(),
|
||||
from_field_type: FieldType::MultiSelect,
|
||||
|
||||
expected_content: stringified_cell_data,
|
||||
},
|
||||
];
|
||||
|
@ -1,8 +1,8 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::time::Duration;
|
||||
|
||||
use flowy_database2::entities::{CreateRowPayloadPB, FieldType, FilterDataPB, InsertFilterPB};
|
||||
use flowy_database2::services::cell::stringify_cell_data;
|
||||
use flowy_database2::entities::{CreateRowPayloadPB, FilterDataPB, InsertFilterPB};
|
||||
use flowy_database2::services::cell::stringify_cell;
|
||||
use flowy_database2::services::field::{SelectOptionIds, SELECTION_IDS_SEPARATOR};
|
||||
|
||||
use crate::database::database_editor::DatabaseEditorTest;
|
||||
@ -24,7 +24,6 @@ pub enum PreFillRowCellTestScript {
|
||||
AssertCellContent {
|
||||
field_id: String,
|
||||
row_index: usize,
|
||||
from_field_type: FieldType,
|
||||
expected_content: String,
|
||||
},
|
||||
AssertSelectOptionCellStrict {
|
||||
@ -105,11 +104,9 @@ impl DatabasePreFillRowCellTest {
|
||||
PreFillRowCellTestScript::AssertCellContent {
|
||||
field_id,
|
||||
row_index,
|
||||
from_field_type,
|
||||
expected_content,
|
||||
} => {
|
||||
let field = self.editor.get_field(&field_id).unwrap();
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
|
||||
let rows = self.editor.get_rows(&self.view_id).await.unwrap();
|
||||
let row_detail = rows.get(row_index).unwrap();
|
||||
@ -120,7 +117,7 @@ impl DatabasePreFillRowCellTest {
|
||||
.get(&field_id)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let content = stringify_cell_data(&cell, &from_field_type, &field_type, &field);
|
||||
let content = stringify_cell(&cell, &field);
|
||||
assert_eq!(content, expected_content);
|
||||
},
|
||||
PreFillRowCellTestScript::AssertSelectOptionCellStrict {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use flowy_database2::entities::FieldType;
|
||||
use flowy_database2::services::cell::stringify_cell_data;
|
||||
use flowy_database2::services::cell::stringify_cell;
|
||||
use flowy_database2::services::field::CHECK;
|
||||
use flowy_database2::services::share::csv::CSVFormat;
|
||||
|
||||
@ -67,7 +67,7 @@ async fn export_and_then_import_meta_csv_test() {
|
||||
for (index, row_detail) in rows.iter().enumerate() {
|
||||
if let Some(cell) = row_detail.row.cells.get(&field.id) {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
let s = stringify_cell_data(cell, &field_type, &field_type, &field);
|
||||
let s = stringify_cell(cell, &field);
|
||||
match &field_type {
|
||||
FieldType::RichText => {
|
||||
if index == 0 {
|
||||
@ -141,7 +141,7 @@ async fn history_database_import_test() {
|
||||
for (index, row_detail) in rows.iter().enumerate() {
|
||||
if let Some(cell) = row_detail.row.cells.get(&field.id) {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
let s = stringify_cell_data(cell, &field_type, &field_type, &field);
|
||||
let s = stringify_cell(cell, &field);
|
||||
match &field_type {
|
||||
FieldType::RichText => {
|
||||
if index == 0 {
|
||||
|
@ -8,9 +8,9 @@ use futures::stream::StreamExt;
|
||||
use tokio::sync::broadcast::Receiver;
|
||||
|
||||
use flowy_database2::entities::{
|
||||
CreateRowPayloadPB, DeleteSortPayloadPB, FieldType, ReorderSortPayloadPB, UpdateSortPayloadPB,
|
||||
CreateRowPayloadPB, DeleteSortPayloadPB, ReorderSortPayloadPB, UpdateSortPayloadPB,
|
||||
};
|
||||
use flowy_database2::services::cell::stringify_cell_data;
|
||||
use flowy_database2::services::cell::stringify_cell;
|
||||
use flowy_database2::services::database_view::DatabaseViewChanged;
|
||||
use flowy_database2::services::sort::SortCondition;
|
||||
|
||||
@ -119,10 +119,9 @@ impl DatabaseSortTest {
|
||||
let mut cells = vec![];
|
||||
let rows = self.editor.get_rows(&self.view_id).await.unwrap();
|
||||
let field = self.editor.get_field(&field_id).unwrap();
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
for row_detail in rows {
|
||||
if let Some(cell) = row_detail.row.cells.get(&field_id) {
|
||||
let content = stringify_cell_data(cell, &field_type, &field_type, &field);
|
||||
let content = stringify_cell(cell, &field);
|
||||
cells.push(content);
|
||||
} else {
|
||||
cells.push("".to_string());
|
||||
|
Loading…
Reference in New Issue
Block a user