Merge pull request #1182 from AppFlowy-IO/fix/switch_to_text_property

fix: display cell data after switching to text field
This commit is contained in:
Nathan.fooo 2022-09-27 19:05:10 +08:00 committed by GitHub
commit 82182d7872
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 193 additions and 39 deletions

View File

@ -97,7 +97,6 @@ class GridURLCell extends GridCellWidget {
class _GridURLCellState extends GridCellState<GridURLCell> { class _GridURLCellState extends GridCellState<GridURLCell> {
final _popoverController = PopoverController(); final _popoverController = PopoverController();
GridURLCellController? _cellContext;
late URLCellBloc _cellBloc; late URLCellBloc _cellBloc;
@override @override
@ -132,6 +131,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
controller: _popoverController, controller: _popoverController,
constraints: BoxConstraints.loose(const Size(300, 160)), constraints: BoxConstraints.loose(const Size(300, 160)),
direction: PopoverDirection.bottomWithLeftAligned, direction: PopoverDirection.bottomWithLeftAligned,
triggerActions: PopoverTriggerFlags.none,
offset: const Offset(0, 20), offset: const Offset(0, 20),
child: SizedBox.expand( child: SizedBox.expand(
child: GestureDetector( child: GestureDetector(
@ -144,7 +144,8 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
), ),
popupBuilder: (BuildContext popoverContext) { popupBuilder: (BuildContext popoverContext) {
return URLEditorPopover( return URLEditorPopover(
cellController: _cellContext!, cellController: widget.cellControllerBuilder.build()
as GridURLCellController,
); );
}, },
onClose: () { onClose: () {
@ -166,17 +167,13 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
final uri = Uri.parse(url); final uri = Uri.parse(url);
if (url.isNotEmpty && await canLaunchUrl(uri)) { if (url.isNotEmpty && await canLaunchUrl(uri)) {
await launchUrl(uri); await launchUrl(uri);
} else {
_cellContext =
widget.cellControllerBuilder.build() as GridURLCellController;
widget.onCellEditing.value = true;
_popoverController.show();
} }
} }
@override @override
void requestBeginFocus() { void requestBeginFocus() {
_openUrlOrEdit(_cellBloc.state.url); widget.onCellEditing.value = true;
_popoverController.show();
} }
@override @override

View File

@ -1,3 +1,4 @@
use crate::entities::FieldType;
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode; use flowy_error::ErrorCode;
use flowy_grid_data_model::parser::NotEmptyStr; use flowy_grid_data_model::parser::NotEmptyStr;
@ -74,15 +75,20 @@ pub struct GridCellPB {
#[pb(index = 1)] #[pb(index = 1)]
pub field_id: String, pub field_id: String,
// The data was encoded in field_type's data type
#[pb(index = 2)] #[pb(index = 2)]
pub data: Vec<u8>, pub data: Vec<u8>,
#[pb(index = 3, one_of)]
pub field_type: Option<FieldType>,
} }
impl GridCellPB { impl GridCellPB {
pub fn new(field_id: &str, data: Vec<u8>) -> Self { pub fn new(field_id: &str, field_type: FieldType, data: Vec<u8>) -> Self {
Self { Self {
field_id: field_id.to_owned(), field_id: field_id.to_owned(),
data, data,
field_type: Some(field_type),
} }
} }
@ -90,6 +96,7 @@ impl GridCellPB {
Self { Self {
field_id: field_id.to_owned(), field_id: field_id.to_owned(),
data: vec![], data: vec![],
field_type: None,
} }
} }
} }

View File

@ -24,6 +24,13 @@ pub trait CellDisplayable<CD> {
decoded_field_type: &FieldType, decoded_field_type: &FieldType,
field_rev: &FieldRevision, field_rev: &FieldRevision,
) -> FlowyResult<CellBytes>; ) -> FlowyResult<CellBytes>;
fn display_string(
&self,
cell_data: CellData<CD>,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<String>;
} }
// CD: Short for CellData. This type is the type return by apply_changeset function. // CD: Short for CellData. This type is the type return by apply_changeset function.
@ -84,16 +91,16 @@ pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
pub fn decode_any_cell_data<T: TryInto<AnyCellData, Error = FlowyError> + Debug>( pub fn decode_any_cell_data<T: TryInto<AnyCellData, Error = FlowyError> + Debug>(
data: T, data: T,
field_rev: &FieldRevision, field_rev: &FieldRevision,
) -> CellBytes { ) -> (FieldType, CellBytes) {
let to_field_type = field_rev.ty.into();
match data.try_into() { match data.try_into() {
Ok(any_cell_data) => { Ok(any_cell_data) => {
let AnyCellData { data, field_type } = any_cell_data; let AnyCellData { data, field_type } = any_cell_data;
let to_field_type = field_rev.ty.into();
match try_decode_cell_data(data.into(), &field_type, &to_field_type, field_rev) { match try_decode_cell_data(data.into(), &field_type, &to_field_type, field_rev) {
Ok(cell_bytes) => cell_bytes, Ok(cell_bytes) => (field_type, cell_bytes),
Err(e) => { Err(e) => {
tracing::error!("Decode cell data failed, {:?}", e); tracing::error!("Decode cell data failed, {:?}", e);
CellBytes::default() (field_type, CellBytes::default())
} }
} }
} }
@ -101,12 +108,58 @@ pub fn decode_any_cell_data<T: TryInto<AnyCellData, Error = FlowyError> + Debug>
// It's okay to ignore this error, because it's okay that the current cell can't // It's okay to ignore this error, because it's okay that the current cell can't
// display the existing cell data. For example, the UI of the text cell will be blank if // display the existing cell data. For example, the UI of the text cell will be blank if
// the type of the data of cell is Number. // the type of the data of cell is Number.
CellBytes::default()
(to_field_type, CellBytes::default())
} }
} }
} }
/// Use the `to_field_type`'s TypeOption to parse the cell data into `from_field_type`'s data. pub fn decode_cell_data_to_string(
cell_data: CellData<String>,
from_field_type: &FieldType,
to_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<String> {
let cell_data = cell_data.try_into_inner()?;
let get_cell_display_str = || {
let field_type: FieldTypeRevision = to_field_type.into();
let result = match to_field_type {
FieldType::RichText => field_rev
.get_type_option::<RichTextTypeOptionPB>(field_type)?
.display_string(cell_data.into(), from_field_type, field_rev),
FieldType::Number => field_rev
.get_type_option::<NumberTypeOptionPB>(field_type)?
.display_string(cell_data.into(), from_field_type, field_rev),
FieldType::DateTime => field_rev
.get_type_option::<DateTypeOptionPB>(field_type)?
.display_string(cell_data.into(), from_field_type, field_rev),
FieldType::SingleSelect => field_rev
.get_type_option::<SingleSelectTypeOptionPB>(field_type)?
.display_string(cell_data.into(), from_field_type, field_rev),
FieldType::MultiSelect => field_rev
.get_type_option::<MultiSelectTypeOptionPB>(field_type)?
.display_string(cell_data.into(), from_field_type, field_rev),
FieldType::Checkbox => field_rev
.get_type_option::<CheckboxTypeOptionPB>(field_type)?
.display_string(cell_data.into(), from_field_type, field_rev),
FieldType::URL => field_rev
.get_type_option::<URLTypeOptionPB>(field_type)?
.display_string(cell_data.into(), from_field_type, field_rev),
};
Some(result)
};
match get_cell_display_str() {
Some(Ok(s)) => Ok(s),
Some(Err(err)) => {
tracing::error!("{:?}", err);
Ok("".to_owned())
}
None => Ok("".to_owned()),
}
}
/// Use the `to_field_type`'s TypeOption to parse the cell data into `from_field_type` type's data.
/// ///
/// Each `FieldType` has its corresponding `TypeOption` that implements the `CellDisplayable` /// Each `FieldType` has its corresponding `TypeOption` that implements the `CellDisplayable`
/// and `CellDataOperation` traits. /// and `CellDataOperation` traits.

View File

@ -48,6 +48,16 @@ impl CellDisplayable<CheckboxCellData> for CheckboxTypeOptionPB {
let cell_data = cell_data.try_into_inner()?; let cell_data = cell_data.try_into_inner()?;
Ok(CellBytes::new(cell_data)) Ok(CellBytes::new(cell_data))
} }
fn display_string(
&self,
cell_data: CellData<CheckboxCellData>,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<String> {
let cell_data = cell_data.try_into_inner()?;
Ok(cell_data.to_string())
}
} }
impl CellDataOperation<CheckboxCellData, String> for CheckboxTypeOptionPB { impl CellDataOperation<CheckboxCellData, String> for CheckboxTypeOptionPB {

View File

@ -127,6 +127,17 @@ impl CellDisplayable<DateTimestamp> for DateTypeOptionPB {
let date_cell_data = self.today_desc_from_timestamp(timestamp); let date_cell_data = self.today_desc_from_timestamp(timestamp);
CellBytes::from(date_cell_data) CellBytes::from(date_cell_data)
} }
fn display_string(
&self,
cell_data: CellData<DateTimestamp>,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<String> {
let timestamp = cell_data.try_into_inner()?;
let date_cell_data = self.today_desc_from_timestamp(timestamp);
Ok(date_cell_data.date)
}
} }
impl CellDataOperation<DateTimestamp, DateCellChangesetPB> for DateTypeOptionPB { impl CellDataOperation<DateTimestamp, DateCellChangesetPB> for DateTypeOptionPB {

View File

@ -1,6 +1,6 @@
use crate::entities::FieldType; use crate::entities::FieldType;
use crate::impl_type_option; use crate::impl_type_option;
use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation}; use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
use crate::services::field::type_options::number_type_option::format::*; use crate::services::field::type_options::number_type_option::format::*;
use crate::services::field::{BoxTypeOptionBuilder, NumberCellData, TypeOptionBuilder}; use crate::services::field::{BoxTypeOptionBuilder, NumberCellData, TypeOptionBuilder};
use bytes::Bytes; use bytes::Bytes;
@ -102,17 +102,13 @@ pub(crate) fn strip_currency_symbol<T: ToString>(s: T) -> String {
s s
} }
impl CellDataOperation<String, String> for NumberTypeOptionPB { impl CellDisplayable<String> for NumberTypeOptionPB {
fn decode_cell_data( fn display_data(
&self, &self,
cell_data: CellData<String>, cell_data: CellData<String>,
decoded_field_type: &FieldType, _decoded_field_type: &FieldType,
_field_rev: &FieldRevision, _field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> { ) -> FlowyResult<CellBytes> {
if decoded_field_type.is_date() {
return Ok(CellBytes::default());
}
let cell_data: String = cell_data.try_into_inner()?; let cell_data: String = cell_data.try_into_inner()?;
match self.format_cell_data(&cell_data) { match self.format_cell_data(&cell_data) {
Ok(num) => Ok(CellBytes::new(num.to_string())), Ok(num) => Ok(CellBytes::new(num.to_string())),
@ -120,6 +116,31 @@ impl CellDataOperation<String, String> for NumberTypeOptionPB {
} }
} }
fn display_string(
&self,
cell_data: CellData<String>,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<String> {
let cell_data: String = cell_data.try_into_inner()?;
Ok(cell_data)
}
}
impl CellDataOperation<String, String> for NumberTypeOptionPB {
fn decode_cell_data(
&self,
cell_data: CellData<String>,
decoded_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<CellBytes> {
if decoded_field_type.is_date() {
return Ok(CellBytes::default());
}
self.display_data(cell_data, decoded_field_type, field_rev)
}
fn apply_changeset( fn apply_changeset(
&self, &self,
changeset: CellDataChangeset<String>, changeset: CellDataChangeset<String>,

View File

@ -120,6 +120,21 @@ where
) -> FlowyResult<CellBytes> { ) -> FlowyResult<CellBytes> {
CellBytes::from(self.selected_select_option(cell_data)) CellBytes::from(self.selected_select_option(cell_data))
} }
fn display_string(
&self,
cell_data: CellData<SelectOptionIds>,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<String> {
Ok(self
.selected_select_option(cell_data)
.select_options
.into_iter()
.map(|option| option.name)
.collect::<Vec<String>>()
.join(SELECTION_IDS_SEPARATOR))
}
} }
pub fn select_option_operation(field_rev: &FieldRevision) -> FlowyResult<Box<dyn SelectOptionOperation>> { pub fn select_option_operation(field_rev: &FieldRevision) -> FlowyResult<Box<dyn SelectOptionOperation>> {

View File

@ -17,10 +17,10 @@ mod tests {
type_option type_option
.decode_cell_data(1647251762.into(), &field_type, &field_rev) .decode_cell_data(1647251762.into(), &field_type, &field_rev)
.unwrap() .unwrap()
.parser::<DateCellDataParser>() .parser::<TextCellDataParser>()
.unwrap() .unwrap()
.date, .as_ref(),
"Mar 14,2022".to_owned() "Mar 14,2022"
); );
} }
@ -40,10 +40,10 @@ mod tests {
type_option type_option
.decode_cell_data(option_id.into(), &field_type, &field_rev) .decode_cell_data(option_id.into(), &field_type, &field_rev)
.unwrap() .unwrap()
.parser::<SelectOptionCellDataParser>() .parser::<TextCellDataParser>()
.unwrap() .unwrap()
.select_options, .to_string(),
vec![done_option], done_option.name,
); );
} }
} }

View File

@ -1,8 +1,8 @@
use crate::entities::FieldType; use crate::entities::FieldType;
use crate::impl_type_option; use crate::impl_type_option;
use crate::services::cell::{ use crate::services::cell::{
try_decode_cell_data, CellBytes, CellBytesParser, CellData, CellDataChangeset, CellDataOperation, CellDisplayable, decode_cell_data_to_string, CellBytes, CellBytesParser, CellData, CellDataChangeset, CellDataOperation,
FromCellString, CellDisplayable, FromCellString,
}; };
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use bytes::Bytes; use bytes::Bytes;
@ -44,6 +44,16 @@ impl CellDisplayable<String> for RichTextTypeOptionPB {
let cell_str: String = cell_data.try_into_inner()?; let cell_str: String = cell_data.try_into_inner()?;
Ok(CellBytes::new(cell_str)) Ok(CellBytes::new(cell_str))
} }
fn display_string(
&self,
cell_data: CellData<String>,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<String> {
let cell_str: String = cell_data.try_into_inner()?;
Ok(cell_str)
}
} }
impl CellDataOperation<String, String> for RichTextTypeOptionPB { impl CellDataOperation<String, String> for RichTextTypeOptionPB {
@ -57,8 +67,10 @@ impl CellDataOperation<String, String> for RichTextTypeOptionPB {
|| decoded_field_type.is_single_select() || decoded_field_type.is_single_select()
|| decoded_field_type.is_multi_select() || decoded_field_type.is_multi_select()
|| decoded_field_type.is_number() || decoded_field_type.is_number()
|| decoded_field_type.is_url()
{ {
try_decode_cell_data(cell_data, decoded_field_type, decoded_field_type, field_rev) let s = decode_cell_data_to_string(cell_data, decoded_field_type, decoded_field_type, field_rev);
Ok(CellBytes::new(s.unwrap_or_else(|_| "".to_owned())))
} else { } else {
self.display_data(cell_data, decoded_field_type, field_rev) self.display_data(cell_data, decoded_field_type, field_rev)
} }
@ -85,6 +97,14 @@ impl AsRef<str> for TextCellData {
} }
} }
impl std::ops::Deref for TextCellData {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromCellString for TextCellData { impl FromCellString for TextCellData {
fn from_cell_str(s: &str) -> FlowyResult<Self> fn from_cell_str(s: &str) -> FlowyResult<Self>
where where
@ -94,6 +114,12 @@ impl FromCellString for TextCellData {
} }
} }
impl ToString for TextCellData {
fn to_string(&self) -> String {
self.0.clone()
}
}
pub struct TextCellDataParser(); pub struct TextCellDataParser();
impl CellBytesParser for TextCellDataParser { impl CellBytesParser for TextCellDataParser {
type Object = TextCellData; type Object = TextCellData;

View File

@ -42,6 +42,16 @@ impl CellDisplayable<URLCellDataPB> for URLTypeOptionPB {
let cell_data: URLCellDataPB = cell_data.try_into_inner()?; let cell_data: URLCellDataPB = cell_data.try_into_inner()?;
CellBytes::from(cell_data) CellBytes::from(cell_data)
} }
fn display_string(
&self,
cell_data: CellData<URLCellDataPB>,
_decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<String> {
let cell_data: URLCellDataPB = cell_data.try_into_inner()?;
Ok(cell_data.content)
}
} }
impl CellDataOperation<URLCellDataPB, String> for URLTypeOptionPB { impl CellDataOperation<URLCellDataPB, String> for URLTypeOptionPB {

View File

@ -435,14 +435,18 @@ impl GridRevisionEditor {
} }
pub async fn get_cell(&self, params: &GridCellIdParams) -> Option<GridCellPB> { pub async fn get_cell(&self, params: &GridCellIdParams) -> Option<GridCellPB> {
let cell_bytes = self.get_cell_bytes(params).await?; let (field_type, cell_bytes) = self.decode_any_cell_data(params).await?;
Some(GridCellPB::new(&params.field_id, cell_bytes.to_vec())) Some(GridCellPB::new(&params.field_id, field_type, cell_bytes.to_vec()))
} }
pub async fn get_cell_bytes(&self, params: &GridCellIdParams) -> Option<CellBytes> { pub async fn get_cell_bytes(&self, params: &GridCellIdParams) -> Option<CellBytes> {
let (_, cell_data) = self.decode_any_cell_data(params).await?;
Some(cell_data)
}
async fn decode_any_cell_data(&self, params: &GridCellIdParams) -> Option<(FieldType, CellBytes)> {
let field_rev = self.get_field_rev(&params.field_id).await?; let field_rev = self.get_field_rev(&params.field_id).await?;
let row_rev = self.block_manager.get_row_rev(&params.row_id).await.ok()??; let row_rev = self.block_manager.get_row_rev(&params.row_id).await.ok()??;
let cell_rev = row_rev.cells.get(&params.field_id)?.clone(); let cell_rev = row_rev.cells.get(&params.field_id)?.clone();
Some(decode_any_cell_data(cell_rev.data, &field_rev)) Some(decode_any_cell_data(cell_rev.data, &field_rev))
} }

View File

@ -205,7 +205,7 @@ where
if let Some(cell_rev) = cell_rev { if let Some(cell_rev) = cell_rev {
let mut grouped_rows: Vec<GroupedRow> = vec![]; let mut grouped_rows: Vec<GroupedRow> = vec![];
let cell_bytes = decode_any_cell_data(cell_rev.data, field_rev); let cell_bytes = decode_any_cell_data(cell_rev.data, field_rev).1;
let cell_data = cell_bytes.parser::<P>()?; let cell_data = cell_bytes.parser::<P>()?;
for group in self.group_ctx.groups() { for group in self.group_ctx.groups() {
if self.can_group(&group.filter_content, &cell_data) { if self.can_group(&group.filter_content, &cell_data) {
@ -244,7 +244,7 @@ where
field_rev: &FieldRevision, field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupChangesetPB>> { ) -> FlowyResult<Vec<GroupChangesetPB>> {
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev); let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1;
let cell_data = cell_bytes.parser::<P>()?; let cell_data = cell_bytes.parser::<P>()?;
let mut changesets = self.add_row_if_match(row_rev, &cell_data); let mut changesets = self.add_row_if_match(row_rev, &cell_data);
let default_group_changeset = self.update_default_group(row_rev, &changesets); let default_group_changeset = self.update_default_group(row_rev, &changesets);
@ -265,7 +265,7 @@ where
) -> FlowyResult<Vec<GroupChangesetPB>> { ) -> FlowyResult<Vec<GroupChangesetPB>> {
// if the cell_rev is none, then the row must be crated from the default group. // if the cell_rev is none, then the row must be crated from the default group.
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev); let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1;
let cell_data = cell_bytes.parser::<P>()?; let cell_data = cell_bytes.parser::<P>()?;
Ok(self.remove_row_if_match(row_rev, &cell_data)) Ok(self.remove_row_if_match(row_rev, &cell_data))
} else { } else {
@ -285,7 +285,7 @@ where
}; };
if let Some(cell_rev) = cell_rev { if let Some(cell_rev) = cell_rev {
let cell_bytes = decode_any_cell_data(cell_rev.data, context.field_rev); let cell_bytes = decode_any_cell_data(cell_rev.data, context.field_rev).1;
let cell_data = cell_bytes.parser::<P>()?; let cell_data = cell_bytes.parser::<P>()?;
Ok(self.move_row(&cell_data, context)) Ok(self.move_row(&cell_data, context))
} else { } else {