Merge pull request #505 from AppFlowy-IO/fix_0.0.4_bugs_2

Fix some UI bugs
This commit is contained in:
Nathan.fooo 2022-05-25 06:34:10 +08:00 committed by GitHub
commit 1ca602fb72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 750 additions and 523 deletions

View File

@ -168,6 +168,7 @@
"dateFormatLocal": "Month/Month/Day", "dateFormatLocal": "Month/Month/Day",
"dateFormatUS": "Month/Month/Day", "dateFormatUS": "Month/Month/Day",
"timeFormat": " Time format", "timeFormat": " Time format",
"invalidTimeFormat": "Invalid format",
"timeFormatTwelveHour": "12 hour", "timeFormatTwelveHour": "12 hour",
"timeFormatTwentyFourHour": "24 hour", "timeFormatTwentyFourHour": "24 hour",
"addSelectOption": "Add an option", "addSelectOption": "Add an option",

View File

@ -1,6 +1,9 @@
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:easy_localization/easy_localization.dart' show StringTranslateExtension;
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@ -91,7 +94,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
case ErrorCode.InvalidDateTimeFormat: case ErrorCode.InvalidDateTimeFormat:
emit(state.copyWith( emit(state.copyWith(
dateData: Some(newDateData), dateData: Some(newDateData),
timeFormatError: Some(err.toString()), timeFormatError: Some(timeFormatPrompt(err)),
)); ));
break; break;
default: default:
@ -101,6 +104,21 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
); );
} }
String timeFormatPrompt(FlowyError error) {
String msg = LocaleKeys.grid_field_invalidTimeFormat.tr() + ". ";
switch (state.dateTypeOption.timeFormat) {
case TimeFormat.TwelveHour:
msg = msg + "e.g. 01: 00 AM";
break;
case TimeFormat.TwentyFourHour:
msg = msg + "e.g. 13: 00";
break;
default:
break;
}
return msg;
}
@override @override
Future<void> close() async { Future<void> close() async {
if (_onCellChangedFn != null) { if (_onCellChangedFn != null) {

View File

@ -47,13 +47,11 @@ class BlankCell extends StatelessWidget {
} }
} }
abstract class GridCellWidget extends HoverWidget { abstract class GridCellWidget implements FlowyHoverWidget {
@override @override
final ValueNotifier<bool> onFocus = ValueNotifier<bool>(false); final ValueNotifier<bool> onFocus = ValueNotifier<bool>(false);
final GridCellRequestFocusNotifier requestFocus = GridCellRequestFocusNotifier(); final GridCellRequestFocusNotifier requestFocus = GridCellRequestFocusNotifier();
GridCellWidget({Key? key}) : super(key: key);
} }
class GridCellRequestFocusNotifier extends ChangeNotifier { class GridCellRequestFocusNotifier extends ChangeNotifier {

View File

@ -6,7 +6,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'cell_builder.dart'; import 'cell_builder.dart';
class CheckboxCell extends GridCellWidget { class CheckboxCell extends StatefulWidget with GridCellWidget {
final GridCellContextBuilder cellContextBuilder; final GridCellContextBuilder cellContextBuilder;
CheckboxCell({ CheckboxCell({
required this.cellContextBuilder, required this.cellContextBuilder,
@ -41,7 +41,7 @@ class _CheckboxCellState extends State<CheckboxCell> {
onPressed: () => context.read<CheckboxCellBloc>().add(const CheckboxCellEvent.select()), onPressed: () => context.read<CheckboxCellBloc>().add(const CheckboxCellEvent.select()),
iconPadding: EdgeInsets.zero, iconPadding: EdgeInsets.zero,
icon: icon, icon: icon,
width: 23, width: 20,
), ),
); );
}, },

View File

@ -18,7 +18,7 @@ abstract class GridCellDelegate {
GridCellDelegate get delegate; GridCellDelegate get delegate;
} }
class DateCell extends GridCellWidget { class DateCell extends StatefulWidget with GridCellWidget {
final GridCellContextBuilder cellContextBuilder; final GridCellContextBuilder cellContextBuilder;
late final DateCellStyle? cellStyle; late final DateCellStyle? cellStyle;

View File

@ -7,7 +7,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'cell_builder.dart'; import 'cell_builder.dart';
class NumberCell extends GridCellWidget { class NumberCell extends StatefulWidget with GridCellWidget {
final GridCellContextBuilder cellContextBuilder; final GridCellContextBuilder cellContextBuilder;
NumberCell({ NumberCell({

View File

@ -87,7 +87,7 @@ class SelectOptionTag extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ChoiceChip( return ChoiceChip(
pressElevation: 1, pressElevation: 1,
label: FlowyText.medium(name, fontSize: 12), label: FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis),
selectedColor: color, selectedColor: color,
backgroundColor: color, backgroundColor: color,
labelPadding: const EdgeInsets.symmetric(horizontal: 6), labelPadding: const EdgeInsets.symmetric(horizontal: 6),
@ -130,11 +130,18 @@ class SelectOptionTagCell extends StatelessWidget {
child: InkWell( child: InkWell(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 3), padding: const EdgeInsets.symmetric(horizontal: 3),
child: Row(children: [ child: Row(
SelectOptionTag.fromSelectOption(context: context, option: option), mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
fit: FlexFit.loose,
flex: 2,
child: SelectOptionTag.fromSelectOption(context: context, option: option),
),
const Spacer(), const Spacer(),
...children, ...children,
]), ],
),
), ),
onTap: () => onSelected(option), onTap: () => onSelected(option),
), ),

View File

@ -20,7 +20,7 @@ class SelectOptionCellStyle extends GridCellStyle {
}); });
} }
class SingleSelectCell extends GridCellWidget { class SingleSelectCell extends StatefulWidget with GridCellWidget {
final GridCellContextBuilder cellContextBuilder; final GridCellContextBuilder cellContextBuilder;
late final SelectOptionCellStyle? cellStyle; late final SelectOptionCellStyle? cellStyle;
@ -74,7 +74,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
} }
//---------------------------------------------------------------- //----------------------------------------------------------------
class MultiSelectCell extends GridCellWidget { class MultiSelectCell extends StatefulWidget with GridCellWidget {
final GridCellContextBuilder cellContextBuilder; final GridCellContextBuilder cellContextBuilder;
late final SelectOptionCellStyle? cellStyle; late final SelectOptionCellStyle? cellStyle;
@ -160,7 +160,7 @@ class _SelectOptionCell extends StatelessWidget {
.toList(); .toList();
child = Align( child = Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Wrap(children: tags, spacing: 4, runSpacing: 4), child: Wrap(children: tags, spacing: 4, runSpacing: 2),
); );
} }

View File

@ -225,7 +225,18 @@ class _SelectOptionCell extends StatelessWidget {
height: GridSize.typeOptionItemHeight, height: GridSize.typeOptionItemHeight,
child: Row( child: Row(
children: [ children: [
Expanded(child: _body(theme, context)), Flexible(
fit: FlexFit.loose,
child: SelectOptionTagCell(
option: option,
onSelected: (option) {
context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.selectOption(option.id));
},
children: [
if (isSelected) svgWidget("grid/checkmark"),
],
),
),
FlowyIconButton( FlowyIconButton(
width: 30, width: 30,
onPressed: () => _showEditPannel(context), onPressed: () => _showEditPannel(context),
@ -237,18 +248,6 @@ class _SelectOptionCell extends StatelessWidget {
); );
} }
Widget _body(AppTheme theme, BuildContext context) {
return SelectOptionTagCell(
option: option,
onSelected: (option) {
context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.selectOption(option.id));
},
children: [
if (isSelected) svgWidget("grid/checkmark"),
],
);
}
void _showEditPannel(BuildContext context) { void _showEditPannel(BuildContext context) {
final pannel = SelectOptionTypeOptionEditor( final pannel = SelectOptionTypeOptionEditor(
option: option, option: option,

View File

@ -13,7 +13,7 @@ class GridTextCellStyle extends GridCellStyle {
}); });
} }
class GridTextCell extends GridCellWidget { class GridTextCell extends StatefulWidget with GridCellWidget {
final GridCellContextBuilder cellContextBuilder; final GridCellContextBuilder cellContextBuilder;
late final GridTextCellStyle? cellStyle; late final GridTextCellStyle? cellStyle;
GridTextCell({ GridTextCell({

View File

@ -1,44 +0,0 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class NumberCell extends StatefulWidget {
final GridCell cellData;
const NumberCell({
required this.cellData,
Key? key,
}) : super(key: key);
@override
State<NumberCell> createState() => _NumberCellState();
}
class _NumberCellState extends State<NumberCell> {
late NumberCellBloc _cellBloc;
@override
void initState() {
_cellBloc = getIt<NumberCellBloc>(param1: widget.cellData);
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<NumberCellBloc, NumberCellState>(
builder: (context, state) {
return Container();
},
),
);
}
@override
Future<void> dispose() async {
_cellBloc.close();
super.dispose();
}
}

View File

@ -67,14 +67,15 @@ class _RowDetailPageState extends State<RowDetailPage> {
return bloc; return bloc;
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 40), padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
child: Column( child: Column(
children: [ children: [
SizedBox( SizedBox(
height: 40, height: 40,
child: Row( child: Row(
children: const [Spacer(), _CloseButton()], children: const [Spacer(), _CloseButton()],
)), ),
),
Expanded(child: _PropertyList(cellCache: widget.cellCache)), Expanded(child: _PropertyList(cellCache: widget.cellCache)),
], ],
), ),
@ -153,8 +154,9 @@ class _RowDetailCell extends StatelessWidget {
cellCache, cellCache,
style: _buildCellStyle(theme, gridCell.field.fieldType), style: _buildCellStyle(theme, gridCell.field.fieldType),
); );
return SizedBox( return ConstrainedBox(
height: 36, constraints: const BoxConstraints(minHeight: 40),
child: IntrinsicHeight(
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -167,11 +169,12 @@ class _RowDetailCell extends StatelessWidget {
Expanded( Expanded(
child: FlowyHover2( child: FlowyHover2(
child: cell, child: cell,
contentPadding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
), ),
), ),
], ],
), ),
),
); );
} }

View File

@ -102,14 +102,14 @@ class FlowyHoverContainer extends StatelessWidget {
} }
// //
abstract class HoverWidget extends StatefulWidget { abstract class FlowyHoverWidget extends Widget {
const HoverWidget({Key? key}) : super(key: key); const FlowyHoverWidget({Key? key}) : super(key: key);
ValueNotifier<bool> get onFocus; ValueNotifier<bool>? get onFocus;
} }
class FlowyHover2 extends StatefulWidget { class FlowyHover2 extends StatefulWidget {
final Widget child; final FlowyHoverWidget child;
final EdgeInsets contentPadding; final EdgeInsets contentPadding;
const FlowyHover2({ const FlowyHover2({
required this.child, required this.child,
@ -123,24 +123,30 @@ class FlowyHover2 extends StatefulWidget {
class _FlowyHover2State extends State<FlowyHover2> { class _FlowyHover2State extends State<FlowyHover2> {
late FlowyHoverState _hoverState; late FlowyHoverState _hoverState;
VoidCallback? _listenerFn;
@override @override
void initState() { void initState() {
_hoverState = FlowyHoverState(); _hoverState = FlowyHoverState();
if (widget.child is HoverWidget) { listener() {
final hoverWidget = widget.child as HoverWidget; _hoverState.onFocus = widget.child.onFocus?.value ?? false;
hoverWidget.onFocus.addListener(() {
_hoverState.onFocus = hoverWidget.onFocus.value;
});
} }
_listenerFn = listener;
widget.child.onFocus?.addListener(listener);
super.initState(); super.initState();
} }
@override @override
void dispose() { void dispose() {
_hoverState.dispose(); _hoverState.dispose();
if (_listenerFn != null) {
widget.child.onFocus?.removeListener(_listenerFn!);
_listenerFn = null;
}
super.dispose(); super.dispose();
} }
@ -179,10 +185,7 @@ class _HoverBackground extends StatelessWidget {
builder: (context, state, child) { builder: (context, state, child) {
if (state.onHover || state.onFocus) { if (state.onHover || state.onFocus) {
return FlowyHoverContainer( return FlowyHoverContainer(
style: HoverStyle( style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6),
borderRadius: Corners.s6Border,
hoverColor: theme.shader6,
),
); );
} else { } else {
return const SizedBox(); return const SizedBox();

View File

@ -132,11 +132,14 @@ class _RoundedInputFieldState extends State<RoundedInputField> {
children.add( children.add(
Align( Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Text( child: Text(
widget.errorText, widget.errorText,
style: widget.style, style: widget.style,
), ),
), ),
),
); );
} }

View File

@ -2,7 +2,7 @@
proto_crates = [ proto_crates = [
"src/event_map.rs", "src/event_map.rs",
"src/services/field/type_options", "src/services/field/type_options",
"src/services/entities", "src/entities",
"src/dart_notification.rs" "src/dart_notification.rs"
] ]
event_files = ["src/event_map.rs"] event_files = ["src/event_map.rs"]

View File

@ -1,4 +1,4 @@
use crate::services::entities::{FieldIdentifier, FieldIdentifierPayload}; use crate::entities::{FieldIdentifier, FieldIdentifierPayload};
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;

View File

@ -1,5 +1,5 @@
use crate::entities::*;
use crate::manager::GridManager; use crate::manager::GridManager;
use crate::services::entities::*;
use crate::services::field::type_options::*; use crate::services::field::type_options::*;
use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_json_str}; use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_json_str};
use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_error::{ErrorCode, FlowyError, FlowyResult};

View File

@ -6,6 +6,7 @@ pub mod event_map;
pub mod manager; pub mod manager;
mod dart_notification; mod dart_notification;
pub mod entities;
mod protobuf; mod protobuf;
pub mod services; pub mod services;
pub mod util; pub mod util;

View File

@ -1,15 +1,14 @@
use crate::impl_type_option; use crate::impl_type_option;
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData};
use bytes::Bytes; use bytes::Bytes;
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
use flowy_error::FlowyError; use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::entities::{ use flowy_grid_data_model::entities::{
CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Default)] #[derive(Default)]
pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption); pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption);
@ -43,32 +42,38 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox);
const YES: &str = "Yes"; const YES: &str = "Yes";
const NO: &str = "No"; const NO: &str = "No";
impl CellDataOperation for CheckboxTypeOption { impl CellDataOperation<String, String> for CheckboxTypeOption {
fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { fn decode_cell_data<T>(
if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
if !type_option_cell_data.is_checkbox() {
return DecodedCellData::default();
}
let cell_data = type_option_cell_data.data;
if cell_data == YES || cell_data == NO {
return DecodedCellData::from_content(cell_data);
}
}
DecodedCellData::default()
}
fn apply_changeset<T: Into<CellContentChangeset>>(
&self, &self,
changeset: T, encoded_data: T,
_cell_meta: Option<CellMeta>, decoded_field_type: &FieldType,
) -> Result<String, FlowyError> { _field_meta: &FieldMeta,
) -> FlowyResult<DecodedCellData>
where
T: Into<String>,
{
if !decoded_field_type.is_checkbox() {
return Ok(DecodedCellData::default());
}
let encoded_data = encoded_data.into();
if encoded_data == YES || encoded_data == NO {
return Ok(DecodedCellData::from_content(encoded_data));
}
Ok(DecodedCellData::default())
}
fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
where
C: Into<CellContentChangeset>,
{
let changeset = changeset.into(); let changeset = changeset.into();
let s = match string_to_bool(&changeset) { let s = match string_to_bool(&changeset) {
true => YES, true => YES,
false => NO, false => NO,
}; };
Ok(TypeOptionCellData::new(s, self.field_type()).json()) Ok(s.to_string())
} }
} }
@ -88,32 +93,49 @@ fn string_to_bool(bool_str: &str) -> bool {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::services::field::type_options::checkbox_type_option::{NO, YES}; use crate::services::field::type_options::checkbox_type_option::{NO, YES};
use crate::services::field::CheckboxTypeOption;
use crate::services::field::FieldBuilder; use crate::services::field::FieldBuilder;
use crate::services::row::CellDataOperation; use crate::services::row::{apply_cell_data_changeset, decode_cell_data_from_type_option_cell_data};
use flowy_grid_data_model::entities::FieldType; use flowy_grid_data_model::entities::FieldType;
#[test] #[test]
fn checkout_box_description_test() { fn checkout_box_description_test() {
let type_option = CheckboxTypeOption::default(); let field_meta = FieldBuilder::from_field_type(&FieldType::Checkbox).build();
let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); let data = apply_cell_data_changeset("true", None, &field_meta).unwrap();
assert_eq!(
decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
YES
);
let data = type_option.apply_changeset("true", None).unwrap(); let data = apply_cell_data_changeset("1", None, &field_meta).unwrap();
assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES); assert_eq!(
decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
YES
);
let data = type_option.apply_changeset("1", None).unwrap(); let data = apply_cell_data_changeset("yes", None, &field_meta).unwrap();
assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES); assert_eq!(
decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
YES
);
let data = type_option.apply_changeset("yes", None).unwrap(); let data = apply_cell_data_changeset("false", None, &field_meta).unwrap();
assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES); assert_eq!(
decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
NO
);
let data = type_option.apply_changeset("false", None).unwrap(); let data = apply_cell_data_changeset("no", None, &field_meta).unwrap();
assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO); assert_eq!(
decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
NO
);
let data = type_option.apply_changeset("no", None).unwrap(); let data = apply_cell_data_changeset("12", None, &field_meta).unwrap();
assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO); assert_eq!(
decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
let data = type_option.apply_changeset("123", None).unwrap(); NO
assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO); );
} }
} }

View File

@ -1,7 +1,9 @@
use crate::entities::{CellIdentifier, CellIdentifierPayload};
use crate::impl_type_option; use crate::impl_type_option;
use crate::services::entities::{CellIdentifier, CellIdentifierPayload};
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; use crate::services::row::{
CellContentChangeset, CellDataOperation, DecodedCellData, EncodedCellData, TypeOptionCellData,
};
use bytes::Bytes; use bytes::Bytes;
use chrono::format::strftime::StrftimeItems; use chrono::format::strftime::StrftimeItems;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
@ -90,10 +92,10 @@ impl DateTypeOption {
let serde_cell_data = DateCellDataSerde::from_str(&result.unwrap().data)?; let serde_cell_data = DateCellDataSerde::from_str(&result.unwrap().data)?;
let date = self.decode_cell_data_from_timestamp(&serde_cell_data).content; let date = self.decode_cell_data_from_timestamp(&serde_cell_data).content;
let time = serde_cell_data.time.unwrap_or("".to_owned()); let time = serde_cell_data.time.unwrap_or_else(|| "".to_owned());
let timestamp = serde_cell_data.timestamp; let timestamp = serde_cell_data.timestamp;
return Ok(DateCellData { date, time, timestamp }); Ok(DateCellData { date, time, timestamp })
} }
fn decode_cell_data_from_timestamp(&self, serde_cell_data: &DateCellDataSerde) -> DecodedCellData { fn decode_cell_data_from_timestamp(&self, serde_cell_data: &DateCellDataSerde) -> DecodedCellData {
@ -102,7 +104,7 @@ impl DateTypeOption {
} }
let cell_content = self.today_desc_from_timestamp(serde_cell_data.timestamp, &serde_cell_data.time); let cell_content = self.today_desc_from_timestamp(serde_cell_data.timestamp, &serde_cell_data.time);
return DecodedCellData::new(serde_cell_data.timestamp.to_string(), cell_content); DecodedCellData::new(serde_cell_data.timestamp.to_string(), cell_content)
} }
fn timestamp_from_utc_with_time( fn timestamp_from_utc_with_time(
@ -131,34 +133,36 @@ impl DateTypeOption {
} }
} }
return Ok(utc.timestamp()); Ok(utc.timestamp())
} }
} }
impl CellDataOperation for DateTypeOption { impl CellDataOperation<EncodedCellData<DateCellDataSerde>, DateCellDataSerde> for DateTypeOption {
fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { fn decode_cell_data<T>(
if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { &self,
encoded_data: T,
decoded_field_type: &FieldType,
_field_meta: &FieldMeta,
) -> FlowyResult<DecodedCellData>
where
T: Into<EncodedCellData<DateCellDataSerde>>,
{
// Return default data if the type_option_cell_data is not FieldType::DateTime. // Return default data if the type_option_cell_data is not FieldType::DateTime.
// It happens when switching from one field to another. // It happens when switching from one field to another.
// For example: // For example:
// FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen. // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen.
if !type_option_cell_data.is_date() { if !decoded_field_type.is_date() {
return DecodedCellData::default(); return Ok(DecodedCellData::default());
}
return match DateCellDataSerde::from_str(&type_option_cell_data.data) {
Ok(serde_cell_data) => self.decode_cell_data_from_timestamp(&serde_cell_data),
Err(_) => DecodedCellData::default(),
};
} }
DecodedCellData::default() let encoded_data = encoded_data.into().try_into_inner()?;
Ok(self.decode_cell_data_from_timestamp(&encoded_data))
} }
fn apply_changeset<T: Into<CellContentChangeset>>( fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<DateCellDataSerde, FlowyError>
&self, where
changeset: T, C: Into<CellContentChangeset>,
_cell_meta: Option<CellMeta>, {
) -> Result<String, FlowyError> {
let content_changeset: DateCellContentChangeset = serde_json::from_str(&changeset.into())?; let content_changeset: DateCellContentChangeset = serde_json::from_str(&changeset.into())?;
let cell_data = match content_changeset.date_timestamp() { let cell_data = match content_changeset.date_timestamp() {
None => DateCellDataSerde::default(), None => DateCellDataSerde::default(),
@ -173,7 +177,7 @@ impl CellDataOperation for DateTypeOption {
}, },
}; };
Ok(TypeOptionCellData::new(cell_data.to_string(), self.field_type()).json()) Ok(cell_data)
} }
} }
@ -307,23 +311,29 @@ impl DateCellDataSerde {
fn new(timestamp: i64, time: Option<String>, time_format: &TimeFormat) -> Self { fn new(timestamp: i64, time: Option<String>, time_format: &TimeFormat) -> Self {
Self { Self {
timestamp, timestamp,
time: Some(time.unwrap_or(default_time_str(time_format))), time: Some(time.unwrap_or_else(|| default_time_str(time_format))),
} }
} }
pub(crate) fn from_timestamp(timestamp: i64, time: Option<String>) -> Self { pub(crate) fn from_timestamp(timestamp: i64, time: Option<String>) -> Self {
Self { timestamp, time } Self { timestamp, time }
} }
fn to_string(self) -> String {
serde_json::to_string(&self).unwrap_or("".to_string())
} }
fn from_str(s: &str) -> FlowyResult<Self> { impl FromStr for DateCellDataSerde {
type Err = FlowyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str::<DateCellDataSerde>(s).map_err(internal_error) serde_json::from_str::<DateCellDataSerde>(s).map_err(internal_error)
} }
} }
impl ToString for DateCellDataSerde {
fn to_string(&self) -> String {
serde_json::to_string(&self).unwrap_or_else(|_| "".to_string())
}
}
fn default_time_str(time_format: &TimeFormat) -> String { fn default_time_str(time_format: &TimeFormat) -> String {
match time_format { match time_format {
TimeFormat::TwelveHour => "12:00 AM".to_string(), TimeFormat::TwelveHour => "12:00 AM".to_string(),
@ -407,52 +417,64 @@ impl std::convert::From<DateCellContentChangeset> for CellContentChangeset {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::services::field::FieldBuilder; use crate::services::field::FieldBuilder;
use crate::services::field::{ use crate::services::field::{DateCellContentChangeset, DateCellDataSerde, DateFormat, DateTypeOption, TimeFormat};
DateCellContentChangeset, DateCellData, DateCellDataSerde, DateFormat, DateTypeOption, TimeFormat, use crate::services::row::{
apply_cell_data_changeset, decode_cell_data_from_type_option_cell_data, CellDataOperation, EncodedCellData,
}; };
use crate::services::row::{CellDataOperation, TypeOptionCellData}; use flowy_grid_data_model::entities::{FieldMeta, FieldType};
use flowy_grid_data_model::entities::FieldType;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
#[test] #[test]
fn date_description_invalid_input_test() { fn date_description_invalid_input_test() {
let type_option = DateTypeOption::default();
let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
let data = apply_cell_data_changeset("1e", None, &field_meta).unwrap();
assert_eq!( assert_eq!(
"".to_owned(), decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
type_option.decode_cell_data("1e".to_owned(), &field_meta).content "".to_owned()
); );
} }
#[test] #[test]
fn date_description_date_format_test() { fn date_description_date_format_test() {
let mut type_option = DateTypeOption::default(); let mut type_option = DateTypeOption::default();
let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
for date_format in DateFormat::iter() { for date_format in DateFormat::iter() {
type_option.date_format = date_format; type_option.date_format = date_format;
match date_format { match date_format {
DateFormat::Friendly => { DateFormat::Friendly => {
assert_eq!( assert_eq!(
"Mar 14,2022".to_owned(), "Mar 14,2022".to_owned(),
type_option.decode_cell_data(data(1647251762), &field_meta).content type_option
.decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
.unwrap()
.content
); );
} }
DateFormat::US => { DateFormat::US => {
assert_eq!( assert_eq!(
"2022/03/14".to_owned(), "2022/03/14".to_owned(),
type_option.decode_cell_data(data(1647251762), &field_meta).content type_option
.decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
.unwrap()
.content
); );
} }
DateFormat::ISO => { DateFormat::ISO => {
assert_eq!( assert_eq!(
"2022-03-14".to_owned(), "2022-03-14".to_owned(),
type_option.decode_cell_data(data(1647251762), &field_meta).content type_option
.decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
.unwrap()
.content
); );
} }
DateFormat::Local => { DateFormat::Local => {
assert_eq!( assert_eq!(
"2022/03/14".to_owned(), "2022/03/14".to_owned(),
type_option.decode_cell_data(data(1647251762), &field_meta).content type_option
.decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
.unwrap()
.content
); );
} }
} }
@ -462,7 +484,7 @@ mod tests {
#[test] #[test]
fn date_description_time_format_test() { fn date_description_time_format_test() {
let mut type_option = DateTypeOption::default(); let mut type_option = DateTypeOption::default();
let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
for time_format in TimeFormat::iter() { for time_format in TimeFormat::iter() {
type_option.time_format = time_format; type_option.time_format = time_format;
match time_format { match time_format {
@ -473,7 +495,10 @@ mod tests {
); );
assert_eq!( assert_eq!(
"Mar 14,2022".to_owned(), "Mar 14,2022".to_owned(),
type_option.decode_cell_data(data(1647251762), &field_meta).content type_option
.decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
.unwrap()
.content
); );
} }
TimeFormat::TwelveHour => { TimeFormat::TwelveHour => {
@ -483,7 +508,10 @@ mod tests {
); );
assert_eq!( assert_eq!(
"Mar 14,2022".to_owned(), "Mar 14,2022".to_owned(),
type_option.decode_cell_data(data(1647251762), &field_meta).content type_option
.decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
.unwrap()
.content
); );
} }
} }
@ -493,53 +521,68 @@ mod tests {
#[test] #[test]
fn date_description_time_format_test2() { fn date_description_time_format_test2() {
let mut type_option = DateTypeOption::default(); let mut type_option = DateTypeOption::default();
let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); let field_type = FieldType::DateTime;
let field_meta = FieldBuilder::from_field_type(&field_type).build();
for time_format in TimeFormat::iter() { for time_format in TimeFormat::iter() {
type_option.time_format = time_format; type_option.time_format = time_format;
type_option.include_time = true; type_option.include_time = true;
match time_format { match time_format {
TimeFormat::TwentyFourHour => { TimeFormat::TwentyFourHour => {
let changeset = DateCellContentChangeset { assert_result(
&type_option,
DateCellContentChangeset {
date: Some(1653609600.to_string()), date: Some(1653609600.to_string()),
time: None, time: None,
}; },
let result = type_option.apply_changeset(changeset, None).unwrap(); &field_type,
let content = type_option.decode_cell_data(result, &field_meta).content; &field_meta,
assert_eq!("May 27,2022 00:00".to_owned(), content); "May 27,2022 00:00",
);
let changeset = DateCellContentChangeset { assert_result(
&type_option,
DateCellContentChangeset {
date: Some(1653609600.to_string()), date: Some(1653609600.to_string()),
time: Some("23:00".to_owned()), time: Some("23:00".to_owned()),
}; },
&field_type,
let result = type_option.apply_changeset(changeset, None).unwrap(); &field_meta,
let content = type_option.decode_cell_data(result, &field_meta).content; "May 27,2022 23:00",
assert_eq!("May 27,2022 23:00".to_owned(), content); );
} }
TimeFormat::TwelveHour => { TimeFormat::TwelveHour => {
let changeset = DateCellContentChangeset { assert_result(
&type_option,
DateCellContentChangeset {
date: Some(1653609600.to_string()), date: Some(1653609600.to_string()),
time: None, time: None,
}; },
let result = type_option.apply_changeset(changeset, None).unwrap(); &field_type,
let content = type_option.decode_cell_data(result, &field_meta).content; &field_meta,
assert_eq!("May 27,2022 12:00 AM".to_owned(), content); "May 27,2022 12:00 AM",
);
let changeset = DateCellContentChangeset { assert_result(
&type_option,
DateCellContentChangeset {
date: Some(1653609600.to_string()), date: Some(1653609600.to_string()),
time: Some("".to_owned()), time: Some("".to_owned()),
}; },
let result = type_option.apply_changeset(changeset, None).unwrap(); &field_type,
let content = type_option.decode_cell_data(result, &field_meta).content; &field_meta,
assert_eq!("May 27,2022".to_owned(), content); "May 27,2022",
);
let changeset = DateCellContentChangeset { assert_result(
&type_option,
DateCellContentChangeset {
date: Some(1653609600.to_string()), date: Some(1653609600.to_string()),
time: Some("11:23 pm".to_owned()), time: Some("11:23 pm".to_owned()),
}; },
let result = type_option.apply_changeset(changeset, None).unwrap(); &field_type,
let content = type_option.decode_cell_data(result, &field_meta).content; &field_meta,
assert_eq!("May 27,2022 11:23 PM".to_owned(), content); "May 27,2022 11:23 PM",
);
} }
} }
} }
@ -548,37 +591,55 @@ mod tests {
#[test] #[test]
fn date_description_apply_changeset_test() { fn date_description_apply_changeset_test() {
let mut type_option = DateTypeOption::default(); let mut type_option = DateTypeOption::default();
let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); let field_type = FieldType::DateTime;
let field_meta = FieldBuilder::from_field_type(&field_type).build();
let date_timestamp = "1653609600".to_owned(); let date_timestamp = "1653609600".to_owned();
let changeset = DateCellContentChangeset { assert_result(
&type_option,
DateCellContentChangeset {
date: Some(date_timestamp.clone()), date: Some(date_timestamp.clone()),
time: None, time: None,
}; },
let result = type_option.apply_changeset(changeset, None).unwrap(); &field_type,
let content = type_option.decode_cell_data(result.clone(), &field_meta).content; &field_meta,
assert_eq!(content, "May 27,2022".to_owned()); "May 27,2022",
);
type_option.include_time = true; type_option.include_time = true;
let content = type_option.decode_cell_data(result, &field_meta).content; assert_result(
assert_eq!(content, "May 27,2022 00:00".to_owned()); &type_option,
DateCellContentChangeset {
date: Some(date_timestamp.clone()),
time: None,
},
&field_type,
&field_meta,
"May 27,2022 00:00",
);
let changeset = DateCellContentChangeset { assert_result(
&type_option,
DateCellContentChangeset {
date: Some(date_timestamp.clone()), date: Some(date_timestamp.clone()),
time: Some("1:00".to_owned()), time: Some("1:00".to_owned()),
}; },
let result = type_option.apply_changeset(changeset, None).unwrap(); &field_type,
let content = type_option.decode_cell_data(result, &field_meta).content; &field_meta,
assert_eq!(content, "May 27,2022 01:00".to_owned()); "May 27,2022 01:00",
);
let changeset = DateCellContentChangeset { type_option.time_format = TimeFormat::TwelveHour;
assert_result(
&type_option,
DateCellContentChangeset {
date: Some(date_timestamp), date: Some(date_timestamp),
time: Some("1:00 am".to_owned()), time: Some("1:00 am".to_owned()),
}; },
type_option.time_format = TimeFormat::TwelveHour; &field_type,
let result = type_option.apply_changeset(changeset, None).unwrap(); &field_meta,
let content = type_option.decode_cell_data(result, &field_meta).content; "May 27,2022 01:00 AM",
assert_eq!(content, "May 27,2022 01:00 AM".to_owned()); );
} }
#[test] #[test]
@ -586,7 +647,7 @@ mod tests {
fn date_description_apply_changeset_error_test() { fn date_description_apply_changeset_error_test() {
let mut type_option = DateTypeOption::default(); let mut type_option = DateTypeOption::default();
type_option.include_time = true; type_option.include_time = true;
let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); let _field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
let date_timestamp = "1653609600".to_owned(); let date_timestamp = "1653609600".to_owned();
let changeset = DateCellContentChangeset { let changeset = DateCellContentChangeset {
@ -596,7 +657,7 @@ mod tests {
let _ = type_option.apply_changeset(changeset, None).unwrap(); let _ = type_option.apply_changeset(changeset, None).unwrap();
let changeset = DateCellContentChangeset { let changeset = DateCellContentChangeset {
date: Some(date_timestamp.clone()), date: Some(date_timestamp),
time: Some("1:".to_owned()), time: Some("1:".to_owned()),
}; };
let _ = type_option.apply_changeset(changeset, None).unwrap(); let _ = type_option.apply_changeset(changeset, None).unwrap();
@ -610,7 +671,21 @@ mod tests {
} }
fn data(s: i64) -> String { fn data(s: i64) -> String {
let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(s, None)).unwrap(); serde_json::to_string(&DateCellDataSerde::from_timestamp(s, None)).unwrap()
TypeOptionCellData::new(&json, FieldType::DateTime).json() }
fn assert_result(
type_option: &DateTypeOption,
changeset: DateCellContentChangeset,
field_type: &FieldType,
field_meta: &FieldMeta,
expected: &str,
) {
let encoded_data = EncodedCellData(Some(type_option.apply_changeset(changeset, None).unwrap()));
let content = type_option
.decode_cell_data(encoded_data, field_type, field_meta)
.unwrap()
.content;
assert_eq!(expected.to_owned(), content);
} }
} }

View File

@ -1,9 +1,9 @@
use crate::impl_type_option; use crate::impl_type_option;
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData};
use bytes::Bytes; use bytes::Bytes;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::FlowyError; use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::entities::{ use flowy_grid_data_model::entities::{
CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
}; };
@ -76,45 +76,48 @@ pub struct NumberTypeOption {
} }
impl_type_option!(NumberTypeOption, FieldType::Number); impl_type_option!(NumberTypeOption, FieldType::Number);
impl CellDataOperation for NumberTypeOption { impl CellDataOperation<String, String> for NumberTypeOption {
fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { fn decode_cell_data<T>(
if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { &self,
if type_option_cell_data.is_date() { encoded_data: T,
return DecodedCellData::default(); decoded_field_type: &FieldType,
_field_meta: &FieldMeta,
) -> FlowyResult<DecodedCellData>
where
T: Into<String>,
{
if decoded_field_type.is_date() {
return Ok(DecodedCellData::default());
} }
let cell_data = type_option_cell_data.data; let cell_data = encoded_data.into();
match self.format { match self.format {
NumberFormat::Number => { NumberFormat::Number => {
if let Ok(v) = cell_data.parse::<f64>() { if let Ok(v) = cell_data.parse::<f64>() {
return DecodedCellData::from_content(v.to_string()); return Ok(DecodedCellData::from_content(v.to_string()));
} }
if let Ok(v) = cell_data.parse::<i64>() { if let Ok(v) = cell_data.parse::<i64>() {
return DecodedCellData::from_content(v.to_string()); return Ok(DecodedCellData::from_content(v.to_string()));
} }
DecodedCellData::default() Ok(DecodedCellData::default())
} }
NumberFormat::Percent => { NumberFormat::Percent => {
let content = cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string()); let content = cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string());
DecodedCellData::from_content(content) Ok(DecodedCellData::from_content(content))
} }
_ => { _ => {
let content = self.money_from_str(&cell_data); let content = self.money_from_str(&cell_data);
DecodedCellData::from_content(content) Ok(DecodedCellData::from_content(content))
} }
} }
} else {
DecodedCellData::default()
}
} }
fn apply_changeset<T: Into<CellContentChangeset>>( fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
&self, where
changeset: T, C: Into<CellContentChangeset>,
_cell_meta: Option<CellMeta>, {
) -> Result<String, FlowyError> {
let changeset = changeset.into(); let changeset = changeset.into();
let mut data = changeset.trim().to_string(); let mut data = changeset.trim().to_string();
@ -125,7 +128,7 @@ impl CellDataOperation for NumberTypeOption {
} }
} }
Ok(TypeOptionCellData::new(&data, self.field_type()).json()) Ok(data)
} }
} }
@ -619,28 +622,24 @@ fn make_strip_symbol() -> Vec<String> {
mod tests { mod tests {
use crate::services::field::FieldBuilder; use crate::services::field::FieldBuilder;
use crate::services::field::{NumberFormat, NumberTypeOption}; use crate::services::field::{NumberFormat, NumberTypeOption};
use crate::services::row::{CellDataOperation, TypeOptionCellData}; use crate::services::row::CellDataOperation;
use flowy_grid_data_model::entities::FieldType; use flowy_grid_data_model::entities::{FieldMeta, FieldType};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
#[test] #[test]
fn number_description_invalid_input_test() { fn number_description_invalid_input_test() {
let type_option = NumberTypeOption::default(); let type_option = NumberTypeOption::default();
let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); let field_type = FieldType::Number;
assert_eq!( let field_meta = FieldBuilder::from_field_type(&field_type).build();
"".to_owned(), assert_equal(&type_option, "", "", &field_type, &field_meta);
type_option.decode_cell_data(data(""), &field_meta).content assert_equal(&type_option, "abc", "", &field_type, &field_meta);
);
assert_eq!(
"".to_owned(),
type_option.decode_cell_data(data("abc"), &field_meta).content
);
} }
#[test] #[test]
fn number_description_test() { fn number_description_test() {
let mut type_option = NumberTypeOption::default(); let mut type_option = NumberTypeOption::default();
let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); let field_type = FieldType::Number;
let field_meta = FieldBuilder::from_field_type(&field_type).build();
assert_eq!(type_option.strip_symbol("¥18,443"), "18443".to_owned()); assert_eq!(type_option.strip_symbol("¥18,443"), "18443".to_owned());
assert_eq!(type_option.strip_symbol("$18,443"), "18443".to_owned()); assert_eq!(type_option.strip_symbol("$18,443"), "18443".to_owned());
assert_eq!(type_option.strip_symbol("€18.443"), "18443".to_owned()); assert_eq!(type_option.strip_symbol("€18.443"), "18443".to_owned());
@ -649,86 +648,50 @@ mod tests {
type_option.format = format; type_option.format = format;
match format { match format {
NumberFormat::Number => { NumberFormat::Number => {
assert_eq!( assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
type_option.decode_cell_data(data("18443"), &field_meta).content,
"18443".to_owned()
);
} }
NumberFormat::USD => { NumberFormat::USD => {
assert_eq!( assert_equal(&type_option, "18443", "$18,443", &field_type, &field_meta);
type_option.decode_cell_data(data("18443"), &field_meta).content, assert_equal(&type_option, "", "", &field_type, &field_meta);
"$18,443".to_owned() assert_equal(&type_option, "abc", "", &field_type, &field_meta);
);
assert_eq!(
type_option.decode_cell_data(data(""), &field_meta).content,
"".to_owned()
);
assert_eq!(
type_option.decode_cell_data(data("abc"), &field_meta).content,
"".to_owned()
);
} }
NumberFormat::Yen => { NumberFormat::Yen => {
assert_eq!( assert_equal(&type_option, "18443", "¥18,443", &field_type, &field_meta);
type_option.decode_cell_data(data("18443"), &field_meta).content,
"¥18,443".to_owned()
);
} }
NumberFormat::Yuan => { NumberFormat::Yuan => {
assert_eq!( assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_meta);
type_option.decode_cell_data(data("18443"), &field_meta).content,
"CN¥18,443".to_owned()
);
} }
NumberFormat::EUR => { NumberFormat::EUR => {
assert_eq!( assert_equal(&type_option, "18443", "€18.443", &field_type, &field_meta);
type_option.decode_cell_data(data("18443"), &field_meta).content,
"€18.443".to_owned()
);
} }
_ => {} _ => {}
} }
} }
} }
fn data(s: &str) -> String {
TypeOptionCellData::new(s, FieldType::Number).json()
}
#[test] #[test]
fn number_description_scale_test() { fn number_description_scale_test() {
let mut type_option = NumberTypeOption { let mut type_option = NumberTypeOption {
scale: 1, scale: 1,
..Default::default() ..Default::default()
}; };
let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); let field_type = FieldType::Number;
let field_meta = FieldBuilder::from_field_type(&field_type).build();
for format in NumberFormat::iter() { for format in NumberFormat::iter() {
type_option.format = format; type_option.format = format;
match format { match format {
NumberFormat::Number => { NumberFormat::Number => {
assert_eq!( assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
type_option.decode_cell_data(data("18443"), &field_meta).content,
"18443".to_owned()
);
} }
NumberFormat::USD => { NumberFormat::USD => {
assert_eq!( assert_equal(&type_option, "18443", "$1,844.3", &field_type, &field_meta);
type_option.decode_cell_data(data("18443"), &field_meta).content,
"$1,844.3".to_owned()
);
} }
NumberFormat::Yen => { NumberFormat::Yen => {
assert_eq!( assert_equal(&type_option, "18443", "¥1,844.3", &field_type, &field_meta);
type_option.decode_cell_data(data("18443"), &field_meta).content,
"¥1,844.3".to_owned()
);
} }
NumberFormat::EUR => { NumberFormat::EUR => {
assert_eq!( assert_equal(&type_option, "18443", "€1.844,3", &field_type, &field_meta);
type_option.decode_cell_data(data("18443"), &field_meta).content,
"€1.844,3".to_owned()
);
} }
_ => {} _ => {}
} }
@ -741,37 +704,46 @@ mod tests {
sign_positive: false, sign_positive: false,
..Default::default() ..Default::default()
}; };
let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); let field_type = FieldType::Number;
let field_meta = FieldBuilder::from_field_type(&field_type).build();
for format in NumberFormat::iter() { for format in NumberFormat::iter() {
type_option.format = format; type_option.format = format;
match format { match format {
NumberFormat::Number => { NumberFormat::Number => {
assert_eq!( assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
type_option.decode_cell_data(data("18443"), &field_meta).content,
"18443".to_owned()
);
} }
NumberFormat::USD => { NumberFormat::USD => {
assert_eq!( assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_meta);
type_option.decode_cell_data(data("18443"), &field_meta).content,
"-$18,443".to_owned()
);
} }
NumberFormat::Yen => { NumberFormat::Yen => {
assert_eq!( assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_meta);
type_option.decode_cell_data(data("18443"), &field_meta).content,
"-¥18,443".to_owned()
);
} }
NumberFormat::EUR => { NumberFormat::EUR => {
assert_eq!( assert_equal(&type_option, "18443", "-€18.443", &field_type, &field_meta);
type_option.decode_cell_data(data("18443"), &field_meta).content,
"-€18.443".to_owned()
);
} }
_ => {} _ => {}
} }
} }
} }
fn assert_equal(
type_option: &NumberTypeOption,
cell_data: &str,
expected_str: &str,
field_type: &FieldType,
field_meta: &FieldMeta,
) {
assert_eq!(
type_option
.decode_cell_data(data(cell_data), field_type, field_meta)
.unwrap()
.content,
expected_str.to_owned()
);
}
fn data(s: &str) -> String {
s.to_owned()
}
} }

View File

@ -1,5 +1,5 @@
use crate::entities::{CellIdentifier, CellIdentifierPayload};
use crate::impl_type_option; use crate::impl_type_option;
use crate::services::entities::{CellIdentifier, CellIdentifierPayload};
use crate::services::field::type_options::util::get_cell_data; use crate::services::field::type_options::util::get_cell_data;
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData};
@ -95,29 +95,36 @@ impl SelectOptionOperation for SingleSelectTypeOption {
} }
} }
impl CellDataOperation for SingleSelectTypeOption { impl CellDataOperation<String, String> for SingleSelectTypeOption {
fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { fn decode_cell_data<T>(
if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { &self,
if !type_option_cell_data.is_single_select() { encoded_data: T,
return DecodedCellData::default(); decoded_field_type: &FieldType,
_field_meta: &FieldMeta,
) -> FlowyResult<DecodedCellData>
where
T: Into<String>,
{
if !decoded_field_type.is_select_option() {
return Ok(DecodedCellData::default());
} }
if let Some(option_id) = select_option_ids(type_option_cell_data.data).first() { let cell_data = encoded_data.into();
return match self.options.iter().find(|option| &option.id == option_id) { if let Some(option_id) = select_option_ids(cell_data).first() {
let data = match self.options.iter().find(|option| &option.id == option_id) {
None => DecodedCellData::default(), None => DecodedCellData::default(),
Some(option) => DecodedCellData::from_content(option.name.clone()), Some(option) => DecodedCellData::from_content(option.name.clone()),
}; };
Ok(data)
} else {
Ok(DecodedCellData::default())
} }
} }
DecodedCellData::default() fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
} where
C: Into<CellContentChangeset>,
fn apply_changeset<T: Into<CellContentChangeset>>( {
&self,
changeset: T,
_cell_meta: Option<CellMeta>,
) -> Result<String, FlowyError> {
let changeset = changeset.into(); let changeset = changeset.into();
let select_option_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset)?; let select_option_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset)?;
let new_cell_data: String; let new_cell_data: String;
@ -129,7 +136,7 @@ impl CellDataOperation for SingleSelectTypeOption {
new_cell_data = "".to_string() new_cell_data = "".to_string()
} }
Ok(TypeOptionCellData::new(&new_cell_data, self.field_type()).json()) Ok(new_cell_data)
} }
} }
@ -184,13 +191,21 @@ impl SelectOptionOperation for MultiSelectTypeOption {
} }
} }
impl CellDataOperation for MultiSelectTypeOption { impl CellDataOperation<String, String> for MultiSelectTypeOption {
fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { fn decode_cell_data<T>(
if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { &self,
if !type_option_cell_data.is_multi_select() { encoded_data: T,
return DecodedCellData::default(); decoded_field_type: &FieldType,
_field_meta: &FieldMeta,
) -> FlowyResult<DecodedCellData>
where
T: Into<String>,
{
if !decoded_field_type.is_select_option() {
return Ok(DecodedCellData::default());
} }
let option_ids = select_option_ids(type_option_cell_data.data); let cell_data = encoded_data.into();
let option_ids = select_option_ids(cell_data);
let content = self let content = self
.options .options
.iter() .iter()
@ -198,17 +213,14 @@ impl CellDataOperation for MultiSelectTypeOption {
.map(|option| option.name.clone()) .map(|option| option.name.clone())
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(SELECTION_IDS_SEPARATOR); .join(SELECTION_IDS_SEPARATOR);
DecodedCellData::from_content(content)
} else { Ok(DecodedCellData::from_content(content))
DecodedCellData::default()
}
} }
fn apply_changeset<T: Into<CellContentChangeset>>( fn apply_changeset<T>(&self, changeset: T, cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
&self, where
changeset: T, T: Into<CellContentChangeset>,
cell_meta: Option<CellMeta>, {
) -> Result<String, FlowyError> {
let content_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset.into())?; let content_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset.into())?;
let new_cell_data: String; let new_cell_data: String;
match cell_meta { match cell_meta {
@ -237,7 +249,7 @@ impl CellDataOperation for MultiSelectTypeOption {
} }
} }
Ok(TypeOptionCellData::new(&new_cell_data, self.field_type()).json()) Ok(new_cell_data)
} }
} }
@ -515,14 +527,20 @@ mod tests {
let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
let cell_data = type_option.apply_changeset(data, None).unwrap(); let cell_data = type_option.apply_changeset(data, None).unwrap();
assert_eq!( assert_eq!(
type_option.decode_cell_data(cell_data, &field_meta).content, type_option
.decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
.unwrap()
.content,
google_option.name, google_option.name,
); );
let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
let cell_data = type_option.apply_changeset(data, None).unwrap(); let cell_data = type_option.apply_changeset(data, None).unwrap();
assert_eq!( assert_eq!(
type_option.decode_cell_data(cell_data, &field_meta).content, type_option
.decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
.unwrap()
.content,
google_option.name, google_option.name,
); );
@ -530,13 +548,25 @@ mod tests {
let cell_data = type_option let cell_data = type_option
.apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
.unwrap(); .unwrap();
assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); assert_eq!(
type_option
.decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
.unwrap()
.content,
"",
);
// Invalid option id // Invalid option id
let cell_data = type_option let cell_data = type_option
.apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None) .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None)
.unwrap(); .unwrap();
assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); assert_eq!(
type_option
.decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
.unwrap()
.content,
"",
);
// Invalid changeset // Invalid changeset
assert!(type_option.apply_changeset("123", None).is_err()); assert!(type_option.apply_changeset("123", None).is_err());
@ -563,14 +593,20 @@ mod tests {
let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
let cell_data = type_option.apply_changeset(data, None).unwrap(); let cell_data = type_option.apply_changeset(data, None).unwrap();
assert_eq!( assert_eq!(
type_option.decode_cell_data(cell_data, &field_meta).content, type_option
.decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
.unwrap()
.content,
vec![google_option.name.clone(), facebook_option.name].join(SELECTION_IDS_SEPARATOR), vec![google_option.name.clone(), facebook_option.name].join(SELECTION_IDS_SEPARATOR),
); );
let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
let cell_data = type_option.apply_changeset(data, None).unwrap(); let cell_data = type_option.apply_changeset(data, None).unwrap();
assert_eq!( assert_eq!(
type_option.decode_cell_data(cell_data, &field_meta).content, type_option
.decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
.unwrap()
.content,
google_option.name, google_option.name,
); );
@ -578,13 +614,25 @@ mod tests {
let cell_data = type_option let cell_data = type_option
.apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
.unwrap(); .unwrap();
assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); assert_eq!(
type_option
.decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
.unwrap()
.content,
"",
);
// Invalid option id // Invalid option id
let cell_data = type_option let cell_data = type_option
.apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None) .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None)
.unwrap(); .unwrap();
assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); assert_eq!(
type_option
.decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
.unwrap()
.content,
"",
);
// Invalid changeset // Invalid changeset
assert!(type_option.apply_changeset("123", None).is_err()); assert!(type_option.apply_changeset("123", None).is_err());

View File

@ -1,16 +1,13 @@
use crate::impl_type_option; use crate::impl_type_option;
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use crate::services::row::{ use crate::services::row::{decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData};
decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData,
};
use bytes::Bytes; use bytes::Bytes;
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
use flowy_error::FlowyError; use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::entities::{ use flowy_grid_data_model::entities::{
CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Default)] #[derive(Default)]
pub struct RichTextTypeOptionBuilder(RichTextTypeOption); pub struct RichTextTypeOptionBuilder(RichTextTypeOption);
@ -34,33 +31,37 @@ pub struct RichTextTypeOption {
} }
impl_type_option!(RichTextTypeOption, FieldType::RichText); impl_type_option!(RichTextTypeOption, FieldType::RichText);
impl CellDataOperation for RichTextTypeOption { impl CellDataOperation<String, String> for RichTextTypeOption {
fn decode_cell_data(&self, data: String, field_meta: &FieldMeta) -> DecodedCellData { fn decode_cell_data<T>(
if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { &self,
if type_option_cell_data.is_date() encoded_data: T,
|| type_option_cell_data.is_single_select() decoded_field_type: &FieldType,
|| type_option_cell_data.is_multi_select() field_meta: &FieldMeta,
|| type_option_cell_data.is_number() ) -> FlowyResult<DecodedCellData>
where
T: Into<String>,
{ {
decode_cell_data(data, field_meta, &type_option_cell_data.field_type).unwrap_or_default() if decoded_field_type.is_date()
|| decoded_field_type.is_single_select()
|| decoded_field_type.is_multi_select()
|| decoded_field_type.is_number()
{
decode_cell_data(encoded_data, decoded_field_type, decoded_field_type, field_meta)
} else { } else {
DecodedCellData::from_content(type_option_cell_data.data) let cell_data = encoded_data.into();
} Ok(DecodedCellData::from_content(cell_data))
} else {
DecodedCellData::default()
} }
} }
fn apply_changeset<T: Into<CellContentChangeset>>( fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
&self, where
changeset: T, C: Into<CellContentChangeset>,
_cell_meta: Option<CellMeta>, {
) -> Result<String, FlowyError> {
let data = changeset.into(); let data = changeset.into();
if data.len() > 10000 { if data.len() > 10000 {
Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000")) Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000"))
} else { } else {
Ok(TypeOptionCellData::new(&data, self.field_type()).json()) Ok(data.0)
} }
} }
} }
@ -69,7 +70,7 @@ impl CellDataOperation for RichTextTypeOption {
mod tests { mod tests {
use crate::services::field::FieldBuilder; use crate::services::field::FieldBuilder;
use crate::services::field::*; use crate::services::field::*;
use crate::services::row::{CellDataOperation, TypeOptionCellData}; use crate::services::row::CellDataOperation;
use flowy_grid_data_model::entities::FieldType; use flowy_grid_data_model::entities::FieldType;
#[test] #[test]
@ -77,11 +78,14 @@ mod tests {
let type_option = RichTextTypeOption::default(); let type_option = RichTextTypeOption::default();
// date // date
let date_time_field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); let field_type = FieldType::DateTime;
let date_time_field_meta = FieldBuilder::from_field_type(&field_type).build();
let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(1647251762, None)).unwrap(); let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(1647251762, None)).unwrap();
let data = TypeOptionCellData::new(&json, FieldType::DateTime).json();
assert_eq!( assert_eq!(
type_option.decode_cell_data(data, &date_time_field_meta).content, type_option
.decode_cell_data(json, &field_type, &date_time_field_meta)
.unwrap()
.content,
"Mar 14,2022".to_owned() "Mar 14,2022".to_owned()
); );
@ -90,10 +94,11 @@ mod tests {
let done_option_id = done_option.id.clone(); let done_option_id = done_option.id.clone();
let single_select = SingleSelectTypeOptionBuilder::default().option(done_option); let single_select = SingleSelectTypeOptionBuilder::default().option(done_option);
let single_select_field_meta = FieldBuilder::new(single_select).build(); let single_select_field_meta = FieldBuilder::new(single_select).build();
let cell_data = TypeOptionCellData::new(&done_option_id, FieldType::SingleSelect).json();
assert_eq!( assert_eq!(
type_option type_option
.decode_cell_data(cell_data, &single_select_field_meta) .decode_cell_data(done_option_id, &FieldType::SingleSelect, &single_select_field_meta)
.unwrap()
.content, .content,
"Done".to_owned() "Done".to_owned()
); );
@ -111,7 +116,8 @@ mod tests {
let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap(); let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap();
assert_eq!( assert_eq!(
type_option type_option
.decode_cell_data(cell_data, &multi_select_field_meta) .decode_cell_data(cell_data, &FieldType::MultiSelect, &multi_select_field_meta)
.unwrap()
.content, .content,
"Google,Facebook".to_owned() "Google,Facebook".to_owned()
); );
@ -119,9 +125,11 @@ mod tests {
//Number //Number
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
let number_field_meta = FieldBuilder::new(number).build(); let number_field_meta = FieldBuilder::new(number).build();
let data = TypeOptionCellData::new("18443", FieldType::Number).json();
assert_eq!( assert_eq!(
type_option.decode_cell_data(data, &number_field_meta).content, type_option
.decode_cell_data("18443".to_owned(), &FieldType::Number, &number_field_meta)
.unwrap()
.content,
"$18,443".to_owned() "$18,443".to_owned()
); );
} }

View File

@ -1,7 +1,7 @@
use crate::dart_notification::{send_dart_notification, GridNotification}; use crate::dart_notification::{send_dart_notification, GridNotification};
use crate::entities::CellIdentifier;
use crate::manager::GridUser; use crate::manager::GridUser;
use crate::services::block_meta_manager::GridBlockMetaEditorManager; use crate::services::block_meta_manager::GridBlockMetaEditorManager;
use crate::services::entities::CellIdentifier;
use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder}; use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder};
use crate::services::persistence::block_index::BlockIndexPersistence; use crate::services::persistence::block_index::BlockIndexPersistence;
use crate::services::row::*; use crate::services::row::*;

View File

@ -2,7 +2,6 @@ mod util;
pub mod block_meta_editor; pub mod block_meta_editor;
mod block_meta_manager; mod block_meta_manager;
pub mod entities;
pub mod field; pub mod field;
pub mod grid_editor; pub mod grid_editor;
pub mod persistence; pub mod persistence;

View File

@ -1,20 +1,30 @@
use crate::services::field::*; use crate::services::field::*;
use flowy_error::FlowyError; use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_grid_data_model::entities::{CellMeta, FieldMeta, FieldType}; use flowy_grid_data_model::entities::{CellMeta, FieldMeta, FieldType};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Formatter; use std::fmt::Formatter;
use std::str::FromStr;
pub trait CellDataOperation { pub trait CellDataOperation<D, CO: ToString> {
fn decode_cell_data(&self, data: String, field_meta: &FieldMeta) -> DecodedCellData; fn decode_cell_data<T>(
fn apply_changeset<T: Into<CellContentChangeset>>(
&self, &self,
changeset: T, encoded_data: T,
decoded_field_type: &FieldType,
field_meta: &FieldMeta,
) -> FlowyResult<DecodedCellData>
where
T: Into<D>;
//
fn apply_changeset<C: Into<CellContentChangeset>>(
&self,
changeset: C,
cell_meta: Option<CellMeta>, cell_meta: Option<CellMeta>,
) -> Result<String, FlowyError>; ) -> FlowyResult<CO>;
} }
#[derive(Debug)] #[derive(Debug)]
pub struct CellContentChangeset(String); pub struct CellContentChangeset(pub String);
impl std::fmt::Display for CellContentChangeset { impl std::fmt::Display for CellContentChangeset {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
@ -43,6 +53,12 @@ pub struct TypeOptionCellData {
pub field_type: FieldType, pub field_type: FieldType,
} }
impl TypeOptionCellData {
pub fn split(self) -> (String, FieldType) {
(self.data, self.field_type)
}
}
impl std::str::FromStr for TypeOptionCellData { impl std::str::FromStr for TypeOptionCellData {
type Err = FlowyError; type Err = FlowyError;
@ -52,6 +68,14 @@ impl std::str::FromStr for TypeOptionCellData {
} }
} }
impl std::convert::TryInto<TypeOptionCellData> for String {
type Error = FlowyError;
fn try_into(self) -> Result<TypeOptionCellData, Self::Error> {
TypeOptionCellData::from_str(&self)
}
}
impl TypeOptionCellData { impl TypeOptionCellData {
pub fn new<T: ToString>(data: T, field_type: FieldType) -> Self { pub fn new<T: ToString>(data: T, field_type: FieldType) -> Self {
TypeOptionCellData { TypeOptionCellData {
@ -87,6 +111,10 @@ impl TypeOptionCellData {
pub fn is_multi_select(&self) -> bool { pub fn is_multi_select(&self) -> bool {
self.field_type == FieldType::MultiSelect self.field_type == FieldType::MultiSelect
} }
pub fn is_select_option(&self) -> bool {
self.field_type == FieldType::MultiSelect || self.field_type == FieldType::SingleSelect
}
} }
/// The changeset will be deserialized into specific data base on the FieldType. /// The changeset will be deserialized into specific data base on the FieldType.
@ -96,47 +124,109 @@ pub fn apply_cell_data_changeset<T: Into<CellContentChangeset>>(
cell_meta: Option<CellMeta>, cell_meta: Option<CellMeta>,
field_meta: &FieldMeta, field_meta: &FieldMeta,
) -> Result<String, FlowyError> { ) -> Result<String, FlowyError> {
match field_meta.field_type { let s = match field_meta.field_type {
FieldType::RichText => RichTextTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::RichText => RichTextTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
FieldType::Number => NumberTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::Number => NumberTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
FieldType::DateTime => DateTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::DateTime => DateTypeOption::from(field_meta)
.apply_changeset(changeset, cell_meta)
.map(|data| data.to_string()),
FieldType::SingleSelect => SingleSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::SingleSelect => SingleSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
FieldType::MultiSelect => MultiSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::MultiSelect => MultiSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
FieldType::Checkbox => CheckboxTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::Checkbox => CheckboxTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
}?;
Ok(TypeOptionCellData::new(s, field_meta.field_type.clone()).json())
}
pub fn decode_cell_data_from_type_option_cell_data<T: TryInto<TypeOptionCellData>>(
data: T,
field_meta: &FieldMeta,
field_type: &FieldType,
) -> DecodedCellData {
if let Ok(type_option_cell_data) = data.try_into() {
let (encoded_data, s_field_type) = type_option_cell_data.split();
decode_cell_data(encoded_data, &s_field_type, field_type, field_meta).unwrap_or_default()
} else {
DecodedCellData::default()
} }
} }
pub fn decode_cell_data(data: String, field_meta: &FieldMeta, field_type: &FieldType) -> Option<DecodedCellData> { pub fn decode_cell_data<T: Into<String>>(
let s = match field_type { encoded_data: T,
s_field_type: &FieldType,
t_field_type: &FieldType,
field_meta: &FieldMeta,
) -> FlowyResult<DecodedCellData> {
let encoded_data = encoded_data.into();
let get_cell_data = || {
let data = match t_field_type {
FieldType::RichText => field_meta FieldType::RichText => field_meta
.get_type_option_entry::<RichTextTypeOption>(field_type)? .get_type_option_entry::<RichTextTypeOption>(t_field_type)?
.decode_cell_data(data, field_meta), .decode_cell_data(encoded_data, s_field_type, field_meta),
FieldType::Number => field_meta FieldType::Number => field_meta
.get_type_option_entry::<NumberTypeOption>(field_type)? .get_type_option_entry::<NumberTypeOption>(t_field_type)?
.decode_cell_data(data, field_meta), .decode_cell_data(encoded_data, s_field_type, field_meta),
FieldType::DateTime => field_meta FieldType::DateTime => field_meta
.get_type_option_entry::<DateTypeOption>(field_type)? .get_type_option_entry::<DateTypeOption>(t_field_type)?
.decode_cell_data(data, field_meta), .decode_cell_data(encoded_data, s_field_type, field_meta),
FieldType::SingleSelect => field_meta FieldType::SingleSelect => field_meta
.get_type_option_entry::<SingleSelectTypeOption>(field_type)? .get_type_option_entry::<SingleSelectTypeOption>(t_field_type)?
.decode_cell_data(data, field_meta), .decode_cell_data(encoded_data, s_field_type, field_meta),
FieldType::MultiSelect => field_meta FieldType::MultiSelect => field_meta
.get_type_option_entry::<MultiSelectTypeOption>(field_type)? .get_type_option_entry::<MultiSelectTypeOption>(t_field_type)?
.decode_cell_data(data, field_meta), .decode_cell_data(encoded_data, s_field_type, field_meta),
FieldType::Checkbox => field_meta FieldType::Checkbox => field_meta
.get_type_option_entry::<CheckboxTypeOption>(field_type)? .get_type_option_entry::<CheckboxTypeOption>(t_field_type)?
.decode_cell_data(data, field_meta), .decode_cell_data(encoded_data, s_field_type, field_meta),
}; };
Some(data)
};
match get_cell_data() {
Some(Ok(data)) => {
tracing::Span::current().record( tracing::Span::current().record(
"content", "content",
&format!("{:?}: {}", field_meta.field_type, s.content).as_str(), &format!("{:?}: {}", field_meta.field_type, data.content).as_str(),
); );
Some(s) Ok(data)
}
Some(Err(err)) => {
tracing::error!("{:?}", err);
Ok(DecodedCellData::default())
}
None => Ok(DecodedCellData::default()),
}
}
pub(crate) struct EncodedCellData<T>(pub Option<T>);
impl<T> EncodedCellData<T> {
pub fn try_into_inner(self) -> FlowyResult<T> {
match self.0 {
None => Err(ErrorCode::InvalidData.into()),
Some(data) => Ok(data),
}
}
}
impl<T> std::convert::From<String> for EncodedCellData<T>
where
T: FromStr<Err = FlowyError>,
{
fn from(s: String) -> Self {
match T::from_str(&s) {
Ok(inner) => EncodedCellData(Some(inner)),
Err(e) => {
tracing::error!("Deserialize Cell Data failed: {}", e);
EncodedCellData(None)
}
}
}
} }
#[derive(Default)] #[derive(Default)]
pub struct DecodedCellData { pub struct DecodedCellData {
pub raw: String, raw: String,
pub content: String, pub content: String,
} }

View File

@ -1,4 +1,4 @@
use crate::services::row::decode_cell_data; use crate::services::row::decode_cell_data_from_type_option_cell_data;
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
use flowy_grid_data_model::entities::{ use flowy_grid_data_model::entities::{
Cell, CellMeta, FieldMeta, GridBlock, GridBlockOrder, RepeatedGridBlock, Row, RowMeta, RowOrder, Cell, CellMeta, FieldMeta, GridBlock, GridBlockOrder, RepeatedGridBlock, Row, RowMeta, RowOrder,
@ -31,14 +31,16 @@ pub fn make_cell_by_field_id(
cell_meta: CellMeta, cell_meta: CellMeta,
) -> Option<(String, Cell)> { ) -> Option<(String, Cell)> {
let field_meta = field_map.get(&field_id)?; let field_meta = field_map.get(&field_id)?;
let (raw, content) = decode_cell_data(cell_meta.data, field_meta, &field_meta.field_type)?.split(); let (raw, content) =
decode_cell_data_from_type_option_cell_data(cell_meta.data, field_meta, &field_meta.field_type).split();
let cell = Cell::new(&field_id, content, raw); let cell = Cell::new(&field_id, content, raw);
Some((field_id, cell)) Some((field_id, cell))
} }
pub fn make_cell(field_id: &str, field_meta: &FieldMeta, row_meta: &RowMeta) -> Option<Cell> { pub fn make_cell(field_id: &str, field_meta: &FieldMeta, row_meta: &RowMeta) -> Option<Cell> {
let cell_meta = row_meta.cells.get(field_id)?.clone(); let cell_meta = row_meta.cells.get(field_id)?.clone();
let (raw, content) = decode_cell_data(cell_meta.data, field_meta, &field_meta.field_type)?.split(); let (raw, content) =
decode_cell_data_from_type_option_cell_data(cell_meta.data, field_meta, &field_meta.field_type).split();
Some(Cell::new(field_id, content, raw)) Some(Cell::new(field_id, content, raw))
} }

View File

@ -5,7 +5,7 @@ use flowy_grid::services::field::{
DateCellContentChangeset, MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset, DateCellContentChangeset, MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset,
SingleSelectTypeOption, SELECTION_IDS_SEPARATOR, SingleSelectTypeOption, SELECTION_IDS_SEPARATOR,
}; };
use flowy_grid::services::row::{decode_cell_data, CreateRowMetaBuilder}; use flowy_grid::services::row::{decode_cell_data_from_type_option_cell_data, CreateRowMetaBuilder};
use flowy_grid_data_model::entities::{ use flowy_grid_data_model::entities::{
CellChangeset, FieldChangesetParams, FieldType, GridBlockMeta, GridBlockMetaChangeset, RowMetaChangeset, CellChangeset, FieldChangesetParams, FieldType, GridBlockMeta, GridBlockMetaChangeset, RowMetaChangeset,
TypeOptionDataEntry, TypeOptionDataEntry,
@ -291,8 +291,7 @@ async fn grid_row_add_date_cell_test() {
let date_field = date_field.unwrap(); let date_field = date_field.unwrap();
let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone(); let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone();
assert_eq!( assert_eq!(
decode_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type) decode_cell_data_from_type_option_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type)
.unwrap()
.split() .split()
.1, .1,
"2022/03/16", "2022/03/16",

View File

@ -912,6 +912,34 @@ impl FieldType {
_ => 150, _ => 150,
} }
} }
pub fn is_number(&self) -> bool {
self == &FieldType::Number
}
pub fn is_text(&self) -> bool {
self == &FieldType::RichText
}
pub fn is_checkbox(&self) -> bool {
self == &FieldType::Checkbox
}
pub fn is_date(&self) -> bool {
self == &FieldType::DateTime
}
pub fn is_single_select(&self) -> bool {
self == &FieldType::SingleSelect
}
pub fn is_multi_select(&self) -> bool {
self == &FieldType::MultiSelect
}
pub fn is_select_option(&self) -> bool {
self == &FieldType::MultiSelect || self == &FieldType::SingleSelect
}
} }
#[derive(Debug, Clone, Default, ProtoBuf)] #[derive(Debug, Clone, Default, ProtoBuf)]

View File

@ -153,27 +153,22 @@ pub fn check_pb_dart_plugin() {
let output = Command::new("sh").arg("-c").arg("echo $PATH").output(); let output = Command::new("sh").arg("-c").arg("echo $PATH").output();
let paths = String::from_utf8(output.unwrap().stdout) let paths = String::from_utf8(output.unwrap().stdout)
.unwrap() .unwrap()
.split(":") .split(':')
.map(|s| s.to_string()) .map(|s| s.to_string())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
paths.iter().for_each(|s| msg.push_str(&format!("{}\n", s))); paths.iter().for_each(|s| msg.push_str(&format!("{}\n", s)));
match Command::new("sh").arg("-c").arg("which protoc-gen-dart").output() { if let Ok(output) = Command::new("sh").arg("-c").arg("which protoc-gen-dart").output() {
Ok(output) => {
msg.push_str(&format!( msg.push_str(&format!(
"Installed protoc-gen-dart path: {:?}\n", "Installed protoc-gen-dart path: {:?}\n",
String::from_utf8(output.stdout).unwrap() String::from_utf8(output.stdout).unwrap()
)); ));
} }
Err(_) => {}
}
msg.push_str(&format!("✅ You can fix that by adding:")); msg.push_str(&"✅ You can fix that by adding:".to_string());
msg.push_str(&format!("\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n",)); msg.push_str(&"\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n".to_string());
msg.push_str(&format!( msg.push_str(&"to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)".to_string());
"to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)"
));
panic!("{}", msg) panic!("{}", msg)
} }
} }