mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: use commas to select a tag in multi-select grid cells (#1158)
* fix: comma to select tags * chore: support passing multiple option ids * chore: add more unit tests for single select and multi select * chore: move to select multiple options using a single payload * chore: do not unselect the option if insert option ids contain that option Co-authored-by: appflowy <annie@appflowy.io>
This commit is contained in:
parent
d80a67bdda
commit
295b887cf1
@ -57,6 +57,10 @@ class SelectOptionCellEditorBloc
|
||||
trySelectOption: (_TrySelectOption value) {
|
||||
_trySelectOption(value.optionName, emit);
|
||||
},
|
||||
selectMultipleOptions: (_SelectMultipleOptions value) {
|
||||
_selectMultipleOptions(value.optionNames);
|
||||
_filterOption(value.remainder, emit);
|
||||
},
|
||||
filterOption: (_SelectOptionFilter value) {
|
||||
_filterOption(value.optionName, emit);
|
||||
},
|
||||
@ -97,14 +101,14 @@ class SelectOptionCellEditorBloc
|
||||
final hasSelected = state.selectedOptions
|
||||
.firstWhereOrNull((option) => option.id == optionId);
|
||||
if (hasSelected != null) {
|
||||
_selectOptionService.unSelect(optionId: optionId);
|
||||
_selectOptionService.unSelect(optionIds: [optionId]);
|
||||
} else {
|
||||
_selectOptionService.select(optionId: optionId);
|
||||
_selectOptionService.select(optionIds: [optionId]);
|
||||
}
|
||||
}
|
||||
|
||||
void _trySelectOption(
|
||||
String optionName, Emitter<SelectOptionEditorState> emit) async {
|
||||
String optionName, Emitter<SelectOptionEditorState> emit) {
|
||||
SelectOptionPB? matchingOption;
|
||||
bool optionExistsButSelected = false;
|
||||
|
||||
@ -126,13 +130,20 @@ class SelectOptionCellEditorBloc
|
||||
|
||||
// if there is an unselected matching option, select it
|
||||
if (matchingOption != null) {
|
||||
_selectOptionService.select(optionId: matchingOption.id);
|
||||
_selectOptionService.select(optionIds: [matchingOption.id]);
|
||||
}
|
||||
|
||||
// clear the filter
|
||||
emit(state.copyWith(filter: none()));
|
||||
}
|
||||
|
||||
void _selectMultipleOptions(List<String> optionNames) {
|
||||
final optionIds = state.options
|
||||
.where((e) => optionNames.contains(e.name))
|
||||
.map((e) => e.id);
|
||||
_selectOptionService.select(optionIds: optionIds);
|
||||
}
|
||||
|
||||
void _filterOption(String optionName, Emitter<SelectOptionEditorState> emit) {
|
||||
final _MakeOptionResult result =
|
||||
_makeOptions(Some(optionName), state.allOptions);
|
||||
@ -222,6 +233,8 @@ class SelectOptionEditorEvent with _$SelectOptionEditorEvent {
|
||||
_SelectOptionFilter;
|
||||
const factory SelectOptionEditorEvent.trySelectOption(String optionName) =
|
||||
_TrySelectOption;
|
||||
const factory SelectOptionEditorEvent.selectMultipleOptions(
|
||||
List<String> optionNames, String remainder) = _SelectMultipleOptions;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -26,7 +26,7 @@ class SelectOptionService {
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId;
|
||||
final payload = SelectOptionChangesetPayloadPB.create()
|
||||
..insertOption = option
|
||||
..insertOptions.add(option)
|
||||
..cellIdentifier = cellIdentifier;
|
||||
return GridEventUpdateSelectOption(payload).send();
|
||||
},
|
||||
@ -40,7 +40,7 @@ class SelectOptionService {
|
||||
required SelectOptionPB option,
|
||||
}) {
|
||||
final payload = SelectOptionChangesetPayloadPB.create()
|
||||
..updateOption = option
|
||||
..updateOptions.add(option)
|
||||
..cellIdentifier = _cellIdentifier();
|
||||
return GridEventUpdateSelectOption(payload).send();
|
||||
}
|
||||
@ -49,7 +49,7 @@ class SelectOptionService {
|
||||
required SelectOptionPB option,
|
||||
}) {
|
||||
final payload = SelectOptionChangesetPayloadPB.create()
|
||||
..deleteOption = option
|
||||
..deleteOptions.add(option)
|
||||
..cellIdentifier = _cellIdentifier();
|
||||
|
||||
return GridEventUpdateSelectOption(payload).send();
|
||||
@ -64,17 +64,19 @@ class SelectOptionService {
|
||||
return GridEventGetSelectOptionCellData(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<void, FlowyError>> select({required String optionId}) {
|
||||
Future<Either<void, FlowyError>> select(
|
||||
{required Iterable<String> optionIds}) {
|
||||
final payload = SelectOptionCellChangesetPayloadPB.create()
|
||||
..cellIdentifier = _cellIdentifier()
|
||||
..insertOptionId = optionId;
|
||||
..insertOptionIds.addAll(optionIds);
|
||||
return GridEventUpdateSelectOptionCell(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<void, FlowyError>> unSelect({required String optionId}) {
|
||||
Future<Either<void, FlowyError>> unSelect(
|
||||
{required Iterable<String> optionIds}) {
|
||||
final payload = SelectOptionCellChangesetPayloadPB.create()
|
||||
..cellIdentifier = _cellIdentifier()
|
||||
..deleteOptionId = optionId;
|
||||
..deleteOptionIds.addAll(optionIds);
|
||||
return GridEventUpdateSelectOptionCell(payload).send();
|
||||
}
|
||||
|
||||
|
@ -149,6 +149,7 @@ class _TextField extends StatelessWidget {
|
||||
distanceToText: _editorPanelWidth * 0.7,
|
||||
maxLength: 30,
|
||||
tagController: _tagController,
|
||||
textSeparators: const [','],
|
||||
onClick: () => popoverMutex.close(),
|
||||
newText: (text) {
|
||||
context
|
||||
@ -160,6 +161,14 @@ class _TextField extends StatelessWidget {
|
||||
.read<SelectOptionCellEditorBloc>()
|
||||
.add(SelectOptionEditorEvent.trySelectOption(tagName));
|
||||
},
|
||||
onPaste: (tagNames, remainder) {
|
||||
context
|
||||
.read<SelectOptionCellEditorBloc>()
|
||||
.add(SelectOptionEditorEvent.selectMultipleOptions(
|
||||
tagNames,
|
||||
remainder,
|
||||
));
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -17,9 +17,11 @@ class SelectOptionTextField extends StatefulWidget {
|
||||
final List<SelectOptionPB> options;
|
||||
final LinkedHashMap<String, SelectOptionPB> selectedOptionMap;
|
||||
final double distanceToText;
|
||||
final List<String> textSeparators;
|
||||
|
||||
final Function(String) onSubmitted;
|
||||
final Function(String) newText;
|
||||
final Function(List<String>, String) onPaste;
|
||||
final VoidCallback? onClick;
|
||||
final int? maxLength;
|
||||
|
||||
@ -29,7 +31,9 @@ class SelectOptionTextField extends StatefulWidget {
|
||||
required this.distanceToText,
|
||||
required this.tagController,
|
||||
required this.onSubmitted,
|
||||
required this.onPaste,
|
||||
required this.newText,
|
||||
required this.textSeparators,
|
||||
this.onClick,
|
||||
this.maxLength,
|
||||
TextEditingController? textController,
|
||||
@ -65,7 +69,7 @@ class _SelectOptionTextFieldState extends State<SelectOptionTextField> {
|
||||
textfieldTagsController: widget.tagController,
|
||||
initialTags: widget.selectedOptionMap.keys.toList(),
|
||||
focusNode: focusNode,
|
||||
textSeparators: const [','],
|
||||
textSeparators: widget.textSeparators,
|
||||
inputfieldBuilder: (
|
||||
BuildContext context,
|
||||
editController,
|
||||
@ -83,7 +87,7 @@ class _SelectOptionTextFieldState extends State<SelectOptionTextField> {
|
||||
if (onChanged != null) {
|
||||
onChanged(text);
|
||||
}
|
||||
widget.newText(text);
|
||||
_newText(text, editController);
|
||||
},
|
||||
onSubmitted: (text) {
|
||||
if (onSubmitted != null) {
|
||||
@ -121,6 +125,40 @@ class _SelectOptionTextFieldState extends State<SelectOptionTextField> {
|
||||
);
|
||||
}
|
||||
|
||||
void _newText(String text, TextEditingController editingController) {
|
||||
if (text.isEmpty) {
|
||||
widget.newText('');
|
||||
return;
|
||||
}
|
||||
|
||||
final trimmedText = text.trim();
|
||||
List<String> splits = [];
|
||||
String currentString = '';
|
||||
|
||||
// split the string into tokens
|
||||
for (final char in trimmedText.split('')) {
|
||||
if (!widget.textSeparators.contains(char)) {
|
||||
currentString += char;
|
||||
continue;
|
||||
}
|
||||
if (currentString.isNotEmpty) {
|
||||
splits.add(currentString);
|
||||
}
|
||||
currentString = '';
|
||||
}
|
||||
// add the remainder (might be '')
|
||||
splits.add(currentString);
|
||||
|
||||
final submittedOptions =
|
||||
splits.sublist(0, splits.length - 1).map((e) => e.trim()).toList();
|
||||
|
||||
final remainder = splits.elementAt(splits.length - 1).trimLeft();
|
||||
editingController.text = remainder;
|
||||
editingController.selection =
|
||||
TextSelection.collapsed(offset: controller.text.length);
|
||||
widget.onPaste(submittedOptions, remainder);
|
||||
}
|
||||
|
||||
Widget? _renderTags(BuildContext context, ScrollController sc) {
|
||||
if (widget.selectedOptionMap.isEmpty) {
|
||||
return null;
|
||||
|
@ -341,19 +341,19 @@ pub(crate) async fn update_select_option_handler(
|
||||
let mut cell_content_changeset = None;
|
||||
let mut is_changed = None;
|
||||
|
||||
if let Some(option) = changeset.insert_option {
|
||||
cell_content_changeset = Some(SelectOptionCellChangeset::from_insert(&option.id).to_str());
|
||||
for option in changeset.insert_options {
|
||||
cell_content_changeset = Some(SelectOptionCellChangeset::from_insert_option_id(&option.id).to_str());
|
||||
type_option.insert_option(option);
|
||||
is_changed = Some(());
|
||||
}
|
||||
|
||||
if let Some(option) = changeset.update_option {
|
||||
for option in changeset.update_options {
|
||||
type_option.insert_option(option);
|
||||
is_changed = Some(());
|
||||
}
|
||||
|
||||
if let Some(option) = changeset.delete_option {
|
||||
cell_content_changeset = Some(SelectOptionCellChangeset::from_delete(&option.id).to_str());
|
||||
for option in changeset.delete_options {
|
||||
cell_content_changeset = Some(SelectOptionCellChangeset::from_delete_option_id(&option.id).to_str());
|
||||
type_option.delete_option(option);
|
||||
is_changed = Some(());
|
||||
}
|
||||
|
@ -244,14 +244,14 @@ pub fn insert_date_cell(timestamp: i64, field_rev: &FieldRevision) -> CellRevisi
|
||||
CellRevision::new(data)
|
||||
}
|
||||
|
||||
pub fn insert_select_option_cell(option_id: String, field_rev: &FieldRevision) -> CellRevision {
|
||||
let cell_data = SelectOptionCellChangeset::from_insert(&option_id).to_str();
|
||||
pub fn insert_select_option_cell(option_ids: Vec<String>, field_rev: &FieldRevision) -> CellRevision {
|
||||
let cell_data = SelectOptionCellChangeset::from_insert_options(option_ids).to_str();
|
||||
let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap();
|
||||
CellRevision::new(data)
|
||||
}
|
||||
|
||||
pub fn delete_select_option_cell(option_id: String, field_rev: &FieldRevision) -> CellRevision {
|
||||
let cell_data = SelectOptionCellChangeset::from_delete(&option_id).to_str();
|
||||
pub fn delete_select_option_cell(option_ids: Vec<String>, field_rev: &FieldRevision) -> CellRevision {
|
||||
let cell_data = SelectOptionCellChangeset::from_delete_options(option_ids).to_str();
|
||||
let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap();
|
||||
CellRevision::new(data)
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOper
|
||||
use crate::services::field::type_options::util::get_cell_data;
|
||||
use crate::services::field::{
|
||||
make_selected_select_options, BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB,
|
||||
SelectOptionIds, SelectOptionOperation, SelectOptionPB, TypeOptionBuilder, SELECTION_IDS_SEPARATOR,
|
||||
SelectOptionIds, SelectOptionOperation, SelectOptionPB, TypeOptionBuilder,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
@ -61,29 +61,32 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for MultiSele
|
||||
cell_rev: Option<CellRevision>,
|
||||
) -> Result<String, FlowyError> {
|
||||
let content_changeset = changeset.try_into_inner()?;
|
||||
|
||||
let insert_option_ids = content_changeset
|
||||
.insert_option_ids
|
||||
.into_iter()
|
||||
.filter(|insert_option_id| self.options.iter().any(|option| &option.id == insert_option_id))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let new_cell_data: String;
|
||||
match cell_rev {
|
||||
None => {
|
||||
new_cell_data = content_changeset.insert_option_id.unwrap_or_else(|| "".to_owned());
|
||||
new_cell_data = SelectOptionIds::from(insert_option_ids).to_string();
|
||||
}
|
||||
Some(cell_rev) => {
|
||||
let cell_data = get_cell_data(&cell_rev);
|
||||
let mut select_ids: SelectOptionIds = cell_data.into();
|
||||
if let Some(insert_option_id) = content_changeset.insert_option_id {
|
||||
tracing::trace!("Insert multi select option: {}", &insert_option_id);
|
||||
if select_ids.contains(&insert_option_id) {
|
||||
select_ids.retain(|id| id != &insert_option_id);
|
||||
} else {
|
||||
for insert_option_id in insert_option_ids {
|
||||
if !select_ids.contains(&insert_option_id) {
|
||||
select_ids.push(insert_option_id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(delete_option_id) = content_changeset.delete_option_id {
|
||||
tracing::trace!("Delete multi select option: {}", &delete_option_id);
|
||||
for delete_option_id in content_changeset.delete_option_ids {
|
||||
select_ids.retain(|id| id != &delete_option_id);
|
||||
}
|
||||
|
||||
new_cell_data = select_ids.join(SELECTION_IDS_SEPARATOR);
|
||||
new_cell_data = select_ids.to_string();
|
||||
tracing::trace!("Multi select cell data: {}", &new_cell_data);
|
||||
}
|
||||
}
|
||||
@ -114,22 +117,86 @@ impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::CellDataOperation;
|
||||
use crate::services::field::type_options::selection_type_option::*;
|
||||
use crate::services::field::FieldBuilder;
|
||||
use crate::services::field::{MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB};
|
||||
use flowy_grid_data_model::revision::FieldRevision;
|
||||
|
||||
#[test]
|
||||
fn multi_select_test() {
|
||||
let google_option = SelectOptionPB::new("Google");
|
||||
let facebook_option = SelectOptionPB::new("Facebook");
|
||||
let twitter_option = SelectOptionPB::new("Twitter");
|
||||
fn multi_select_insert_multi_option_test() {
|
||||
let google = SelectOptionPB::new("Google");
|
||||
let facebook = SelectOptionPB::new("Facebook");
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.add_option(google_option.clone())
|
||||
.add_option(facebook_option.clone())
|
||||
.add_option(twitter_option);
|
||||
.add_option(google.clone())
|
||||
.add_option(facebook.clone());
|
||||
|
||||
let field_rev = FieldBuilder::new(multi_select).name("Platform").build();
|
||||
let type_option = MultiSelectTypeOptionPB::from(&field_rev);
|
||||
let option_ids = vec![google.id, facebook.id];
|
||||
let data = SelectOptionCellChangeset::from_insert_options(option_ids.clone()).to_str();
|
||||
let select_option_ids: SelectOptionIds = type_option.apply_changeset(data.into(), None).unwrap().into();
|
||||
|
||||
assert_eq!(&*select_option_ids, &option_ids);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_select_unselect_multi_option_test() {
|
||||
let google = SelectOptionPB::new("Google");
|
||||
let facebook = SelectOptionPB::new("Facebook");
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.add_option(google.clone())
|
||||
.add_option(facebook.clone());
|
||||
|
||||
let field_rev = FieldBuilder::new(multi_select).name("Platform").build();
|
||||
let type_option = MultiSelectTypeOptionPB::from(&field_rev);
|
||||
let option_ids = vec![google.id, facebook.id];
|
||||
|
||||
// insert
|
||||
let data = SelectOptionCellChangeset::from_insert_options(option_ids.clone()).to_str();
|
||||
let select_option_ids: SelectOptionIds = type_option.apply_changeset(data.into(), None).unwrap().into();
|
||||
assert_eq!(&*select_option_ids, &option_ids);
|
||||
|
||||
// delete
|
||||
let data = SelectOptionCellChangeset::from_delete_options(option_ids).to_str();
|
||||
let select_option_ids: SelectOptionIds = type_option.apply_changeset(data.into(), None).unwrap().into();
|
||||
assert!(select_option_ids.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_select_insert_single_option_test() {
|
||||
let google = SelectOptionPB::new("Google");
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default().add_option(google.clone());
|
||||
|
||||
let field_rev = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
.visibility(true)
|
||||
.build();
|
||||
|
||||
let type_option = MultiSelectTypeOptionPB::from(&field_rev);
|
||||
let data = SelectOptionCellChangeset::from_insert_option_id(&google.id).to_str();
|
||||
let cell_option_ids = type_option.apply_changeset(data.into(), None).unwrap();
|
||||
assert_eq!(cell_option_ids, google.id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_select_insert_non_exist_option_test() {
|
||||
let google = SelectOptionPB::new("Google");
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default();
|
||||
let field_rev = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
.visibility(true)
|
||||
.build();
|
||||
|
||||
let type_option = MultiSelectTypeOptionPB::from(&field_rev);
|
||||
let data = SelectOptionCellChangeset::from_insert_option_id(&google.id).to_str();
|
||||
let cell_option_ids = type_option.apply_changeset(data.into(), None).unwrap();
|
||||
assert!(cell_option_ids.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_select_insert_invalid_option_id_test() {
|
||||
let google = SelectOptionPB::new("Google");
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default().add_option(google);
|
||||
|
||||
let field_rev = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
@ -138,51 +205,23 @@ mod tests {
|
||||
|
||||
let type_option = MultiSelectTypeOptionPB::from(&field_rev);
|
||||
|
||||
let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
|
||||
let data = SelectOptionCellChangeset::from_insert(&option_ids).to_str();
|
||||
let cell_data = type_option.apply_changeset(data.into(), None).unwrap();
|
||||
assert_multi_select_options(
|
||||
cell_data,
|
||||
&type_option,
|
||||
&field_rev,
|
||||
vec![google_option.clone(), facebook_option],
|
||||
);
|
||||
// empty option id string
|
||||
let data = SelectOptionCellChangeset::from_insert_option_id("").to_str();
|
||||
let cell_option_ids = type_option.apply_changeset(data.into(), None).unwrap();
|
||||
assert_eq!(cell_option_ids, "");
|
||||
|
||||
let data = SelectOptionCellChangeset::from_insert(&google_option.id).to_str();
|
||||
let cell_data = type_option.apply_changeset(data.into(), None).unwrap();
|
||||
assert_multi_select_options(cell_data, &type_option, &field_rev, vec![google_option]);
|
||||
let data = SelectOptionCellChangeset::from_insert_option_id("123,456").to_str();
|
||||
let cell_option_ids = type_option.apply_changeset(data.into(), None).unwrap();
|
||||
assert_eq!(cell_option_ids, "");
|
||||
}
|
||||
|
||||
// Invalid option id
|
||||
let cell_data = type_option
|
||||
.apply_changeset(SelectOptionCellChangeset::from_insert("").to_str().into(), None)
|
||||
.unwrap();
|
||||
assert_multi_select_options(cell_data, &type_option, &field_rev, vec![]);
|
||||
#[test]
|
||||
fn multi_select_invalid_changeset_data_test() {
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default();
|
||||
let field_rev = FieldBuilder::new(multi_select).name("Platform").build();
|
||||
let type_option = MultiSelectTypeOptionPB::from(&field_rev);
|
||||
|
||||
// Invalid option id
|
||||
let cell_data = type_option
|
||||
.apply_changeset(SelectOptionCellChangeset::from_insert("123,456").to_str().into(), None)
|
||||
.unwrap();
|
||||
assert_multi_select_options(cell_data, &type_option, &field_rev, vec![]);
|
||||
|
||||
// Invalid changeset
|
||||
// The type of the changeset should be SelectOptionCellChangeset
|
||||
assert!(type_option.apply_changeset("123".to_owned().into(), None).is_err());
|
||||
}
|
||||
|
||||
fn assert_multi_select_options(
|
||||
cell_data: String,
|
||||
type_option: &MultiSelectTypeOptionPB,
|
||||
field_rev: &FieldRevision,
|
||||
expected: Vec<SelectOptionPB>,
|
||||
) {
|
||||
let field_type: FieldType = field_rev.ty.into();
|
||||
assert_eq!(
|
||||
expected,
|
||||
type_option
|
||||
.decode_cell_data(cell_data.into(), &field_type, field_rev)
|
||||
.unwrap()
|
||||
.parser::<SelectOptionCellDataParser>()
|
||||
.unwrap()
|
||||
.select_options,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,8 @@ pub fn make_selected_select_options(
|
||||
}
|
||||
|
||||
pub trait SelectOptionOperation: TypeOptionDataFormat + Send + Sync {
|
||||
/// Insert the `SelectOptionPB` into corresponding type option.
|
||||
/// Replace the old value if the option already exists in the option list.
|
||||
fn insert_option(&mut self, new_option: SelectOptionPB) {
|
||||
let options = self.mut_options();
|
||||
if let Some(index) = options
|
||||
@ -170,6 +172,11 @@ pub fn select_option_color_from_index(index: usize) -> SelectOptionColorPB {
|
||||
}
|
||||
}
|
||||
|
||||
/// List of select option ids
|
||||
///
|
||||
/// Calls [to_string] will return a string consists list of ids,
|
||||
/// placing a commas separator between each
|
||||
///
|
||||
#[derive(Default)]
|
||||
pub struct SelectOptionIds(Vec<String>);
|
||||
|
||||
@ -193,6 +200,10 @@ impl FromCellString for SelectOptionIds {
|
||||
|
||||
impl std::convert::From<String> for SelectOptionIds {
|
||||
fn from(s: String) -> Self {
|
||||
if s.is_empty() {
|
||||
return Self(vec![]);
|
||||
}
|
||||
|
||||
let ids = s
|
||||
.split(SELECTION_IDS_SEPARATOR)
|
||||
.map(|id| id.to_string())
|
||||
@ -201,7 +212,16 @@ impl std::convert::From<String> for SelectOptionIds {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<String>> for SelectOptionIds {
|
||||
fn from(ids: Vec<String>) -> Self {
|
||||
let ids = ids.into_iter().filter(|id| !id.is_empty()).collect::<Vec<String>>();
|
||||
Self(ids)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for SelectOptionIds {
|
||||
/// Returns a string that consists list of ids, placing a commas
|
||||
/// separator between each
|
||||
fn to_string(&self) -> String {
|
||||
self.0.join(SELECTION_IDS_SEPARATOR)
|
||||
}
|
||||
@ -254,24 +274,24 @@ pub struct SelectOptionCellChangesetPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub cell_identifier: GridCellIdPB,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub insert_option_id: Option<String>,
|
||||
#[pb(index = 2)]
|
||||
pub insert_option_ids: Vec<String>,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub delete_option_id: Option<String>,
|
||||
#[pb(index = 3)]
|
||||
pub delete_option_ids: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct SelectOptionCellChangesetParams {
|
||||
pub cell_identifier: GridCellIdParams,
|
||||
pub insert_option_id: Option<String>,
|
||||
pub delete_option_id: Option<String>,
|
||||
pub insert_option_ids: Vec<String>,
|
||||
pub delete_option_ids: Vec<String>,
|
||||
}
|
||||
|
||||
impl std::convert::From<SelectOptionCellChangesetParams> for CellChangesetPB {
|
||||
fn from(params: SelectOptionCellChangesetParams) -> Self {
|
||||
let changeset = SelectOptionCellChangeset {
|
||||
insert_option_id: params.insert_option_id,
|
||||
delete_option_id: params.delete_option_id,
|
||||
insert_option_ids: params.insert_option_ids,
|
||||
delete_option_ids: params.delete_option_ids,
|
||||
};
|
||||
let content = serde_json::to_string(&changeset).unwrap();
|
||||
CellChangesetPB {
|
||||
@ -288,36 +308,42 @@ impl TryInto<SelectOptionCellChangesetParams> for SelectOptionCellChangesetPaylo
|
||||
|
||||
fn try_into(self) -> Result<SelectOptionCellChangesetParams, Self::Error> {
|
||||
let cell_identifier: GridCellIdParams = self.cell_identifier.try_into()?;
|
||||
let insert_option_id = match self.insert_option_id {
|
||||
None => None,
|
||||
Some(insert_option_id) => Some(
|
||||
NotEmptyStr::parse(insert_option_id)
|
||||
.map_err(|_| ErrorCode::OptionIdIsEmpty)?
|
||||
.0,
|
||||
),
|
||||
};
|
||||
let insert_option_ids = self
|
||||
.insert_option_ids
|
||||
.into_iter()
|
||||
.flat_map(|option_id| match NotEmptyStr::parse(option_id) {
|
||||
Ok(option_id) => Some(option_id.0),
|
||||
Err(_) => {
|
||||
tracing::error!("The insert option id should not be empty");
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let delete_option_id = match self.delete_option_id {
|
||||
None => None,
|
||||
Some(delete_option_id) => Some(
|
||||
NotEmptyStr::parse(delete_option_id)
|
||||
.map_err(|_| ErrorCode::OptionIdIsEmpty)?
|
||||
.0,
|
||||
),
|
||||
};
|
||||
let delete_option_ids = self
|
||||
.delete_option_ids
|
||||
.into_iter()
|
||||
.flat_map(|option_id| match NotEmptyStr::parse(option_id) {
|
||||
Ok(option_id) => Some(option_id.0),
|
||||
Err(_) => {
|
||||
tracing::error!("The deleted option id should not be empty");
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
Ok(SelectOptionCellChangesetParams {
|
||||
cell_identifier,
|
||||
insert_option_id,
|
||||
delete_option_id,
|
||||
insert_option_ids,
|
||||
delete_option_ids,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct SelectOptionCellChangeset {
|
||||
pub insert_option_id: Option<String>,
|
||||
pub delete_option_id: Option<String>,
|
||||
pub insert_option_ids: Vec<String>,
|
||||
pub delete_option_ids: Vec<String>,
|
||||
}
|
||||
|
||||
impl FromCellChangeset for SelectOptionCellChangeset {
|
||||
@ -330,17 +356,31 @@ impl FromCellChangeset for SelectOptionCellChangeset {
|
||||
}
|
||||
|
||||
impl SelectOptionCellChangeset {
|
||||
pub fn from_insert(option_id: &str) -> Self {
|
||||
pub fn from_insert_option_id(option_id: &str) -> Self {
|
||||
SelectOptionCellChangeset {
|
||||
insert_option_id: Some(option_id.to_string()),
|
||||
delete_option_id: None,
|
||||
insert_option_ids: vec![option_id.to_string()],
|
||||
delete_option_ids: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_delete(option_id: &str) -> Self {
|
||||
pub fn from_insert_options(option_ids: Vec<String>) -> Self {
|
||||
SelectOptionCellChangeset {
|
||||
insert_option_id: None,
|
||||
delete_option_id: Some(option_id.to_string()),
|
||||
insert_option_ids: option_ids,
|
||||
delete_option_ids: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_delete_option_id(option_id: &str) -> Self {
|
||||
SelectOptionCellChangeset {
|
||||
insert_option_ids: vec![],
|
||||
delete_option_ids: vec![option_id.to_string()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_delete_options(option_ids: Vec<String>) -> Self {
|
||||
SelectOptionCellChangeset {
|
||||
insert_option_ids: vec![],
|
||||
delete_option_ids: option_ids,
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,21 +409,21 @@ pub struct SelectOptionChangesetPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub cell_identifier: GridCellIdPB,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub insert_option: Option<SelectOptionPB>,
|
||||
#[pb(index = 2)]
|
||||
pub insert_options: Vec<SelectOptionPB>,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub update_option: Option<SelectOptionPB>,
|
||||
#[pb(index = 3)]
|
||||
pub update_options: Vec<SelectOptionPB>,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub delete_option: Option<SelectOptionPB>,
|
||||
#[pb(index = 4)]
|
||||
pub delete_options: Vec<SelectOptionPB>,
|
||||
}
|
||||
|
||||
pub struct SelectOptionChangeset {
|
||||
pub cell_identifier: GridCellIdParams,
|
||||
pub insert_option: Option<SelectOptionPB>,
|
||||
pub update_option: Option<SelectOptionPB>,
|
||||
pub delete_option: Option<SelectOptionPB>,
|
||||
pub insert_options: Vec<SelectOptionPB>,
|
||||
pub update_options: Vec<SelectOptionPB>,
|
||||
pub delete_options: Vec<SelectOptionPB>,
|
||||
}
|
||||
|
||||
impl TryInto<SelectOptionChangeset> for SelectOptionChangesetPayloadPB {
|
||||
@ -393,9 +433,9 @@ impl TryInto<SelectOptionChangeset> for SelectOptionChangesetPayloadPB {
|
||||
let cell_identifier = self.cell_identifier.try_into()?;
|
||||
Ok(SelectOptionChangeset {
|
||||
cell_identifier,
|
||||
insert_option: self.insert_option,
|
||||
update_option: self.update_option,
|
||||
delete_option: self.delete_option,
|
||||
insert_options: self.insert_options,
|
||||
update_options: self.update_options,
|
||||
delete_options: self.delete_options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -62,12 +62,24 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for SingleSel
|
||||
changeset: CellDataChangeset<SelectOptionCellChangeset>,
|
||||
_cell_rev: Option<CellRevision>,
|
||||
) -> Result<String, FlowyError> {
|
||||
let select_option_changeset = changeset.try_into_inner()?;
|
||||
let content_changeset = changeset.try_into_inner()?;
|
||||
let new_cell_data: String;
|
||||
if let Some(insert_option_id) = select_option_changeset.insert_option_id {
|
||||
new_cell_data = insert_option_id;
|
||||
} else {
|
||||
|
||||
let mut insert_option_ids = content_changeset
|
||||
.insert_option_ids
|
||||
.into_iter()
|
||||
.filter(|insert_option_id| self.options.iter().any(|option| &option.id == insert_option_id))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
// In single select, the insert_option_ids should only contain one select option id.
|
||||
// Sometimes, the insert_option_ids may contain list of option ids. For example,
|
||||
// copy/paste a ids string.
|
||||
if insert_option_ids.is_empty() {
|
||||
new_cell_data = "".to_string()
|
||||
} else {
|
||||
// Just take the first select option
|
||||
let _ = insert_option_ids.drain(1..);
|
||||
new_cell_data = insert_option_ids.pop().unwrap();
|
||||
}
|
||||
|
||||
Ok(new_cell_data)
|
||||
@ -98,71 +110,83 @@ impl TypeOptionBuilder for SingleSelectTypeOptionBuilder {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::CellDataOperation;
|
||||
|
||||
use crate::services::field::type_options::*;
|
||||
use crate::services::field::FieldBuilder;
|
||||
use flowy_grid_data_model::revision::FieldRevision;
|
||||
|
||||
#[test]
|
||||
fn single_select_test() {
|
||||
let google_option = SelectOptionPB::new("Google");
|
||||
let facebook_option = SelectOptionPB::new("Facebook");
|
||||
let twitter_option = SelectOptionPB::new("Twitter");
|
||||
fn single_select_insert_multi_option_test() {
|
||||
let google = SelectOptionPB::new("Google");
|
||||
let facebook = SelectOptionPB::new("Facebook");
|
||||
let single_select = SingleSelectTypeOptionBuilder::default()
|
||||
.add_option(google_option.clone())
|
||||
.add_option(facebook_option.clone())
|
||||
.add_option(twitter_option);
|
||||
.add_option(google.clone())
|
||||
.add_option(facebook.clone());
|
||||
|
||||
let field_rev = FieldBuilder::new(single_select)
|
||||
.name("Platform")
|
||||
.visibility(true)
|
||||
.build();
|
||||
let field_rev = FieldBuilder::new(single_select).name("Platform").build();
|
||||
let type_option = SingleSelectTypeOptionPB::from(&field_rev);
|
||||
let option_ids = vec![google.id.clone(), facebook.id];
|
||||
let data = SelectOptionCellChangeset::from_insert_options(option_ids).to_str();
|
||||
let select_option_ids: SelectOptionIds = type_option.apply_changeset(data.into(), None).unwrap().into();
|
||||
|
||||
assert_eq!(&*select_option_ids, &vec![google.id]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_select_unselect_multi_option_test() {
|
||||
let google = SelectOptionPB::new("Google");
|
||||
let facebook = SelectOptionPB::new("Facebook");
|
||||
let single_select = SingleSelectTypeOptionBuilder::default()
|
||||
.add_option(google.clone())
|
||||
.add_option(facebook.clone());
|
||||
|
||||
let field_rev = FieldBuilder::new(single_select).name("Platform").build();
|
||||
let type_option = SingleSelectTypeOptionPB::from(&field_rev);
|
||||
let option_ids = vec![google.id.clone(), facebook.id];
|
||||
|
||||
// insert
|
||||
let data = SelectOptionCellChangeset::from_insert_options(option_ids.clone()).to_str();
|
||||
let select_option_ids: SelectOptionIds = type_option.apply_changeset(data.into(), None).unwrap().into();
|
||||
assert_eq!(&*select_option_ids, &vec![google.id]);
|
||||
|
||||
// delete
|
||||
let data = SelectOptionCellChangeset::from_delete_options(option_ids).to_str();
|
||||
let select_option_ids: SelectOptionIds = type_option.apply_changeset(data.into(), None).unwrap().into();
|
||||
assert!(select_option_ids.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_select_insert_non_exist_option_test() {
|
||||
let google = SelectOptionPB::new("Google");
|
||||
let single_select = SingleSelectTypeOptionBuilder::default();
|
||||
let field_rev = FieldBuilder::new(single_select).name("Platform").build();
|
||||
let type_option = SingleSelectTypeOptionPB::from(&field_rev);
|
||||
|
||||
let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR);
|
||||
let data = SelectOptionCellChangeset::from_insert(&option_ids).to_str();
|
||||
let cell_data = type_option.apply_changeset(data.into(), None).unwrap();
|
||||
assert_single_select_options(cell_data, &type_option, &field_rev, vec![google_option.clone()]);
|
||||
let option_ids = vec![google.id];
|
||||
let data = SelectOptionCellChangeset::from_insert_options(option_ids).to_str();
|
||||
let cell_option_ids = type_option.apply_changeset(data.into(), None).unwrap();
|
||||
|
||||
let data = SelectOptionCellChangeset::from_insert(&google_option.id).to_str();
|
||||
let cell_data = type_option.apply_changeset(data.into(), None).unwrap();
|
||||
assert_single_select_options(cell_data, &type_option, &field_rev, vec![google_option]);
|
||||
assert!(cell_option_ids.is_empty());
|
||||
}
|
||||
|
||||
// Invalid option id
|
||||
let cell_data = type_option
|
||||
.apply_changeset(SelectOptionCellChangeset::from_insert("").to_str().into(), None)
|
||||
.unwrap();
|
||||
assert_single_select_options(cell_data, &type_option, &field_rev, vec![]);
|
||||
#[test]
|
||||
fn single_select_insert_invalid_option_id_test() {
|
||||
let single_select = SingleSelectTypeOptionBuilder::default();
|
||||
let field_rev = FieldBuilder::new(single_select).name("Platform").build();
|
||||
let type_option = SingleSelectTypeOptionPB::from(&field_rev);
|
||||
|
||||
// Invalid option id
|
||||
let cell_data = type_option
|
||||
.apply_changeset(SelectOptionCellChangeset::from_insert("123").to_str().into(), None)
|
||||
.unwrap();
|
||||
let data = SelectOptionCellChangeset::from_insert_option_id("").to_str();
|
||||
let cell_option_ids = type_option.apply_changeset(data.into(), None).unwrap();
|
||||
assert_eq!(cell_option_ids, "");
|
||||
}
|
||||
|
||||
assert_single_select_options(cell_data, &type_option, &field_rev, vec![]);
|
||||
#[test]
|
||||
fn single_select_invalid_changeset_data_test() {
|
||||
let single_select = SingleSelectTypeOptionBuilder::default();
|
||||
let field_rev = FieldBuilder::new(single_select).name("Platform").build();
|
||||
let type_option = SingleSelectTypeOptionPB::from(&field_rev);
|
||||
|
||||
// Invalid changeset
|
||||
// The type of the changeset should be SelectOptionCellChangeset
|
||||
assert!(type_option.apply_changeset("123".to_owned().into(), None).is_err());
|
||||
}
|
||||
|
||||
fn assert_single_select_options(
|
||||
cell_data: String,
|
||||
type_option: &SingleSelectTypeOptionPB,
|
||||
field_rev: &FieldRevision,
|
||||
expected: Vec<SelectOptionPB>,
|
||||
) {
|
||||
let field_type: FieldType = field_rev.ty.into();
|
||||
assert_eq!(
|
||||
expected,
|
||||
type_option
|
||||
.decode_cell_data(cell_data.into(), &field_type, field_rev)
|
||||
.unwrap()
|
||||
.parser::<SelectOptionCellDataParser>()
|
||||
.unwrap()
|
||||
.select_options,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ impl GroupController for MultiSelectGroupController {
|
||||
match self.group_ctx.get_group(group_id) {
|
||||
None => tracing::warn!("Can not find the group: {}", group_id),
|
||||
Some((_, group)) => {
|
||||
let cell_rev = insert_select_option_cell(group.id.clone(), field_rev);
|
||||
let cell_rev = insert_select_option_cell(vec![group.id.clone()], field_rev);
|
||||
row_rev.cells.insert(field_rev.id.clone(), cell_rev);
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ impl GroupController for SingleSelectGroupController {
|
||||
match group {
|
||||
None => {}
|
||||
Some(group) => {
|
||||
let cell_rev = insert_select_option_cell(group.id.clone(), field_rev);
|
||||
let cell_rev = insert_select_option_cell(vec![group.id.clone()], field_rev);
|
||||
row_rev.cells.insert(field_rev.id.clone(), cell_rev);
|
||||
}
|
||||
}
|
||||
|
@ -134,11 +134,11 @@ pub fn make_inserted_cell_rev(group_id: &str, field_rev: &FieldRevision) -> Opti
|
||||
let field_type: FieldType = field_rev.ty.into();
|
||||
match field_type {
|
||||
FieldType::SingleSelect => {
|
||||
let cell_rev = insert_select_option_cell(group_id.to_owned(), field_rev);
|
||||
let cell_rev = insert_select_option_cell(vec![group_id.to_owned()], field_rev);
|
||||
Some(cell_rev)
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
let cell_rev = insert_select_option_cell(group_id.to_owned(), field_rev);
|
||||
let cell_rev = insert_select_option_cell(vec![group_id.to_owned()], field_rev);
|
||||
Some(cell_rev)
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
|
@ -92,13 +92,13 @@ impl<'a> RowRevisionBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_select_option_cell(&mut self, field_id: &str, data: String) {
|
||||
pub fn insert_select_option_cell(&mut self, field_id: &str, option_ids: Vec<String>) {
|
||||
match self.field_rev_map.get(&field_id.to_owned()) {
|
||||
None => tracing::warn!("Can't find the select option field with id: {}", field_id),
|
||||
Some(field_rev) => {
|
||||
self.payload
|
||||
.cell_by_field_id
|
||||
.insert(field_id.to_owned(), insert_select_option_cell(data, field_rev));
|
||||
.insert(field_id.to_owned(), insert_select_option_cell(option_ids, field_rev));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ pub fn make_default_board() -> BuildGridContext {
|
||||
|
||||
for i in 0..3 {
|
||||
let mut row_builder = RowRevisionBuilder::new(grid_builder.block_id(), grid_builder.field_revs());
|
||||
row_builder.insert_select_option_cell(&single_select_field_id, to_do_option.id.clone());
|
||||
row_builder.insert_select_option_cell(&single_select_field_id, vec![to_do_option.id.clone()]);
|
||||
let data = format!("Card {}", i + 1);
|
||||
row_builder.insert_text_cell(&text_field_id, data);
|
||||
let row = row_builder.build();
|
||||
@ -116,23 +116,23 @@ pub fn make_default_board_2() -> BuildGridContext {
|
||||
|
||||
for i in 0..3 {
|
||||
let mut row_builder = RowRevisionBuilder::new(grid_builder.block_id(), grid_builder.field_revs());
|
||||
row_builder.insert_select_option_cell(&single_select_field_id, to_do_option.id.clone());
|
||||
row_builder.insert_select_option_cell(&single_select_field_id, vec![to_do_option.id.clone()]);
|
||||
match i {
|
||||
0 => {
|
||||
row_builder.insert_text_cell(&text_field_id, "Update AppFlowy Website".to_string());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, work_option.id.clone());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, vec![work_option.id.clone()]);
|
||||
}
|
||||
1 => {
|
||||
row_builder.insert_text_cell(&text_field_id, "Learn French".to_string());
|
||||
let mut options = SelectOptionIds::new();
|
||||
options.push(fun_option.id.clone());
|
||||
options.push(travel_option.id.clone());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, options.to_string());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, vec![options.to_string()]);
|
||||
}
|
||||
|
||||
2 => {
|
||||
row_builder.insert_text_cell(&text_field_id, "Exercise 4x/week".to_string());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, fun_option.id.clone());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, vec![fun_option.id.clone()]);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -142,15 +142,15 @@ pub fn make_default_board_2() -> BuildGridContext {
|
||||
|
||||
for i in 0..3 {
|
||||
let mut row_builder = RowRevisionBuilder::new(grid_builder.block_id(), grid_builder.field_revs());
|
||||
row_builder.insert_select_option_cell(&single_select_field_id, doing_option.id.clone());
|
||||
row_builder.insert_select_option_cell(&single_select_field_id, vec![doing_option.id.clone()]);
|
||||
match i {
|
||||
0 => {
|
||||
row_builder.insert_text_cell(&text_field_id, "Learn how to swim".to_string());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, fun_option.id.clone());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, vec![fun_option.id.clone()]);
|
||||
}
|
||||
1 => {
|
||||
row_builder.insert_text_cell(&text_field_id, "Meditate 10 mins each day".to_string());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, health_option.id.clone());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, vec![health_option.id.clone()]);
|
||||
}
|
||||
|
||||
2 => {
|
||||
@ -158,7 +158,7 @@ pub fn make_default_board_2() -> BuildGridContext {
|
||||
let mut options = SelectOptionIds::new();
|
||||
options.push(fun_option.id.clone());
|
||||
options.push(work_option.id.clone());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, options.to_string());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, vec![options.to_string()]);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -168,18 +168,18 @@ pub fn make_default_board_2() -> BuildGridContext {
|
||||
|
||||
for i in 0..2 {
|
||||
let mut row_builder = RowRevisionBuilder::new(grid_builder.block_id(), grid_builder.field_revs());
|
||||
row_builder.insert_select_option_cell(&single_select_field_id, done_option.id.clone());
|
||||
row_builder.insert_select_option_cell(&single_select_field_id, vec![done_option.id.clone()]);
|
||||
match i {
|
||||
0 => {
|
||||
row_builder.insert_text_cell(&text_field_id, "Publish an article".to_string());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, work_option.id.clone());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, vec![work_option.id.clone()]);
|
||||
}
|
||||
1 => {
|
||||
row_builder.insert_text_cell(&text_field_id, "Visit Chicago".to_string());
|
||||
let mut options = SelectOptionIds::new();
|
||||
options.push(travel_option.id.clone());
|
||||
options.push(fun_option.id.clone());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, options.to_string());
|
||||
row_builder.insert_select_option_cell(&multi_select_field_id, vec![options.to_string()]);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
|
@ -2,7 +2,7 @@ use flowy_grid::entities::FieldType;
|
||||
use std::sync::Arc;
|
||||
|
||||
use flowy_grid::services::field::{
|
||||
DateCellChangesetPB, MultiSelectTypeOptionPB, SelectOptionPB, SingleSelectTypeOptionPB, SELECTION_IDS_SEPARATOR,
|
||||
DateCellChangesetPB, MultiSelectTypeOptionPB, SelectOptionPB, SingleSelectTypeOptionPB,
|
||||
};
|
||||
use flowy_grid::services::row::RowRevisionBuilder;
|
||||
use flowy_grid_data_model::revision::{FieldRevision, RowRevision};
|
||||
@ -70,7 +70,7 @@ impl<'a> GridRowTestBuilder<'a> {
|
||||
let type_option = SingleSelectTypeOptionPB::from(&single_select_field);
|
||||
let option = f(type_option.options);
|
||||
self.inner_builder
|
||||
.insert_select_option_cell(&single_select_field.id, option.id);
|
||||
.insert_select_option_cell(&single_select_field.id, vec![option.id]);
|
||||
|
||||
single_select_field.id.clone()
|
||||
}
|
||||
@ -82,11 +82,7 @@ impl<'a> GridRowTestBuilder<'a> {
|
||||
let multi_select_field = self.field_rev_with_type(&FieldType::MultiSelect);
|
||||
let type_option = MultiSelectTypeOptionPB::from(&multi_select_field);
|
||||
let options = f(type_option.options);
|
||||
let ops_ids = options
|
||||
.iter()
|
||||
.map(|option| option.id.clone())
|
||||
.collect::<Vec<_>>()
|
||||
.join(SELECTION_IDS_SEPARATOR);
|
||||
let ops_ids = options.iter().map(|option| option.id.clone()).collect::<Vec<_>>();
|
||||
self.inner_builder
|
||||
.insert_select_option_cell(&multi_select_field.id, ops_ids);
|
||||
|
||||
|
@ -25,11 +25,11 @@ async fn grid_cell_update() {
|
||||
FieldType::DateTime => make_date_cell_string("123"),
|
||||
FieldType::SingleSelect => {
|
||||
let type_option = SingleSelectTypeOptionPB::from(field_rev);
|
||||
SelectOptionCellChangeset::from_insert(&type_option.options.first().unwrap().id).to_str()
|
||||
SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id).to_str()
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
let type_option = MultiSelectTypeOptionPB::from(field_rev);
|
||||
SelectOptionCellChangeset::from_insert(&type_option.options.first().unwrap().id).to_str()
|
||||
SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id).to_str()
|
||||
}
|
||||
FieldType::Checkbox => "1".to_string(),
|
||||
FieldType::URL => "1".to_string(),
|
||||
|
@ -136,16 +136,24 @@ impl GridGroupTest {
|
||||
|
||||
let cell_rev = if to_group.is_default {
|
||||
match field_type {
|
||||
FieldType::SingleSelect => delete_select_option_cell(to_group.group_id.clone(), &field_rev),
|
||||
FieldType::MultiSelect => delete_select_option_cell(to_group.group_id.clone(), &field_rev),
|
||||
FieldType::SingleSelect => {
|
||||
delete_select_option_cell(vec![to_group.group_id.clone()], &field_rev)
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
delete_select_option_cell(vec![to_group.group_id.clone()], &field_rev)
|
||||
}
|
||||
_ => {
|
||||
panic!("Unsupported group field type");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match field_type {
|
||||
FieldType::SingleSelect => insert_select_option_cell(to_group.group_id.clone(), &field_rev),
|
||||
FieldType::MultiSelect => insert_select_option_cell(to_group.group_id.clone(), &field_rev),
|
||||
FieldType::SingleSelect => {
|
||||
insert_select_option_cell(vec![to_group.group_id.clone()], &field_rev)
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
insert_select_option_cell(vec![to_group.group_id.clone()], &field_rev)
|
||||
}
|
||||
_ => {
|
||||
panic!("Unsupported group field type");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user