mirror of
				https://github.com/AppFlowy-IO/AppFlowy.git
				synced 2025-10-31 10:03:18 +00:00 
			
		
		
		
	 f28c5d849c
			
		
	
	
		f28c5d849c
		
			
		
	
	
	
	
		
			
			* chore: Add _ClearDateFieldButton * chore: Add new DateCellCalendarEvent to clear date * chore: Add grid_field_clearDate translation token * refactor: _ClearDateFieldButton -> _ClearDateButton * fix: Fix dart(unused_element) in _ClearDateButton * feat: Handle clearDate * Revert "feat: Handle clearDate" This reverts commit fd145ef8e9589f3b4aa1a16e5d72c15b6d0b53c4. * feat: Handle clearDate * feat: Add clear_flag field * feat: check clear_flag to clear date * chore: update exisitng tests * fix: add mssing import * refactor: remove check for timeFormatError * fix: fix margin on _ClearDateButton * refactor: _ClearDateButton -> ClearDateButton * test: add clear date backend unit test * test: add clear date integration test * test: add clear date to edit time cell test * chore: mark ClearDateButton visible for testing * feat: close date editor popover on clear date * style: make AppFlowyPopover taller * fix: trim dateStr in _dateStrFromCellData * fix: don't dismiss CellEditor after clearDate
		
			
				
	
	
		
			426 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			426 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use std::collections::HashMap;
 | |
| use std::sync::Arc;
 | |
| 
 | |
| use collab_database::database::{gen_database_view_id, timestamp};
 | |
| use collab_database::fields::Field;
 | |
| use collab_database::rows::{CreateRowParams, RowDetail, RowId};
 | |
| use strum::EnumCount;
 | |
| 
 | |
| use flowy_database2::entities::{FieldType, FilterPB, RowMetaPB, SelectOptionPB};
 | |
| use flowy_database2::services::cell::{CellBuilder, ToCellChangeset};
 | |
| use flowy_database2::services::database::DatabaseEditor;
 | |
| use flowy_database2::services::field::checklist_type_option::{
 | |
|   ChecklistCellChangeset, ChecklistTypeOption,
 | |
| };
 | |
| use flowy_database2::services::field::{
 | |
|   CheckboxTypeOption, DateCellChangeset, MultiSelectTypeOption, SelectOption,
 | |
|   SelectOptionCellChangeset, SingleSelectTypeOption,
 | |
| };
 | |
| use flowy_database2::services::share::csv::{CSVFormat, ImportResult};
 | |
| use flowy_error::FlowyResult;
 | |
| use flowy_test::folder_event::ViewTest;
 | |
| use flowy_test::FlowyCoreTest;
 | |
| 
 | |
| use crate::database::mock_data::{
 | |
|   make_no_date_test_grid, make_test_board, make_test_calendar, make_test_grid,
 | |
| };
 | |
| 
 | |
| pub struct DatabaseEditorTest {
 | |
|   pub sdk: FlowyCoreTest,
 | |
|   pub app_id: String,
 | |
|   pub view_id: String,
 | |
|   pub editor: Arc<DatabaseEditor>,
 | |
|   pub fields: Vec<Arc<Field>>,
 | |
|   pub row_details: Vec<Arc<RowDetail>>,
 | |
|   pub field_count: usize,
 | |
|   pub row_by_row_id: HashMap<String, RowMetaPB>,
 | |
| }
 | |
| 
 | |
| impl DatabaseEditorTest {
 | |
|   pub async fn new_grid() -> Self {
 | |
|     let sdk = FlowyCoreTest::new();
 | |
|     let _ = sdk.init_user().await;
 | |
| 
 | |
|     let params = make_test_grid();
 | |
|     let view_test = ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await;
 | |
|     Self::new(sdk, view_test).await
 | |
|   }
 | |
| 
 | |
|   pub async fn new_no_date_grid() -> Self {
 | |
|     let sdk = FlowyCoreTest::new();
 | |
|     let _ = sdk.init_user().await;
 | |
| 
 | |
|     let params = make_no_date_test_grid();
 | |
|     let view_test = ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await;
 | |
|     Self::new(sdk, view_test).await
 | |
|   }
 | |
| 
 | |
|   pub async fn new_board() -> Self {
 | |
|     let sdk = FlowyCoreTest::new();
 | |
|     let _ = sdk.init_user().await;
 | |
| 
 | |
|     let params = make_test_board();
 | |
|     let view_test = ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await;
 | |
|     Self::new(sdk, view_test).await
 | |
|   }
 | |
| 
 | |
|   pub async fn new_calendar() -> Self {
 | |
|     let sdk = FlowyCoreTest::new();
 | |
|     let _ = sdk.init_user().await;
 | |
| 
 | |
|     let params = make_test_calendar();
 | |
|     let view_test = ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await;
 | |
|     Self::new(sdk, view_test).await
 | |
|   }
 | |
| 
 | |
|   pub async fn new(sdk: FlowyCoreTest, test: ViewTest) -> Self {
 | |
|     let editor = sdk
 | |
|       .database_manager
 | |
|       .get_database_with_view_id(&test.child_view.id)
 | |
|       .await
 | |
|       .unwrap();
 | |
|     let fields = editor
 | |
|       .get_fields(&test.child_view.id, None)
 | |
|       .into_iter()
 | |
|       .map(Arc::new)
 | |
|       .collect();
 | |
|     let rows = editor
 | |
|       .get_rows(&test.child_view.id)
 | |
|       .await
 | |
|       .unwrap()
 | |
|       .into_iter()
 | |
|       .collect();
 | |
| 
 | |
|     let view_id = test.child_view.id;
 | |
|     let app_id = test.parent_view.id;
 | |
|     Self {
 | |
|       sdk,
 | |
|       app_id,
 | |
|       view_id,
 | |
|       editor,
 | |
|       fields,
 | |
|       row_details: rows,
 | |
|       field_count: FieldType::COUNT,
 | |
|       row_by_row_id: HashMap::default(),
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   pub async fn database_filters(&self) -> Vec<FilterPB> {
 | |
|     self.editor.get_all_filters(&self.view_id).await.items
 | |
|   }
 | |
| 
 | |
|   pub async fn get_rows(&self) -> Vec<Arc<RowDetail>> {
 | |
|     self.editor.get_rows(&self.view_id).await.unwrap()
 | |
|   }
 | |
| 
 | |
|   pub fn get_field(&self, field_id: &str, field_type: FieldType) -> Field {
 | |
|     self
 | |
|       .editor
 | |
|       .get_fields(&self.view_id, None)
 | |
|       .into_iter()
 | |
|       .filter(|field| {
 | |
|         let t_field_type = FieldType::from(field.field_type);
 | |
|         field.id == field_id && t_field_type == field_type
 | |
|       })
 | |
|       .collect::<Vec<_>>()
 | |
|       .pop()
 | |
|       .unwrap()
 | |
|   }
 | |
| 
 | |
|   /// returns the first `Field` in the build-in test grid.
 | |
|   /// Not support duplicate `FieldType` in test grid yet.
 | |
|   pub fn get_first_field(&self, field_type: FieldType) -> Field {
 | |
|     self
 | |
|       .editor
 | |
|       .get_fields(&self.view_id, None)
 | |
|       .into_iter()
 | |
|       .filter(|field| {
 | |
|         let t_field_type = FieldType::from(field.field_type);
 | |
|         t_field_type == field_type
 | |
|       })
 | |
|       .collect::<Vec<_>>()
 | |
|       .pop()
 | |
|       .unwrap()
 | |
|   }
 | |
| 
 | |
|   pub fn get_fields(&self) -> Vec<Field> {
 | |
|     self.editor.get_fields(&self.view_id, None)
 | |
|   }
 | |
| 
 | |
|   pub fn get_multi_select_type_option(&self, field_id: &str) -> Vec<SelectOption> {
 | |
|     let field_type = FieldType::MultiSelect;
 | |
|     let field = self.get_field(field_id, field_type.clone());
 | |
|     let type_option = field
 | |
|       .get_type_option::<MultiSelectTypeOption>(field_type)
 | |
|       .unwrap();
 | |
|     type_option.options
 | |
|   }
 | |
| 
 | |
|   pub fn get_single_select_type_option(&self, field_id: &str) -> SingleSelectTypeOption {
 | |
|     let field_type = FieldType::SingleSelect;
 | |
|     let field = self.get_field(field_id, field_type.clone());
 | |
|     field
 | |
|       .get_type_option::<SingleSelectTypeOption>(field_type)
 | |
|       .unwrap()
 | |
|   }
 | |
| 
 | |
|   #[allow(dead_code)]
 | |
|   pub fn get_checklist_type_option(&self, field_id: &str) -> ChecklistTypeOption {
 | |
|     let field_type = FieldType::Checklist;
 | |
|     let field = self.get_field(field_id, field_type.clone());
 | |
|     field
 | |
|       .get_type_option::<ChecklistTypeOption>(field_type)
 | |
|       .unwrap()
 | |
|   }
 | |
| 
 | |
|   #[allow(dead_code)]
 | |
|   pub fn get_checkbox_type_option(&self, field_id: &str) -> CheckboxTypeOption {
 | |
|     let field_type = FieldType::Checkbox;
 | |
|     let field = self.get_field(field_id, field_type.clone());
 | |
|     field
 | |
|       .get_type_option::<CheckboxTypeOption>(field_type)
 | |
|       .unwrap()
 | |
|   }
 | |
| 
 | |
|   pub async fn update_cell<T: ToCellChangeset>(
 | |
|     &mut self,
 | |
|     field_id: &str,
 | |
|     row_id: RowId,
 | |
|     cell_changeset: T,
 | |
|   ) -> FlowyResult<()> {
 | |
|     let field = self
 | |
|       .editor
 | |
|       .get_fields(&self.view_id, None)
 | |
|       .into_iter()
 | |
|       .find(|field| field.id == field_id)
 | |
|       .unwrap();
 | |
| 
 | |
|     self
 | |
|       .editor
 | |
|       .update_cell_with_changeset(&self.view_id, row_id, &field.id, cell_changeset)
 | |
|       .await
 | |
|   }
 | |
| 
 | |
|   pub(crate) async fn update_text_cell(&mut self, row_id: RowId, content: &str) -> FlowyResult<()> {
 | |
|     let field = self
 | |
|       .editor
 | |
|       .get_fields(&self.view_id, None)
 | |
|       .iter()
 | |
|       .find(|field| {
 | |
|         let field_type = FieldType::from(field.field_type);
 | |
|         field_type == FieldType::RichText
 | |
|       })
 | |
|       .unwrap()
 | |
|       .clone();
 | |
| 
 | |
|     self
 | |
|       .update_cell(&field.id, row_id, content.to_string())
 | |
|       .await
 | |
|   }
 | |
| 
 | |
|   pub(crate) async fn set_checklist_cell(
 | |
|     &mut self,
 | |
|     row_id: RowId,
 | |
|     f: Box<dyn FnOnce(Vec<SelectOptionPB>) -> Vec<String>>,
 | |
|   ) -> FlowyResult<()> {
 | |
|     let field = self
 | |
|       .editor
 | |
|       .get_fields(&self.view_id, None)
 | |
|       .iter()
 | |
|       .find(|field| {
 | |
|         let field_type = FieldType::from(field.field_type);
 | |
|         field_type == FieldType::Checklist
 | |
|       })
 | |
|       .unwrap()
 | |
|       .clone();
 | |
|     let options = self
 | |
|       .editor
 | |
|       .get_checklist_option(row_id.clone(), &field.id)
 | |
|       .await
 | |
|       .options;
 | |
|     let cell_changeset = ChecklistCellChangeset {
 | |
|       selected_option_ids: f(options),
 | |
|       ..Default::default()
 | |
|     };
 | |
|     self
 | |
|       .editor
 | |
|       .set_checklist_options(&self.view_id, row_id, &field.id, cell_changeset)
 | |
|       .await
 | |
|   }
 | |
| 
 | |
|   pub(crate) async fn update_single_select_cell(
 | |
|     &mut self,
 | |
|     row_id: RowId,
 | |
|     option_id: &str,
 | |
|   ) -> FlowyResult<()> {
 | |
|     let field = self
 | |
|       .editor
 | |
|       .get_fields(&self.view_id, None)
 | |
|       .iter()
 | |
|       .find(|field| {
 | |
|         let field_type = FieldType::from(field.field_type);
 | |
|         field_type == FieldType::SingleSelect
 | |
|       })
 | |
|       .unwrap()
 | |
|       .clone();
 | |
| 
 | |
|     let cell_changeset = SelectOptionCellChangeset::from_insert_option_id(option_id);
 | |
|     self.update_cell(&field.id, row_id, cell_changeset).await
 | |
|   }
 | |
| 
 | |
|   pub async fn import(&self, s: String, format: CSVFormat) -> ImportResult {
 | |
|     self
 | |
|       .sdk
 | |
|       .database_manager
 | |
|       .import_csv(gen_database_view_id(), s, format)
 | |
|       .await
 | |
|       .unwrap()
 | |
|   }
 | |
| 
 | |
|   pub async fn get_database(&self, database_id: &str) -> Option<Arc<DatabaseEditor>> {
 | |
|     self
 | |
|       .sdk
 | |
|       .database_manager
 | |
|       .get_database(database_id)
 | |
|       .await
 | |
|       .ok()
 | |
|   }
 | |
| }
 | |
| 
 | |
| pub struct TestRowBuilder<'a> {
 | |
|   row_id: RowId,
 | |
|   fields: &'a [Field],
 | |
|   cell_build: CellBuilder<'a>,
 | |
| }
 | |
| 
 | |
| impl<'a> TestRowBuilder<'a> {
 | |
|   pub fn new(row_id: RowId, fields: &'a [Field]) -> Self {
 | |
|     let cell_build = CellBuilder::with_cells(Default::default(), fields);
 | |
|     Self {
 | |
|       row_id,
 | |
|       fields,
 | |
|       cell_build,
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   pub fn insert_text_cell(&mut self, data: &str) -> String {
 | |
|     let text_field = self.field_with_type(&FieldType::RichText);
 | |
|     self
 | |
|       .cell_build
 | |
|       .insert_text_cell(&text_field.id, data.to_string());
 | |
| 
 | |
|     text_field.id.clone()
 | |
|   }
 | |
| 
 | |
|   pub fn insert_number_cell(&mut self, data: &str) -> String {
 | |
|     let number_field = self.field_with_type(&FieldType::Number);
 | |
|     self
 | |
|       .cell_build
 | |
|       .insert_text_cell(&number_field.id, data.to_string());
 | |
|     number_field.id.clone()
 | |
|   }
 | |
| 
 | |
|   pub fn insert_date_cell(
 | |
|     &mut self,
 | |
|     data: &str,
 | |
|     time: Option<String>,
 | |
|     include_time: Option<bool>,
 | |
|     field_type: &FieldType,
 | |
|   ) -> String {
 | |
|     let value = serde_json::to_string(&DateCellChangeset {
 | |
|       date: Some(data.to_string()),
 | |
|       time,
 | |
|       include_time,
 | |
|       clear_flag: None,
 | |
|     })
 | |
|     .unwrap();
 | |
|     let date_field = self.field_with_type(field_type);
 | |
|     self.cell_build.insert_text_cell(&date_field.id, value);
 | |
|     date_field.id.clone()
 | |
|   }
 | |
| 
 | |
|   pub fn insert_checkbox_cell(&mut self, data: &str) -> String {
 | |
|     let checkbox_field = self.field_with_type(&FieldType::Checkbox);
 | |
|     self
 | |
|       .cell_build
 | |
|       .insert_text_cell(&checkbox_field.id, data.to_string());
 | |
| 
 | |
|     checkbox_field.id.clone()
 | |
|   }
 | |
| 
 | |
|   pub fn insert_url_cell(&mut self, content: &str) -> String {
 | |
|     let url_field = self.field_with_type(&FieldType::URL);
 | |
|     self
 | |
|       .cell_build
 | |
|       .insert_url_cell(&url_field.id, content.to_string());
 | |
|     url_field.id.clone()
 | |
|   }
 | |
| 
 | |
|   pub fn insert_single_select_cell<F>(&mut self, f: F) -> String
 | |
|   where
 | |
|     F: Fn(Vec<SelectOption>) -> SelectOption,
 | |
|   {
 | |
|     let single_select_field = self.field_with_type(&FieldType::SingleSelect);
 | |
|     let type_option = single_select_field
 | |
|       .get_type_option::<SingleSelectTypeOption>(FieldType::SingleSelect)
 | |
|       .unwrap();
 | |
|     let option = f(type_option.options);
 | |
|     self
 | |
|       .cell_build
 | |
|       .insert_select_option_cell(&single_select_field.id, vec![option.id]);
 | |
| 
 | |
|     single_select_field.id.clone()
 | |
|   }
 | |
| 
 | |
|   pub fn insert_multi_select_cell<F>(&mut self, f: F) -> String
 | |
|   where
 | |
|     F: Fn(Vec<SelectOption>) -> Vec<SelectOption>,
 | |
|   {
 | |
|     let multi_select_field = self.field_with_type(&FieldType::MultiSelect);
 | |
|     let type_option = multi_select_field
 | |
|       .get_type_option::<MultiSelectTypeOption>(FieldType::MultiSelect)
 | |
|       .unwrap();
 | |
|     let options = f(type_option.options);
 | |
|     let ops_ids = options
 | |
|       .iter()
 | |
|       .map(|option| option.id.clone())
 | |
|       .collect::<Vec<_>>();
 | |
|     self
 | |
|       .cell_build
 | |
|       .insert_select_option_cell(&multi_select_field.id, ops_ids);
 | |
| 
 | |
|     multi_select_field.id.clone()
 | |
|   }
 | |
| 
 | |
|   pub fn insert_checklist_cell(&mut self, option_names: Vec<String>) -> String {
 | |
|     let checklist_field = self.field_with_type(&FieldType::Checklist);
 | |
|     self
 | |
|       .cell_build
 | |
|       .insert_checklist_cell(&checklist_field.id, option_names);
 | |
|     checklist_field.id.clone()
 | |
|   }
 | |
| 
 | |
|   pub fn field_with_type(&self, field_type: &FieldType) -> Field {
 | |
|     self
 | |
|       .fields
 | |
|       .iter()
 | |
|       .find(|field| {
 | |
|         let t_field_type = FieldType::from(field.field_type);
 | |
|         &t_field_type == field_type
 | |
|       })
 | |
|       .unwrap()
 | |
|       .clone()
 | |
|   }
 | |
| 
 | |
|   pub fn build(self) -> CreateRowParams {
 | |
|     CreateRowParams {
 | |
|       id: self.row_id,
 | |
|       cells: self.cell_build.build(),
 | |
|       height: 60,
 | |
|       visibility: true,
 | |
|       prev_row_id: None,
 | |
|       timestamp: timestamp(),
 | |
|     }
 | |
|   }
 | |
| }
 |