mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #505 from AppFlowy-IO/fix_0.0.4_bugs_2
Fix some UI bugs
This commit is contained in:
commit
1ca602fb72
@ -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",
|
||||||
|
@ -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) {
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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({
|
||||||
|
@ -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),
|
||||||
),
|
),
|
||||||
|
@ -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),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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({
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"]
|
@ -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;
|
@ -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};
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
@ -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()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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::*;
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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)]
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user