From 7950f8170b45d2a450e56b119984916e12509376 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sat, 21 May 2022 21:58:46 +0800 Subject: [PATCH 01/23] chore: optimaze error code --- .../application/grid/cell/date_cal_bloc.dart | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart index a0ba35f9d6..2e70ecddb2 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart @@ -1,6 +1,7 @@ import 'package:app_flowy/workspace/application/grid/field/field_service.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/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -91,7 +92,7 @@ class DateCalBloc extends Bloc { case ErrorCode.InvalidDateTimeFormat: emit(state.copyWith( dateData: Some(newDateData), - timeFormatError: Some(err.toString()), + timeFormatError: Some(messageFromFlowyError(err)), )); break; default: @@ -101,6 +102,18 @@ class DateCalBloc extends Bloc { ); } + String messageFromFlowyError(FlowyError error) { + switch (ErrorCode.valueOf(error.code)!) { + case ErrorCode.EmailFormatInvalid: + return state.copyWith(isSubmitting: false, emailError: some(error.msg), passwordError: none()); + case ErrorCode.PasswordFormatInvalid: + return state.copyWith(isSubmitting: false, passwordError: some(error.msg), emailError: none()); + default: + return state.copyWith(isSubmitting: false, successOrFail: some(right(error))); + } + return ""; + } + @override Future close() async { if (_onCellChangedFn != null) { From 36abd969ac0080483d1a015ec7c66d31e94507e5 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sun, 22 May 2022 13:08:23 +0800 Subject: [PATCH 02/23] chore: format error message of date cell --- .../app_flowy/assets/translations/en.json | 1 + .../application/grid/cell/date_cal_bloc.dart | 23 +++++++++++-------- .../src/widgets/cell/date_cell/date_cell.dart | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index 8755f05952..8fc9e90581 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -168,6 +168,7 @@ "dateFormatLocal": "Month/Month/Day", "dateFormatUS": "Month/Month/Day", "timeFormat": " Time format", + "invalidTimeFormat": "Invalid format", "timeFormatTwelveHour": "12 hour", "timeFormatTwentyFourHour": "24 hour", "addSelectOption": "Add an option", diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart index 2e70ecddb2..ff001eaa75 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart @@ -1,4 +1,6 @@ +import 'package:app_flowy/generated/locale_keys.g.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/protobuf/flowy-error-code/code.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; @@ -92,7 +94,7 @@ class DateCalBloc extends Bloc { case ErrorCode.InvalidDateTimeFormat: emit(state.copyWith( dateData: Some(newDateData), - timeFormatError: Some(messageFromFlowyError(err)), + timeFormatError: Some(timeFormatPrompt(err)), )); break; default: @@ -102,16 +104,19 @@ class DateCalBloc extends Bloc { ); } - String messageFromFlowyError(FlowyError error) { - switch (ErrorCode.valueOf(error.code)!) { - case ErrorCode.EmailFormatInvalid: - return state.copyWith(isSubmitting: false, emailError: some(error.msg), passwordError: none()); - case ErrorCode.PasswordFormatInvalid: - return state.copyWith(isSubmitting: false, passwordError: some(error.msg), emailError: none()); + 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: - return state.copyWith(isSubmitting: false, successOrFail: some(right(error))); + break; } - return ""; + return msg; } @override diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart index 5f0ccbc834..417c9f270e 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart @@ -18,7 +18,7 @@ abstract class GridCellDelegate { GridCellDelegate get delegate; } -class DateCell extends GridCellWidget { +class DateCell extends StatefulWidget with GridCellWidget { final GridCellContextBuilder cellContextBuilder; late final DateCellStyle? cellStyle; From 1b5b8f19d734e7577994d901bb66f626c0c279a7 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sun, 22 May 2022 13:08:38 +0800 Subject: [PATCH 03/23] chore: auto expand row detail page's cell --- .../grid/src/widgets/cell/cell_builder.dart | 4 +- .../grid/src/widgets/cell/checkbox_cell.dart | 4 +- .../grid/src/widgets/cell/number_cell.dart | 2 +- .../select_option_cell.dart | 6 +-- .../grid/src/widgets/cell/text_cell.dart | 2 +- .../grid/src/widgets/row/row_detail.dart | 45 ++++++++++--------- .../lib/style_widget/hover.dart | 29 ++++++------ .../lib/widget/rounded_input_field.dart | 9 ++-- frontend/rust-lib/flowy-grid/Flowy.toml | 2 +- .../{services => }/entities/cell_entities.rs | 2 +- .../{services => }/entities/field_entities.rs | 0 .../src/{services => }/entities/mod.rs | 0 .../{services => }/entities/row_entities.rs | 0 .../rust-lib/flowy-grid/src/event_handler.rs | 2 +- frontend/rust-lib/flowy-grid/src/lib.rs | 1 + .../field/type_options/date_type_option.rs | 2 +- .../type_options/selection_type_option.rs | 2 +- .../flowy-grid/src/services/grid_editor.rs | 2 +- .../rust-lib/flowy-grid/src/services/mod.rs | 1 - 19 files changed, 61 insertions(+), 54 deletions(-) rename frontend/rust-lib/flowy-grid/src/{services => }/entities/cell_entities.rs (96%) rename frontend/rust-lib/flowy-grid/src/{services => }/entities/field_entities.rs (100%) rename frontend/rust-lib/flowy-grid/src/{services => }/entities/mod.rs (100%) rename frontend/rust-lib/flowy-grid/src/{services => }/entities/row_entities.rs (100%) diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart index eab2903857..6f74d23ad0 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart @@ -47,13 +47,11 @@ class BlankCell extends StatelessWidget { } } -abstract class GridCellWidget extends HoverWidget { +abstract class GridCellWidget implements FlowyHoverWidget { @override final ValueNotifier onFocus = ValueNotifier(false); final GridCellRequestFocusNotifier requestFocus = GridCellRequestFocusNotifier(); - - GridCellWidget({Key? key}) : super(key: key); } class GridCellRequestFocusNotifier extends ChangeNotifier { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart index c4da2a223f..b2493d55ed 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart @@ -6,7 +6,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'cell_builder.dart'; -class CheckboxCell extends GridCellWidget { +class CheckboxCell extends StatefulWidget with GridCellWidget { final GridCellContextBuilder cellContextBuilder; CheckboxCell({ required this.cellContextBuilder, @@ -41,7 +41,7 @@ class _CheckboxCellState extends State { onPressed: () => context.read().add(const CheckboxCellEvent.select()), iconPadding: EdgeInsets.zero, icon: icon, - width: 23, + width: 20, ), ); }, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart index c0b3427e65..dc694f48f9 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart @@ -7,7 +7,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'cell_builder.dart'; -class NumberCell extends GridCellWidget { +class NumberCell extends StatefulWidget with GridCellWidget { final GridCellContextBuilder cellContextBuilder; NumberCell({ diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart index 12415c816d..c57a1865be 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart @@ -20,7 +20,7 @@ class SelectOptionCellStyle extends GridCellStyle { }); } -class SingleSelectCell extends GridCellWidget { +class SingleSelectCell extends StatefulWidget with GridCellWidget { final GridCellContextBuilder cellContextBuilder; late final SelectOptionCellStyle? cellStyle; @@ -74,7 +74,7 @@ class _SingleSelectCellState extends State { } //---------------------------------------------------------------- -class MultiSelectCell extends GridCellWidget { +class MultiSelectCell extends StatefulWidget with GridCellWidget { final GridCellContextBuilder cellContextBuilder; late final SelectOptionCellStyle? cellStyle; @@ -160,7 +160,7 @@ class _SelectOptionCell extends StatelessWidget { .toList(); child = Align( alignment: Alignment.centerLeft, - child: Wrap(children: tags, spacing: 4, runSpacing: 4), + child: Wrap(children: tags, spacing: 4, runSpacing: 2), ); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart index 14ace02d28..1563d41d86 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart @@ -13,7 +13,7 @@ class GridTextCellStyle extends GridCellStyle { }); } -class GridTextCell extends GridCellWidget { +class GridTextCell extends StatefulWidget with GridCellWidget { final GridCellContextBuilder cellContextBuilder; late final GridTextCellStyle? cellStyle; GridTextCell({ diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart index 41cbcb1cc1..0ce2b76e6b 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart @@ -71,10 +71,11 @@ class _RowDetailPageState extends State { child: Column( children: [ SizedBox( - height: 40, - child: Row( - children: const [Spacer(), _CloseButton()], - )), + height: 40, + child: Row( + children: const [Spacer(), _CloseButton()], + ), + ), Expanded(child: _PropertyList(cellCache: widget.cellCache)), ], ), @@ -153,24 +154,26 @@ class _RowDetailCell extends StatelessWidget { cellCache, style: _buildCellStyle(theme, gridCell.field.fieldType), ); - return SizedBox( - height: 36, - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 150, - child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)), - ), - const HSpace(10), - Expanded( - child: FlowyHover2( - child: cell, - contentPadding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), + return ConstrainedBox( + constraints: const BoxConstraints(minHeight: 40), + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 150, + child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)), ), - ), - ], + const HSpace(10), + Expanded( + child: FlowyHover2( + child: cell, + contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12), + ), + ), + ], + ), ), ); } diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart index 4f06a40b9b..bb7144974b 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart @@ -102,14 +102,14 @@ class FlowyHoverContainer extends StatelessWidget { } // -abstract class HoverWidget extends StatefulWidget { - const HoverWidget({Key? key}) : super(key: key); +abstract class FlowyHoverWidget extends Widget { + const FlowyHoverWidget({Key? key}) : super(key: key); - ValueNotifier get onFocus; + ValueNotifier? get onFocus; } class FlowyHover2 extends StatefulWidget { - final Widget child; + final FlowyHoverWidget child; final EdgeInsets contentPadding; const FlowyHover2({ required this.child, @@ -123,24 +123,30 @@ class FlowyHover2 extends StatefulWidget { class _FlowyHover2State extends State { late FlowyHoverState _hoverState; + VoidCallback? _listenerFn; @override void initState() { _hoverState = FlowyHoverState(); - if (widget.child is HoverWidget) { - final hoverWidget = widget.child as HoverWidget; - hoverWidget.onFocus.addListener(() { - _hoverState.onFocus = hoverWidget.onFocus.value; - }); + listener() { + _hoverState.onFocus = widget.child.onFocus?.value ?? false; } + _listenerFn = listener; + widget.child.onFocus?.addListener(listener); + super.initState(); } @override void dispose() { _hoverState.dispose(); + + if (_listenerFn != null) { + widget.child.onFocus?.removeListener(_listenerFn!); + _listenerFn = null; + } super.dispose(); } @@ -179,10 +185,7 @@ class _HoverBackground extends StatelessWidget { builder: (context, state, child) { if (state.onHover || state.onFocus) { return FlowyHoverContainer( - style: HoverStyle( - borderRadius: Corners.s6Border, - hoverColor: theme.shader6, - ), + style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6), ); } else { return const SizedBox(); diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart index 712fba3c6c..37eb782a2c 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart @@ -132,9 +132,12 @@ class _RoundedInputFieldState extends State { children.add( Align( alignment: Alignment.centerLeft, - child: Text( - widget.errorText, - style: widget.style, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Text( + widget.errorText, + style: widget.style, + ), ), ), ); diff --git a/frontend/rust-lib/flowy-grid/Flowy.toml b/frontend/rust-lib/flowy-grid/Flowy.toml index 1d0d2baea8..835988d41e 100644 --- a/frontend/rust-lib/flowy-grid/Flowy.toml +++ b/frontend/rust-lib/flowy-grid/Flowy.toml @@ -2,7 +2,7 @@ proto_crates = [ "src/event_map.rs", "src/services/field/type_options", - "src/services/entities", + "src/entities", "src/dart_notification.rs" ] event_files = ["src/event_map.rs"] \ No newline at end of file diff --git a/frontend/rust-lib/flowy-grid/src/services/entities/cell_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs similarity index 96% rename from frontend/rust-lib/flowy-grid/src/services/entities/cell_entities.rs rename to frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs index 2f408db992..4d09fe0eda 100644 --- a/frontend/rust-lib/flowy-grid/src/services/entities/cell_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs @@ -1,4 +1,4 @@ -use crate::services::entities::{FieldIdentifier, FieldIdentifierPayload}; +use crate::entities::{FieldIdentifier, FieldIdentifierPayload}; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; use flowy_grid_data_model::parser::NotEmptyStr; diff --git a/frontend/rust-lib/flowy-grid/src/services/entities/field_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs similarity index 100% rename from frontend/rust-lib/flowy-grid/src/services/entities/field_entities.rs rename to frontend/rust-lib/flowy-grid/src/entities/field_entities.rs diff --git a/frontend/rust-lib/flowy-grid/src/services/entities/mod.rs b/frontend/rust-lib/flowy-grid/src/entities/mod.rs similarity index 100% rename from frontend/rust-lib/flowy-grid/src/services/entities/mod.rs rename to frontend/rust-lib/flowy-grid/src/entities/mod.rs diff --git a/frontend/rust-lib/flowy-grid/src/services/entities/row_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs similarity index 100% rename from frontend/rust-lib/flowy-grid/src/services/entities/row_entities.rs rename to frontend/rust-lib/flowy-grid/src/entities/row_entities.rs diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index 724d898d31..44123a45d9 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -1,5 +1,5 @@ +use crate::entities::*; use crate::manager::GridManager; -use crate::services::entities::*; use crate::services::field::type_options::*; use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_json_str}; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; diff --git a/frontend/rust-lib/flowy-grid/src/lib.rs b/frontend/rust-lib/flowy-grid/src/lib.rs index a3ac3411e2..4f7605b0b8 100644 --- a/frontend/rust-lib/flowy-grid/src/lib.rs +++ b/frontend/rust-lib/flowy-grid/src/lib.rs @@ -6,6 +6,7 @@ pub mod event_map; pub mod manager; mod dart_notification; +pub mod entities; mod protobuf; pub mod services; pub mod util; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs index 84cfe0e4bf..b2589d4f83 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs @@ -1,5 +1,5 @@ +use crate::entities::{CellIdentifier, CellIdentifierPayload}; use crate::impl_type_option; -use crate::services::entities::{CellIdentifier, CellIdentifierPayload}; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; use bytes::Bytes; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs index 30ecfabd9d..2ba96968ce 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs @@ -1,5 +1,5 @@ +use crate::entities::{CellIdentifier, CellIdentifierPayload}; 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::{BoxTypeOptionBuilder, TypeOptionBuilder}; use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index 44c826f31d..c161e191c4 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -1,7 +1,7 @@ use crate::dart_notification::{send_dart_notification, GridNotification}; +use crate::entities::CellIdentifier; use crate::manager::GridUser; 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::persistence::block_index::BlockIndexPersistence; use crate::services::row::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/mod.rs b/frontend/rust-lib/flowy-grid/src/services/mod.rs index 036efd6ada..c9a8217bd8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/mod.rs @@ -2,7 +2,6 @@ mod util; pub mod block_meta_editor; mod block_meta_manager; -pub mod entities; pub mod field; pub mod grid_editor; pub mod persistence; From f521c18512190dc845f88d4e14ea7ecc3399ceae Mon Sep 17 00:00:00 2001 From: appflowy Date: Sun, 22 May 2022 13:26:41 +0800 Subject: [PATCH 04/23] fix: overflow of SelectOptionTagCell --- .../cell/select_option_cell/extension.dart | 19 +++++--- .../select_option_editor.dart | 25 +++++------ .../src/widgets/row/cell/number_cell.dart | 44 ------------------- .../grid/src/widgets/row/row_detail.dart | 2 +- 4 files changed, 26 insertions(+), 64 deletions(-) delete mode 100644 frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart index 3cbf2331fd..6f8212106a 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart @@ -87,7 +87,7 @@ class SelectOptionTag extends StatelessWidget { Widget build(BuildContext context) { return ChoiceChip( pressElevation: 1, - label: FlowyText.medium(name, fontSize: 12), + label: FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis), selectedColor: color, backgroundColor: color, labelPadding: const EdgeInsets.symmetric(horizontal: 6), @@ -130,11 +130,18 @@ class SelectOptionTagCell extends StatelessWidget { child: InkWell( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 3), - child: Row(children: [ - SelectOptionTag.fromSelectOption(context: context, option: option), - const Spacer(), - ...children, - ]), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + fit: FlexFit.loose, + flex: 2, + child: SelectOptionTag.fromSelectOption(context: context, option: option), + ), + const Spacer(), + ...children, + ], + ), ), onTap: () => onSelected(option), ), diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart index f82d52cfb4..01972eb41a 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart @@ -225,7 +225,18 @@ class _SelectOptionCell extends StatelessWidget { height: GridSize.typeOptionItemHeight, child: Row( children: [ - Expanded(child: _body(theme, context)), + Flexible( + fit: FlexFit.loose, + child: SelectOptionTagCell( + option: option, + onSelected: (option) { + context.read().add(SelectOptionEditorEvent.selectOption(option.id)); + }, + children: [ + if (isSelected) svgWidget("grid/checkmark"), + ], + ), + ), FlowyIconButton( width: 30, 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().add(SelectOptionEditorEvent.selectOption(option.id)); - }, - children: [ - if (isSelected) svgWidget("grid/checkmark"), - ], - ); - } - void _showEditPannel(BuildContext context) { final pannel = SelectOptionTypeOptionEditor( option: option, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart deleted file mode 100644 index 0f3f7c5f32..0000000000 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart +++ /dev/null @@ -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 createState() => _NumberCellState(); -} - -class _NumberCellState extends State { - late NumberCellBloc _cellBloc; - - @override - void initState() { - _cellBloc = getIt(param1: widget.cellData); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return BlocProvider.value( - value: _cellBloc, - child: BlocBuilder( - builder: (context, state) { - return Container(); - }, - ), - ); - } - - @override - Future dispose() async { - _cellBloc.close(); - super.dispose(); - } -} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart index 0ce2b76e6b..f2b93e5018 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart @@ -67,7 +67,7 @@ class _RowDetailPageState extends State { return bloc; }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 40), + padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20), child: Column( children: [ SizedBox( From 1ae0b188b1f7b5ebb0807d9bedf864a77072c3e6 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sun, 22 May 2022 23:33:08 +0800 Subject: [PATCH 05/23] refactor: grid unit test --- .../type_options/checkbox_type_option.rs | 84 ++-- .../field/type_options/date_type_option.rs | 175 ++++---- .../field/type_options/number_type_option.rs | 379 +++++++++--------- .../type_options/selection_type_option.rs | 274 ++++++------- .../field/type_options/text_type_option.rs | 150 +++---- .../src/services/row/cell_data_operation.rs | 91 +++-- 6 files changed, 614 insertions(+), 539 deletions(-) diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs index 73c3f5f2d7..a3c084de03 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs @@ -44,15 +44,18 @@ const YES: &str = "Yes"; const NO: &str = "No"; impl CellDataOperation for CheckboxTypeOption { - fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - 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); - } + fn decode_cell_data>( + &self, + type_option_cell_data: T, + _field_meta: &FieldMeta, + ) -> DecodedCellData { + let type_option_cell_data = type_option_cell_data.into(); + 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() @@ -68,7 +71,7 @@ impl CellDataOperation for CheckboxTypeOption { true => YES, false => NO, }; - Ok(TypeOptionCellData::new(s, self.field_type()).json()) + Ok(s.to_string()) } } @@ -90,30 +93,59 @@ mod tests { use crate::services::field::type_options::checkbox_type_option::{NO, YES}; use crate::services::field::CheckboxTypeOption; use crate::services::field::FieldBuilder; - use crate::services::row::CellDataOperation; + use crate::services::row::{apply_cell_data_changeset, decode_cell_data, CellDataOperation}; + use diesel::types::IsNull::No; use flowy_grid_data_model::entities::FieldType; #[test] fn checkout_box_description_test() { - let type_option = CheckboxTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); + let field_meta = FieldBuilder::from_field_type(&FieldType::Checkbox).build(); + let data = apply_cell_data_changeset("true", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data(data, &field_meta, &field_meta.field_type) + .unwrap() + .content, + YES + ); - let data = type_option.apply_changeset("true", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES); + let data = apply_cell_data_changeset("1", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data(data, &field_meta, &field_meta.field_type) + .unwrap() + .content, + YES + ); - let data = type_option.apply_changeset("1", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES); + let data = apply_cell_data_changeset("yes", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data(data, &field_meta, &field_meta.field_type) + .unwrap() + .content, + YES + ); - let data = type_option.apply_changeset("yes", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES); + let data = apply_cell_data_changeset("false", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data(data, &field_meta, &field_meta.field_type) + .unwrap() + .content, + NO + ); - let data = type_option.apply_changeset("false", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO); + let data = apply_cell_data_changeset("no", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data(data, &field_meta, &field_meta.field_type) + .unwrap() + .content, + NO + ); - let data = type_option.apply_changeset("no", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO); - - let data = type_option.apply_changeset("123", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO); + let data = apply_cell_data_changeset("12", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data(data, &field_meta, &field_meta.field_type) + .unwrap() + .content, + NO + ); } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs index b2589d4f83..3dcc1d81eb 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs @@ -136,22 +136,23 @@ impl DateTypeOption { } impl CellDataOperation for DateTypeOption { - fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - // Return default data if the type_option_cell_data is not FieldType::DateTime. - // It happens when switching from one field to another. - // For example: - // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen. - if !type_option_cell_data.is_date() { - return 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(), - }; + fn decode_cell_data>( + &self, + type_option_cell_data: T, + _field_meta: &FieldMeta, + ) -> DecodedCellData { + let type_option_cell_data = type_option_cell_data.into(); + // Return default data if the type_option_cell_data is not FieldType::DateTime. + // It happens when switching from one field to another. + // For example: + // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen. + if !type_option_cell_data.is_date() { + return DecodedCellData::default(); + } + 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() } fn apply_changeset>( @@ -173,7 +174,7 @@ impl CellDataOperation for DateTypeOption { }, }; - Ok(TypeOptionCellData::new(cell_data.to_string(), self.field_type()).json()) + Ok(cell_data.to_string()) } } @@ -410,17 +411,19 @@ mod tests { use crate::services::field::{ DateCellContentChangeset, DateCellData, DateCellDataSerde, DateFormat, DateTypeOption, TimeFormat, }; - use crate::services::row::{CellDataOperation, TypeOptionCellData}; + use crate::services::row::{apply_cell_data_changeset, decode_cell_data, CellDataOperation, TypeOptionCellData}; use flowy_grid_data_model::entities::FieldType; use strum::IntoEnumIterator; #[test] fn date_description_invalid_input_test() { - let type_option = DateTypeOption::default(); let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); + let data = apply_cell_data_changeset("1e", None, &field_meta).unwrap(); assert_eq!( - "".to_owned(), - type_option.decode_cell_data("1e".to_owned(), &field_meta).content + decode_cell_data(data, &field_meta, &field_meta.field_type) + .unwrap() + .content, + "".to_owned() ); } @@ -545,72 +548,72 @@ mod tests { } } - #[test] - fn date_description_apply_changeset_test() { - let mut type_option = DateTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - let date_timestamp = "1653609600".to_owned(); - - let changeset = DateCellContentChangeset { - date: Some(date_timestamp.clone()), - time: None, - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result.clone(), &field_meta).content; - assert_eq!(content, "May 27,2022".to_owned()); - - type_option.include_time = true; - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!(content, "May 27,2022 00:00".to_owned()); - - let changeset = DateCellContentChangeset { - date: Some(date_timestamp.clone()), - time: Some("1:00".to_owned()), - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!(content, "May 27,2022 01:00".to_owned()); - - let changeset = DateCellContentChangeset { - date: Some(date_timestamp), - time: Some("1:00 am".to_owned()), - }; - type_option.time_format = TimeFormat::TwelveHour; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!(content, "May 27,2022 01:00 AM".to_owned()); - } - - #[test] - #[should_panic] - fn date_description_apply_changeset_error_test() { - let mut type_option = DateTypeOption::default(); - type_option.include_time = true; - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - let date_timestamp = "1653609600".to_owned(); - - let changeset = DateCellContentChangeset { - date: Some(date_timestamp.clone()), - time: Some("1:a0".to_owned()), - }; - let _ = type_option.apply_changeset(changeset, None).unwrap(); - - let changeset = DateCellContentChangeset { - date: Some(date_timestamp.clone()), - time: Some("1:".to_owned()), - }; - let _ = type_option.apply_changeset(changeset, None).unwrap(); - } - - #[test] - #[should_panic] - fn date_description_invalid_data_test() { - let type_option = DateTypeOption::default(); - type_option.apply_changeset("he", None).unwrap(); - } - - fn data(s: i64) -> String { + // #[test] + // fn date_description_apply_changeset_test() { + // let mut type_option = DateTypeOption::default(); + // let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); + // let date_timestamp = "1653609600".to_owned(); + // + // let changeset = DateCellContentChangeset { + // date: Some(date_timestamp.clone()), + // time: None, + // }; + // let result = type_option.apply_changeset(changeset, None).unwrap(); + // let content = type_option.decode_cell_data(result.clone(), &field_meta).content; + // assert_eq!(content, "May 27,2022".to_owned()); + // + // type_option.include_time = true; + // let content = type_option.decode_cell_data(result, &field_meta).content; + // assert_eq!(content, "May 27,2022 00:00".to_owned()); + // + // let changeset = DateCellContentChangeset { + // date: Some(date_timestamp.clone()), + // time: Some("1:00".to_owned()), + // }; + // let result = type_option.apply_changeset(changeset, None).unwrap(); + // let content = type_option.decode_cell_data(result, &field_meta).content; + // assert_eq!(content, "May 27,2022 01:00".to_owned()); + // + // let changeset = DateCellContentChangeset { + // date: Some(date_timestamp), + // time: Some("1:00 am".to_owned()), + // }; + // type_option.time_format = TimeFormat::TwelveHour; + // let result = type_option.apply_changeset(changeset, None).unwrap(); + // let content = type_option.decode_cell_data(result, &field_meta).content; + // assert_eq!(content, "May 27,2022 01:00 AM".to_owned()); + // } + // + // #[test] + // #[should_panic] + // fn date_description_apply_changeset_error_test() { + // let mut type_option = DateTypeOption::default(); + // type_option.include_time = true; + // let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); + // let date_timestamp = "1653609600".to_owned(); + // + // let changeset = DateCellContentChangeset { + // date: Some(date_timestamp.clone()), + // time: Some("1:a0".to_owned()), + // }; + // let _ = type_option.apply_changeset(changeset, None).unwrap(); + // + // let changeset = DateCellContentChangeset { + // date: Some(date_timestamp.clone()), + // time: Some("1:".to_owned()), + // }; + // let _ = type_option.apply_changeset(changeset, None).unwrap(); + // } + // + // #[test] + // #[should_panic] + // fn date_description_invalid_data_test() { + // let type_option = DateTypeOption::default(); + // type_option.apply_changeset("he", None).unwrap(); + // } + // + fn data(s: i64) -> TypeOptionCellData { let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(s, None)).unwrap(); - TypeOptionCellData::new(&json, FieldType::DateTime).json() + TypeOptionCellData::new(&json, FieldType::DateTime) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs index a57b056f5d..26f524b110 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs @@ -77,36 +77,37 @@ pub struct NumberTypeOption { impl_type_option!(NumberTypeOption, FieldType::Number); impl CellDataOperation for NumberTypeOption { - fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - if type_option_cell_data.is_date() { - return DecodedCellData::default(); + fn decode_cell_data>( + &self, + type_option_cell_data: T, + _field_meta: &FieldMeta, + ) -> DecodedCellData { + let type_option_cell_data = type_option_cell_data.into(); + if type_option_cell_data.is_date() { + return DecodedCellData::default(); + } + + let cell_data = type_option_cell_data.data; + match self.format { + NumberFormat::Number => { + if let Ok(v) = cell_data.parse::() { + return DecodedCellData::from_content(v.to_string()); + } + + if let Ok(v) = cell_data.parse::() { + return DecodedCellData::from_content(v.to_string()); + } + + DecodedCellData::default() } - - let cell_data = type_option_cell_data.data; - match self.format { - NumberFormat::Number => { - if let Ok(v) = cell_data.parse::() { - return DecodedCellData::from_content(v.to_string()); - } - - if let Ok(v) = cell_data.parse::() { - return DecodedCellData::from_content(v.to_string()); - } - - DecodedCellData::default() - } - NumberFormat::Percent => { - let content = cell_data.parse::().map_or(String::new(), |v| v.to_string()); - DecodedCellData::from_content(content) - } - _ => { - let content = self.money_from_str(&cell_data); - DecodedCellData::from_content(content) - } + NumberFormat::Percent => { + let content = cell_data.parse::().map_or(String::new(), |v| v.to_string()); + DecodedCellData::from_content(content) + } + _ => { + let content = self.money_from_str(&cell_data); + DecodedCellData::from_content(content) } - } else { - DecodedCellData::default() } } @@ -125,7 +126,7 @@ impl CellDataOperation for NumberTypeOption { } } - Ok(TypeOptionCellData::new(&data, self.field_type()).json()) + Ok(data) } } @@ -615,163 +616,163 @@ fn make_strip_symbol() -> Vec { symbols } -#[cfg(test)] -mod tests { - use crate::services::field::FieldBuilder; - use crate::services::field::{NumberFormat, NumberTypeOption}; - use crate::services::row::{CellDataOperation, TypeOptionCellData}; - use flowy_grid_data_model::entities::FieldType; - use strum::IntoEnumIterator; - - #[test] - fn number_description_invalid_input_test() { - let type_option = NumberTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - assert_eq!( - "".to_owned(), - type_option.decode_cell_data(data(""), &field_meta).content - ); - assert_eq!( - "".to_owned(), - type_option.decode_cell_data(data("abc"), &field_meta).content - ); - } - - #[test] - fn number_description_test() { - let mut type_option = NumberTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).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()); - - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Number => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "18443".to_owned() - ); - } - NumberFormat::USD => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "$18,443".to_owned() - ); - 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 => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "¥18,443".to_owned() - ); - } - NumberFormat::Yuan => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "CN¥18,443".to_owned() - ); - } - NumberFormat::EUR => { - assert_eq!( - 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] - fn number_description_scale_test() { - let mut type_option = NumberTypeOption { - scale: 1, - ..Default::default() - }; - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Number => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "18443".to_owned() - ); - } - NumberFormat::USD => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "$1,844.3".to_owned() - ); - } - NumberFormat::Yen => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "¥1,844.3".to_owned() - ); - } - NumberFormat::EUR => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "€1.844,3".to_owned() - ); - } - _ => {} - } - } - } - - #[test] - fn number_description_sign_test() { - let mut type_option = NumberTypeOption { - sign_positive: false, - ..Default::default() - }; - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Number => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "18443".to_owned() - ); - } - NumberFormat::USD => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "-$18,443".to_owned() - ); - } - NumberFormat::Yen => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "-¥18,443".to_owned() - ); - } - NumberFormat::EUR => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "-€18.443".to_owned() - ); - } - _ => {} - } - } - } -} +// #[cfg(test)] +// mod tests { +// use crate::services::field::FieldBuilder; +// use crate::services::field::{NumberFormat, NumberTypeOption}; +// use crate::services::row::{CellDataOperation, TypeOptionCellData}; +// use flowy_grid_data_model::entities::FieldType; +// use strum::IntoEnumIterator; +// +// #[test] +// fn number_description_invalid_input_test() { +// let type_option = NumberTypeOption::default(); +// let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); +// assert_eq!( +// "".to_owned(), +// type_option.decode_cell_data(data(""), &field_meta).content +// ); +// assert_eq!( +// "".to_owned(), +// type_option.decode_cell_data(data("abc"), &field_meta).content +// ); +// } +// +// #[test] +// fn number_description_test() { +// let mut type_option = NumberTypeOption::default(); +// let field_meta = FieldBuilder::from_field_type(&FieldType::Number).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()); +// +// for format in NumberFormat::iter() { +// type_option.format = format; +// match format { +// NumberFormat::Number => { +// assert_eq!( +// type_option.decode_cell_data(data("18443"), &field_meta).content, +// "18443".to_owned() +// ); +// } +// NumberFormat::USD => { +// assert_eq!( +// type_option.decode_cell_data(data("18443"), &field_meta).content, +// "$18,443".to_owned() +// ); +// 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 => { +// assert_eq!( +// type_option.decode_cell_data(data("18443"), &field_meta).content, +// "¥18,443".to_owned() +// ); +// } +// NumberFormat::Yuan => { +// assert_eq!( +// type_option.decode_cell_data(data("18443"), &field_meta).content, +// "CN¥18,443".to_owned() +// ); +// } +// NumberFormat::EUR => { +// assert_eq!( +// 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] +// fn number_description_scale_test() { +// let mut type_option = NumberTypeOption { +// scale: 1, +// ..Default::default() +// }; +// let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); +// +// for format in NumberFormat::iter() { +// type_option.format = format; +// match format { +// NumberFormat::Number => { +// assert_eq!( +// type_option.decode_cell_data(data("18443"), &field_meta).content, +// "18443".to_owned() +// ); +// } +// NumberFormat::USD => { +// assert_eq!( +// type_option.decode_cell_data(data("18443"), &field_meta).content, +// "$1,844.3".to_owned() +// ); +// } +// NumberFormat::Yen => { +// assert_eq!( +// type_option.decode_cell_data(data("18443"), &field_meta).content, +// "¥1,844.3".to_owned() +// ); +// } +// NumberFormat::EUR => { +// assert_eq!( +// type_option.decode_cell_data(data("18443"), &field_meta).content, +// "€1.844,3".to_owned() +// ); +// } +// _ => {} +// } +// } +// } +// +// #[test] +// fn number_description_sign_test() { +// let mut type_option = NumberTypeOption { +// sign_positive: false, +// ..Default::default() +// }; +// let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); +// +// for format in NumberFormat::iter() { +// type_option.format = format; +// match format { +// NumberFormat::Number => { +// assert_eq!( +// type_option.decode_cell_data(data("18443"), &field_meta).content, +// "18443".to_owned() +// ); +// } +// NumberFormat::USD => { +// assert_eq!( +// type_option.decode_cell_data(data("18443"), &field_meta).content, +// "-$18,443".to_owned() +// ); +// } +// NumberFormat::Yen => { +// assert_eq!( +// type_option.decode_cell_data(data("18443"), &field_meta).content, +// "-¥18,443".to_owned() +// ); +// } +// NumberFormat::EUR => { +// assert_eq!( +// type_option.decode_cell_data(data("18443"), &field_meta).content, +// "-€18.443".to_owned() +// ); +// } +// _ => {} +// } +// } +// } +// } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs index 2ba96968ce..71aa697b6f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs @@ -96,18 +96,21 @@ impl SelectOptionOperation for SingleSelectTypeOption { } impl CellDataOperation for SingleSelectTypeOption { - fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - if !type_option_cell_data.is_single_select() { - return DecodedCellData::default(); - } + fn decode_cell_data>( + &self, + type_option_cell_data: T, + _field_meta: &FieldMeta, + ) -> DecodedCellData { + let type_option_cell_data = type_option_cell_data.into(); + if !type_option_cell_data.is_select_option() { + return DecodedCellData::default(); + } - if let Some(option_id) = select_option_ids(type_option_cell_data.data).first() { - return match self.options.iter().find(|option| &option.id == option_id) { - None => DecodedCellData::default(), - Some(option) => DecodedCellData::from_content(option.name.clone()), - }; - } + if let Some(option_id) = select_option_ids(type_option_cell_data.data).first() { + return match self.options.iter().find(|option| &option.id == option_id) { + None => DecodedCellData::default(), + Some(option) => DecodedCellData::from_content(option.name.clone()), + }; } DecodedCellData::default() @@ -129,7 +132,7 @@ impl CellDataOperation for SingleSelectTypeOption { new_cell_data = "".to_string() } - Ok(TypeOptionCellData::new(&new_cell_data, self.field_type()).json()) + Ok(new_cell_data) } } @@ -185,23 +188,26 @@ impl SelectOptionOperation for MultiSelectTypeOption { } impl CellDataOperation for MultiSelectTypeOption { - fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - if !type_option_cell_data.is_multi_select() { - return DecodedCellData::default(); - } - let option_ids = select_option_ids(type_option_cell_data.data); - let content = self - .options - .iter() - .filter(|option| option_ids.contains(&option.id)) - .map(|option| option.name.clone()) - .collect::>() - .join(SELECTION_IDS_SEPARATOR); - DecodedCellData::from_content(content) - } else { - DecodedCellData::default() + fn decode_cell_data>( + &self, + type_option_cell_data: T, + _field_meta: &FieldMeta, + ) -> DecodedCellData { + let type_option_cell_data = type_option_cell_data.into(); + if !type_option_cell_data.is_select_option() { + return DecodedCellData::default(); } + + let option_ids = select_option_ids(type_option_cell_data.data); + let content = self + .options + .iter() + .filter(|option| option_ids.contains(&option.id)) + .map(|option| option.name.clone()) + .collect::>() + .join(SELECTION_IDS_SEPARATOR); + + DecodedCellData::from_content(content) } fn apply_changeset>( @@ -237,7 +243,7 @@ impl CellDataOperation for MultiSelectTypeOption { } } - Ok(TypeOptionCellData::new(&new_cell_data, self.field_type()).json()) + Ok(new_cell_data) } } @@ -485,108 +491,108 @@ fn make_select_context_from(cell_meta: &Option, options: &[SelectOptio } } -#[cfg(test)] -mod tests { - use crate::services::field::FieldBuilder; - use crate::services::field::{ - MultiSelectTypeOption, MultiSelectTypeOptionBuilder, SelectOption, SelectOptionCellContentChangeset, - SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR, - }; - use crate::services::row::CellDataOperation; - - #[test] - fn single_select_test() { - let google_option = SelectOption::new("Google"); - let facebook_option = SelectOption::new("Facebook"); - let twitter_option = SelectOption::new("Twitter"); - let single_select = SingleSelectTypeOptionBuilder::default() - .option(google_option.clone()) - .option(facebook_option.clone()) - .option(twitter_option); - - let field_meta = FieldBuilder::new(single_select) - .name("Platform") - .visibility(true) - .build(); - - let type_option = SingleSelectTypeOption::from(&field_meta); - - let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR); - let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); - let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option.decode_cell_data(cell_data, &field_meta).content, - google_option.name, - ); - - let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); - let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option.decode_cell_data(cell_data, &field_meta).content, - google_option.name, - ); - - // Invalid option id - let cell_data = type_option - .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) - .unwrap(); - assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); - - // Invalid option id - let cell_data = type_option - .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None) - .unwrap(); - assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); - - // Invalid changeset - assert!(type_option.apply_changeset("123", None).is_err()); - } - - #[test] - fn multi_select_test() { - let google_option = SelectOption::new("Google"); - let facebook_option = SelectOption::new("Facebook"); - let twitter_option = SelectOption::new("Twitter"); - let multi_select = MultiSelectTypeOptionBuilder::default() - .option(google_option.clone()) - .option(facebook_option.clone()) - .option(twitter_option); - - let field_meta = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); - - let type_option = MultiSelectTypeOption::from(&field_meta); - - let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); - let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); - let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option.decode_cell_data(cell_data, &field_meta).content, - vec![google_option.name.clone(), facebook_option.name].join(SELECTION_IDS_SEPARATOR), - ); - - let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); - let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option.decode_cell_data(cell_data, &field_meta).content, - google_option.name, - ); - - // Invalid option id - let cell_data = type_option - .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) - .unwrap(); - assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); - - // Invalid option id - let cell_data = type_option - .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None) - .unwrap(); - assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); - - // Invalid changeset - assert!(type_option.apply_changeset("123", None).is_err()); - } -} +// #[cfg(test)] +// mod tests { +// use crate::services::field::FieldBuilder; +// use crate::services::field::{ +// MultiSelectTypeOption, MultiSelectTypeOptionBuilder, SelectOption, SelectOptionCellContentChangeset, +// SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR, +// }; +// use crate::services::row::CellDataOperation; +// +// #[test] +// fn single_select_test() { +// let google_option = SelectOption::new("Google"); +// let facebook_option = SelectOption::new("Facebook"); +// let twitter_option = SelectOption::new("Twitter"); +// let single_select = SingleSelectTypeOptionBuilder::default() +// .option(google_option.clone()) +// .option(facebook_option.clone()) +// .option(twitter_option); +// +// let field_meta = FieldBuilder::new(single_select) +// .name("Platform") +// .visibility(true) +// .build(); +// +// let type_option = SingleSelectTypeOption::from(&field_meta); +// +// let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR); +// let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); +// let cell_data = type_option.apply_changeset(data, None).unwrap(); +// assert_eq!( +// type_option.decode_cell_data(cell_data, &field_meta).content, +// google_option.name, +// ); +// +// let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); +// let cell_data = type_option.apply_changeset(data, None).unwrap(); +// assert_eq!( +// type_option.decode_cell_data(cell_data, &field_meta).content, +// google_option.name, +// ); +// +// // Invalid option id +// let cell_data = type_option +// .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) +// .unwrap(); +// assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); +// +// // Invalid option id +// let cell_data = type_option +// .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None) +// .unwrap(); +// assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); +// +// // Invalid changeset +// assert!(type_option.apply_changeset("123", None).is_err()); +// } +// +// #[test] +// fn multi_select_test() { +// let google_option = SelectOption::new("Google"); +// let facebook_option = SelectOption::new("Facebook"); +// let twitter_option = SelectOption::new("Twitter"); +// let multi_select = MultiSelectTypeOptionBuilder::default() +// .option(google_option.clone()) +// .option(facebook_option.clone()) +// .option(twitter_option); +// +// let field_meta = FieldBuilder::new(multi_select) +// .name("Platform") +// .visibility(true) +// .build(); +// +// let type_option = MultiSelectTypeOption::from(&field_meta); +// +// let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); +// let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); +// let cell_data = type_option.apply_changeset(data, None).unwrap(); +// assert_eq!( +// type_option.decode_cell_data(cell_data, &field_meta).content, +// vec![google_option.name.clone(), facebook_option.name].join(SELECTION_IDS_SEPARATOR), +// ); +// +// let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); +// let cell_data = type_option.apply_changeset(data, None).unwrap(); +// assert_eq!( +// type_option.decode_cell_data(cell_data, &field_meta).content, +// google_option.name, +// ); +// +// // Invalid option id +// let cell_data = type_option +// .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) +// .unwrap(); +// assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); +// +// // Invalid option id +// let cell_data = type_option +// .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None) +// .unwrap(); +// assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); +// +// // Invalid changeset +// assert!(type_option.apply_changeset("123", None).is_err()); +// } +// } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs index 348114e122..f24af8328b 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs @@ -35,19 +35,21 @@ pub struct RichTextTypeOption { impl_type_option!(RichTextTypeOption, FieldType::RichText); impl CellDataOperation for RichTextTypeOption { - fn decode_cell_data(&self, data: String, field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - if type_option_cell_data.is_date() - || type_option_cell_data.is_single_select() - || type_option_cell_data.is_multi_select() - || type_option_cell_data.is_number() - { - decode_cell_data(data, field_meta, &type_option_cell_data.field_type).unwrap_or_default() - } else { - DecodedCellData::from_content(type_option_cell_data.data) - } + fn decode_cell_data>( + &self, + type_option_cell_data: T, + field_meta: &FieldMeta, + ) -> DecodedCellData { + let type_option_cell_data = type_option_cell_data.into(); + if type_option_cell_data.is_date() + || type_option_cell_data.is_single_select() + || type_option_cell_data.is_multi_select() + || type_option_cell_data.is_number() + { + let field_type = type_option_cell_data.field_type.clone(); + decode_cell_data(type_option_cell_data, field_meta, &field_type).unwrap_or_default() } else { - DecodedCellData::default() + DecodedCellData::from_content(type_option_cell_data.data) } } @@ -60,69 +62,69 @@ impl CellDataOperation for RichTextTypeOption { if data.len() > 10000 { Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000")) } else { - Ok(TypeOptionCellData::new(&data, self.field_type()).json()) + Ok(data.to_string()) } } } -#[cfg(test)] -mod tests { - use crate::services::field::FieldBuilder; - use crate::services::field::*; - use crate::services::row::{CellDataOperation, TypeOptionCellData}; - use flowy_grid_data_model::entities::FieldType; - - #[test] - fn text_description_test() { - let type_option = RichTextTypeOption::default(); - - // date - let date_time_field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(1647251762, None)).unwrap(); - let data = TypeOptionCellData::new(&json, FieldType::DateTime).json(); - assert_eq!( - type_option.decode_cell_data(data, &date_time_field_meta).content, - "Mar 14,2022".to_owned() - ); - - // Single select - let done_option = SelectOption::new("Done"); - let done_option_id = done_option.id.clone(); - let single_select = SingleSelectTypeOptionBuilder::default().option(done_option); - let single_select_field_meta = FieldBuilder::new(single_select).build(); - let cell_data = TypeOptionCellData::new(&done_option_id, FieldType::SingleSelect).json(); - assert_eq!( - type_option - .decode_cell_data(cell_data, &single_select_field_meta) - .content, - "Done".to_owned() - ); - - // Multiple select - let google_option = SelectOption::new("Google"); - let facebook_option = SelectOption::new("Facebook"); - let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); - let cell_data_changeset = SelectOptionCellContentChangeset::from_insert(&ids).to_str(); - let multi_select = MultiSelectTypeOptionBuilder::default() - .option(google_option) - .option(facebook_option); - let multi_select_field_meta = FieldBuilder::new(multi_select).build(); - let multi_type_option = MultiSelectTypeOption::from(&multi_select_field_meta); - let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap(); - assert_eq!( - type_option - .decode_cell_data(cell_data, &multi_select_field_meta) - .content, - "Google,Facebook".to_owned() - ); - - //Number - let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); - let number_field_meta = FieldBuilder::new(number).build(); - let data = TypeOptionCellData::new("18443", FieldType::Number).json(); - assert_eq!( - type_option.decode_cell_data(data, &number_field_meta).content, - "$18,443".to_owned() - ); - } -} +// #[cfg(test)] +// mod tests { +// use crate::services::field::FieldBuilder; +// use crate::services::field::*; +// use crate::services::row::{CellDataOperation, TypeOptionCellData}; +// use flowy_grid_data_model::entities::FieldType; +// +// #[test] +// fn text_description_test() { +// let type_option = RichTextTypeOption::default(); +// +// // date +// let date_time_field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); +// let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(1647251762, None)).unwrap(); +// let data = TypeOptionCellData::new(&json, FieldType::DateTime).json(); +// assert_eq!( +// type_option.decode_cell_data(data, &date_time_field_meta).content, +// "Mar 14,2022".to_owned() +// ); +// +// // Single select +// let done_option = SelectOption::new("Done"); +// let done_option_id = done_option.id.clone(); +// let single_select = SingleSelectTypeOptionBuilder::default().option(done_option); +// let single_select_field_meta = FieldBuilder::new(single_select).build(); +// let cell_data = TypeOptionCellData::new(&done_option_id, FieldType::SingleSelect).json(); +// assert_eq!( +// type_option +// .decode_cell_data(cell_data, &single_select_field_meta) +// .content, +// "Done".to_owned() +// ); +// +// // Multiple select +// let google_option = SelectOption::new("Google"); +// let facebook_option = SelectOption::new("Facebook"); +// let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); +// let cell_data_changeset = SelectOptionCellContentChangeset::from_insert(&ids).to_str(); +// let multi_select = MultiSelectTypeOptionBuilder::default() +// .option(google_option) +// .option(facebook_option); +// let multi_select_field_meta = FieldBuilder::new(multi_select).build(); +// let multi_type_option = MultiSelectTypeOption::from(&multi_select_field_meta); +// let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap(); +// assert_eq!( +// type_option +// .decode_cell_data(cell_data, &multi_select_field_meta) +// .content, +// "Google,Facebook".to_owned() +// ); +// +// //Number +// let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); +// let number_field_meta = FieldBuilder::new(number).build(); +// let data = TypeOptionCellData::new("18443", FieldType::Number).json(); +// assert_eq!( +// type_option.decode_cell_data(data, &number_field_meta).content, +// "$18,443".to_owned() +// ); +// } +// } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs index 5f6dbc74fd..73513d29f8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs @@ -3,9 +3,10 @@ use flowy_error::FlowyError; use flowy_grid_data_model::entities::{CellMeta, FieldMeta, FieldType}; use serde::{Deserialize, Serialize}; use std::fmt::Formatter; +use std::str::FromStr; pub trait CellDataOperation { - fn decode_cell_data(&self, data: String, field_meta: &FieldMeta) -> DecodedCellData; + fn decode_cell_data>(&self, data: T, field_meta: &FieldMeta) -> DecodedCellData; fn apply_changeset>( &self, changeset: T, @@ -52,6 +53,22 @@ impl std::str::FromStr for TypeOptionCellData { } } +impl std::convert::TryInto for String { + type Error = FlowyError; + + fn try_into(self) -> Result { + TypeOptionCellData::from_str(&self) + } +} + +// impl std::convert::Into for String { +// type Error = FlowyError; +// +// fn try_into(self) -> Result { +// TypeOptionCellData::from_str(&self) +// } +// } + impl TypeOptionCellData { pub fn new(data: T, field_type: FieldType) -> Self { TypeOptionCellData { @@ -87,6 +104,10 @@ impl TypeOptionCellData { pub fn is_multi_select(&self) -> bool { 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. @@ -96,47 +117,57 @@ pub fn apply_cell_data_changeset>( cell_meta: Option, field_meta: &FieldMeta, ) -> Result { - match field_meta.field_type { + let s = match field_meta.field_type { FieldType::RichText => RichTextTypeOption::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::SingleSelect => SingleSelectTypeOption::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), - } + }?; + + Ok(TypeOptionCellData::new(s, field_meta.field_type.clone()).json()) } -pub fn decode_cell_data(data: String, field_meta: &FieldMeta, field_type: &FieldType) -> Option { - let s = match field_type { - FieldType::RichText => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::Number => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::DateTime => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::SingleSelect => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::MultiSelect => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::Checkbox => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - }; - tracing::Span::current().record( - "content", - &format!("{:?}: {}", field_meta.field_type, s.content).as_str(), - ); - Some(s) +pub fn decode_cell_data>( + data: T, + field_meta: &FieldMeta, + field_type: &FieldType, +) -> Option { + if let Ok(type_option_cell_data) = data.try_into() { + let s = match field_type { + FieldType::RichText => field_meta + .get_type_option_entry::(field_type)? + .decode_cell_data(type_option_cell_data, field_meta), + FieldType::Number => field_meta + .get_type_option_entry::(field_type)? + .decode_cell_data(type_option_cell_data, field_meta), + FieldType::DateTime => field_meta + .get_type_option_entry::(field_type)? + .decode_cell_data(type_option_cell_data, field_meta), + FieldType::SingleSelect => field_meta + .get_type_option_entry::(field_type)? + .decode_cell_data(type_option_cell_data, field_meta), + FieldType::MultiSelect => field_meta + .get_type_option_entry::(field_type)? + .decode_cell_data(type_option_cell_data, field_meta), + FieldType::Checkbox => field_meta + .get_type_option_entry::(field_type)? + .decode_cell_data(type_option_cell_data, field_meta), + }; + tracing::Span::current().record( + "content", + &format!("{:?}: {}", field_meta.field_type, s.content).as_str(), + ); + Some(s) + } else { + Some(DecodedCellData::default()) + } } #[derive(Default)] pub struct DecodedCellData { - pub raw: String, + raw: String, pub content: String, } From 5b2b50dc9cdaf817968a04cfa7651e3f91d5a16c Mon Sep 17 00:00:00 2001 From: appflowy Date: Mon, 23 May 2022 22:53:13 +0800 Subject: [PATCH 06/23] refactor: cell data operation --- .../type_options/checkbox_type_option.rs | 14 +++---- .../field/type_options/date_type_option.rs | 26 ++++++------ .../field/type_options/number_type_option.rs | 14 +++---- .../type_options/selection_type_option.rs | 28 ++++++------- .../field/type_options/text_type_option.rs | 22 +++++----- .../src/services/row/cell_data_operation.rs | 40 +++++++++++++------ 6 files changed, 80 insertions(+), 64 deletions(-) diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs index a3c084de03..111715ba6b 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs @@ -43,14 +43,15 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox); const YES: &str = "Yes"; const NO: &str = "No"; -impl CellDataOperation for CheckboxTypeOption { +impl CellDataOperation for CheckboxTypeOption { fn decode_cell_data>( &self, type_option_cell_data: T, + decoded_field_type: &FieldType, _field_meta: &FieldMeta, ) -> DecodedCellData { let type_option_cell_data = type_option_cell_data.into(); - if !type_option_cell_data.is_checkbox() { + if !decoded_field_type.is_checkbox() { return DecodedCellData::default(); } let cell_data = type_option_cell_data.data; @@ -61,11 +62,10 @@ impl CellDataOperation for CheckboxTypeOption { DecodedCellData::default() } - fn apply_changeset>( - &self, - changeset: T, - _cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { let changeset = changeset.into(); let s = match string_to_bool(&changeset) { true => YES, diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs index 3dcc1d81eb..62e4887746 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs @@ -135,10 +135,11 @@ impl DateTypeOption { } } -impl CellDataOperation for DateTypeOption { +impl CellDataOperation for DateTypeOption { fn decode_cell_data>( &self, type_option_cell_data: T, + decoded_field_type: &FieldType, _field_meta: &FieldMeta, ) -> DecodedCellData { let type_option_cell_data = type_option_cell_data.into(); @@ -146,7 +147,7 @@ impl CellDataOperation for DateTypeOption { // It happens when switching from one field to another. // For example: // 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(); } match DateCellDataSerde::from_str(&type_option_cell_data.data) { @@ -155,11 +156,10 @@ impl CellDataOperation for DateTypeOption { } } - fn apply_changeset>( - &self, - changeset: T, - _cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { let content_changeset: DateCellContentChangeset = serde_json::from_str(&changeset.into())?; let cell_data = match content_changeset.date_timestamp() { None => DateCellDataSerde::default(), @@ -174,7 +174,7 @@ impl CellDataOperation for DateTypeOption { }, }; - Ok(cell_data.to_string()) + Ok(cell_data) } } @@ -316,15 +316,17 @@ impl DateCellDataSerde { Self { timestamp, time } } - fn to_string(self) -> String { - serde_json::to_string(&self).unwrap_or("".to_string()) - } - fn from_str(s: &str) -> FlowyResult { serde_json::from_str::(s).map_err(internal_error) } } +impl ToString for DateCellDataSerde { + fn to_string(&self) -> String { + serde_json::to_string(&self).unwrap_or("".to_string()) + } +} + fn default_time_str(time_format: &TimeFormat) -> String { match time_format { TimeFormat::TwelveHour => "12:00 AM".to_string(), diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs index 26f524b110..b97a3ca092 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs @@ -76,14 +76,15 @@ pub struct NumberTypeOption { } impl_type_option!(NumberTypeOption, FieldType::Number); -impl CellDataOperation for NumberTypeOption { +impl CellDataOperation for NumberTypeOption { fn decode_cell_data>( &self, type_option_cell_data: T, + decoded_field_type: &FieldType, _field_meta: &FieldMeta, ) -> DecodedCellData { let type_option_cell_data = type_option_cell_data.into(); - if type_option_cell_data.is_date() { + if decoded_field_type.is_date() { return DecodedCellData::default(); } @@ -111,11 +112,10 @@ impl CellDataOperation for NumberTypeOption { } } - fn apply_changeset>( - &self, - changeset: T, - _cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { let changeset = changeset.into(); let mut data = changeset.trim().to_string(); diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs index 71aa697b6f..3d9cee701f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs @@ -95,14 +95,15 @@ impl SelectOptionOperation for SingleSelectTypeOption { } } -impl CellDataOperation for SingleSelectTypeOption { +impl CellDataOperation for SingleSelectTypeOption { fn decode_cell_data>( &self, type_option_cell_data: T, + decoded_field_type: &FieldType, _field_meta: &FieldMeta, ) -> DecodedCellData { let type_option_cell_data = type_option_cell_data.into(); - if !type_option_cell_data.is_select_option() { + if !decoded_field_type.is_select_option() { return DecodedCellData::default(); } @@ -116,11 +117,10 @@ impl CellDataOperation for SingleSelectTypeOption { DecodedCellData::default() } - fn apply_changeset>( - &self, - changeset: T, - _cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { let changeset = changeset.into(); let select_option_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset)?; let new_cell_data: String; @@ -187,14 +187,15 @@ impl SelectOptionOperation for MultiSelectTypeOption { } } -impl CellDataOperation for MultiSelectTypeOption { +impl CellDataOperation for MultiSelectTypeOption { fn decode_cell_data>( &self, type_option_cell_data: T, + decoded_field_type: &FieldType, _field_meta: &FieldMeta, ) -> DecodedCellData { let type_option_cell_data = type_option_cell_data.into(); - if !type_option_cell_data.is_select_option() { + if !decoded_field_type.is_select_option() { return DecodedCellData::default(); } @@ -210,11 +211,10 @@ impl CellDataOperation for MultiSelectTypeOption { DecodedCellData::from_content(content) } - fn apply_changeset>( - &self, - changeset: T, - cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: T, cell_meta: Option) -> Result + where + T: Into, + { let content_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset.into())?; let new_cell_data: String; match cell_meta { diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs index f24af8328b..7cf4fc3d3e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs @@ -34,17 +34,18 @@ pub struct RichTextTypeOption { } impl_type_option!(RichTextTypeOption, FieldType::RichText); -impl CellDataOperation for RichTextTypeOption { +impl CellDataOperation for RichTextTypeOption { fn decode_cell_data>( &self, type_option_cell_data: T, + decoded_field_type: &FieldType, field_meta: &FieldMeta, ) -> DecodedCellData { let type_option_cell_data = type_option_cell_data.into(); - if type_option_cell_data.is_date() - || type_option_cell_data.is_single_select() - || type_option_cell_data.is_multi_select() - || type_option_cell_data.is_number() + if decoded_field_type.is_date() + || decoded_field_type.is_single_select() + || decoded_field_type.is_multi_select() + || decoded_field_type.is_number() { let field_type = type_option_cell_data.field_type.clone(); decode_cell_data(type_option_cell_data, field_meta, &field_type).unwrap_or_default() @@ -53,16 +54,15 @@ impl CellDataOperation for RichTextTypeOption { } } - fn apply_changeset>( - &self, - changeset: T, - _cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { let data = changeset.into(); if data.len() > 10000 { Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000")) } else { - Ok(data.to_string()) + Ok(data.0) } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs index 73513d29f8..2bb78a0bf0 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs @@ -5,17 +5,22 @@ use serde::{Deserialize, Serialize}; use std::fmt::Formatter; use std::str::FromStr; -pub trait CellDataOperation { - fn decode_cell_data>(&self, data: T, field_meta: &FieldMeta) -> DecodedCellData; - fn apply_changeset>( +pub trait CellDataOperation { + fn decode_cell_data>( &self, - changeset: T, + data: T, + decoded_field_type: &FieldType, + field_meta: &FieldMeta, + ) -> DecodedCellData; + fn apply_changeset>( + &self, + changeset: C, cell_meta: Option, - ) -> Result; + ) -> Result; } #[derive(Debug)] -pub struct CellContentChangeset(String); +pub struct CellContentChangeset(pub String); impl std::fmt::Display for CellContentChangeset { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -44,6 +49,12 @@ pub struct TypeOptionCellData { pub field_type: FieldType, } +impl TypeOptionCellData { + pub fn split(self) -> (String, FieldType) { + (self.data, self.field_type) + } +} + impl std::str::FromStr for TypeOptionCellData { type Err = FlowyError; @@ -120,7 +131,9 @@ pub fn apply_cell_data_changeset>( let s = match field_meta.field_type { FieldType::RichText => RichTextTypeOption::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::MultiSelect => MultiSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::Checkbox => CheckboxTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), @@ -135,25 +148,26 @@ pub fn decode_cell_data>( field_type: &FieldType, ) -> Option { if let Ok(type_option_cell_data) = data.try_into() { + let (decoded_field_type, data) = &type_option_cell_data.split(); let s = match field_type { FieldType::RichText => field_meta .get_type_option_entry::(field_type)? - .decode_cell_data(type_option_cell_data, field_meta), + .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta), FieldType::Number => field_meta .get_type_option_entry::(field_type)? - .decode_cell_data(type_option_cell_data, field_meta), + .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta), FieldType::DateTime => field_meta .get_type_option_entry::(field_type)? - .decode_cell_data(type_option_cell_data, field_meta), + .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta), FieldType::SingleSelect => field_meta .get_type_option_entry::(field_type)? - .decode_cell_data(type_option_cell_data, field_meta), + .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta), FieldType::MultiSelect => field_meta .get_type_option_entry::(field_type)? - .decode_cell_data(type_option_cell_data, field_meta), + .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta), FieldType::Checkbox => field_meta .get_type_option_entry::(field_type)? - .decode_cell_data(type_option_cell_data, field_meta), + .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta), }; tracing::Span::current().record( "content", From 8c7b0bd5a738f5f9e7106a0d1d53916d5337342f Mon Sep 17 00:00:00 2001 From: appflowy Date: Tue, 24 May 2022 14:56:55 +0800 Subject: [PATCH 07/23] fix: unit test --- .../type_options/checkbox_type_option.rs | 60 ++- .../field/type_options/date_type_option.rs | 346 +++++++++++------- .../field/type_options/number_type_option.rs | 319 ++++++++-------- .../type_options/selection_type_option.rs | 290 ++++++++------- .../field/type_options/text_type_option.rs | 154 ++++---- .../src/services/row/cell_data_operation.rs | 129 ++++--- .../flowy-grid/src/services/row/row_loader.rs | 8 +- .../flowy-grid/tests/grid/grid_test.rs | 5 +- .../src/entities/grid.rs | 28 ++ .../src/code_gen/protobuf_file/mod.rs | 23 +- 10 files changed, 755 insertions(+), 607 deletions(-) diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs index 111715ba6b..90bb53cea6 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs @@ -1,15 +1,14 @@ use crate::impl_type_option; 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 flowy_derive::ProtoBuf; -use flowy_error::FlowyError; +use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{ CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, }; use serde::{Deserialize, Serialize}; -use std::str::FromStr; #[derive(Default)] pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption); @@ -43,23 +42,26 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox); const YES: &str = "Yes"; const NO: &str = "No"; -impl CellDataOperation for CheckboxTypeOption { - fn decode_cell_data>( +impl CellDataOperation for CheckboxTypeOption { + fn decode_cell_data( &self, - type_option_cell_data: T, + encoded_data: T, decoded_field_type: &FieldType, _field_meta: &FieldMeta, - ) -> DecodedCellData { - let type_option_cell_data = type_option_cell_data.into(); + ) -> FlowyResult + where + T: Into, + { if !decoded_field_type.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); + return Ok(DecodedCellData::default()); } - 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(&self, changeset: C, _cell_meta: Option) -> Result @@ -91,10 +93,10 @@ fn string_to_bool(bool_str: &str) -> bool { #[cfg(test)] mod tests { use crate::services::field::type_options::checkbox_type_option::{NO, YES}; - use crate::services::field::CheckboxTypeOption; + use crate::services::field::FieldBuilder; - use crate::services::row::{apply_cell_data_changeset, decode_cell_data, CellDataOperation}; - use diesel::types::IsNull::No; + use crate::services::row::{apply_cell_data_changeset, decode_cell_data_from_type_option_cell_data}; + use flowy_grid_data_model::entities::FieldType; #[test] @@ -102,49 +104,37 @@ mod tests { let field_meta = FieldBuilder::from_field_type(&FieldType::Checkbox).build(); let data = apply_cell_data_changeset("true", None, &field_meta).unwrap(); assert_eq!( - decode_cell_data(data, &field_meta, &field_meta.field_type) - .unwrap() - .content, + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content, YES ); let data = apply_cell_data_changeset("1", None, &field_meta).unwrap(); assert_eq!( - decode_cell_data(data, &field_meta, &field_meta.field_type) - .unwrap() - .content, + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content, YES ); let data = apply_cell_data_changeset("yes", None, &field_meta).unwrap(); assert_eq!( - decode_cell_data(data, &field_meta, &field_meta.field_type) - .unwrap() - .content, + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content, YES ); let data = apply_cell_data_changeset("false", None, &field_meta).unwrap(); assert_eq!( - decode_cell_data(data, &field_meta, &field_meta.field_type) - .unwrap() - .content, + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content, NO ); let data = apply_cell_data_changeset("no", None, &field_meta).unwrap(); assert_eq!( - decode_cell_data(data, &field_meta, &field_meta.field_type) - .unwrap() - .content, + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content, NO ); let data = apply_cell_data_changeset("12", None, &field_meta).unwrap(); assert_eq!( - decode_cell_data(data, &field_meta, &field_meta.field_type) - .unwrap() - .content, + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content, NO ); } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs index 62e4887746..5ecca97105 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs @@ -1,7 +1,9 @@ use crate::entities::{CellIdentifier, CellIdentifierPayload}; use crate::impl_type_option; 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 chrono::format::strftime::StrftimeItems; use chrono::NaiveDateTime; @@ -90,10 +92,10 @@ impl DateTypeOption { let serde_cell_data = DateCellDataSerde::from_str(&result.unwrap().data)?; 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; - return Ok(DateCellData { date, time, timestamp }); + Ok(DateCellData { date, time, timestamp }) } 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); - 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( @@ -131,29 +133,30 @@ impl DateTypeOption { } } - return Ok(utc.timestamp()); + Ok(utc.timestamp()) } } -impl CellDataOperation for DateTypeOption { - fn decode_cell_data>( +impl CellDataOperation, DateCellDataSerde> for DateTypeOption { + fn decode_cell_data( &self, - type_option_cell_data: T, + encoded_data: T, decoded_field_type: &FieldType, _field_meta: &FieldMeta, - ) -> DecodedCellData { - let type_option_cell_data = type_option_cell_data.into(); + ) -> FlowyResult + where + T: Into>, + { // Return default data if the type_option_cell_data is not FieldType::DateTime. // It happens when switching from one field to another. // For example: // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen. if !decoded_field_type.is_date() { - return DecodedCellData::default(); - } - 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(), + return Ok(DecodedCellData::default()); } + + let encoded_data = encoded_data.into().try_into_inner()?; + Ok(self.decode_cell_data_from_timestamp(&encoded_data)) } fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result @@ -308,22 +311,26 @@ impl DateCellDataSerde { fn new(timestamp: i64, time: Option, time_format: &TimeFormat) -> Self { Self { 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) -> Self { Self { timestamp, time } } +} - fn from_str(s: &str) -> FlowyResult { +impl FromStr for DateCellDataSerde { + type Err = FlowyError; + + fn from_str(s: &str) -> Result { serde_json::from_str::(s).map_err(internal_error) } } impl ToString for DateCellDataSerde { fn to_string(&self) -> String { - serde_json::to_string(&self).unwrap_or("".to_string()) + serde_json::to_string(&self).unwrap_or_else(|_| "".to_string()) } } @@ -410,11 +417,11 @@ impl std::convert::From for CellContentChangeset { #[cfg(test)] mod tests { use crate::services::field::FieldBuilder; - use crate::services::field::{ - DateCellContentChangeset, DateCellData, DateCellDataSerde, DateFormat, DateTypeOption, TimeFormat, + use crate::services::field::{DateCellContentChangeset, 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::{apply_cell_data_changeset, decode_cell_data, CellDataOperation, TypeOptionCellData}; - use flowy_grid_data_model::entities::FieldType; + use flowy_grid_data_model::entities::{FieldMeta, FieldType}; use strum::IntoEnumIterator; #[test] @@ -422,9 +429,7 @@ mod tests { let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); let data = apply_cell_data_changeset("1e", None, &field_meta).unwrap(); assert_eq!( - decode_cell_data(data, &field_meta, &field_meta.field_type) - .unwrap() - .content, + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content, "".to_owned() ); } @@ -432,32 +437,44 @@ mod tests { #[test] fn date_description_date_format_test() { 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() { type_option.date_format = date_format; match date_format { DateFormat::Friendly => { assert_eq!( "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 => { assert_eq!( "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 => { assert_eq!( "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 => { assert_eq!( "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 ); } } @@ -467,7 +484,7 @@ mod tests { #[test] fn date_description_time_format_test() { 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() { type_option.time_format = time_format; match time_format { @@ -478,7 +495,10 @@ mod tests { ); assert_eq!( "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 => { @@ -488,7 +508,10 @@ mod tests { ); assert_eq!( "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 ); } } @@ -498,124 +521,171 @@ mod tests { #[test] fn date_description_time_format_test2() { 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() { type_option.time_format = time_format; type_option.include_time = true; match time_format { TimeFormat::TwentyFourHour => { - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: None, - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022 00:00".to_owned(), content); - - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: Some("23:00".to_owned()), - }; - - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022 23:00".to_owned(), content); + assert_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: None, + }, + &field_type, + &field_meta, + "May 27,2022 00:00", + ); + assert_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: Some("23:00".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022 23:00", + ); } TimeFormat::TwelveHour => { - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: None, - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022 12:00 AM".to_owned(), content); + assert_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: None, + }, + &field_type, + &field_meta, + "May 27,2022 12:00 AM", + ); - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: Some("".to_owned()), - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022".to_owned(), content); + assert_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: Some("".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022", + ); - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: Some("11:23 pm".to_owned()), - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022 11:23 PM".to_owned(), content); + assert_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: Some("11:23 pm".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022 11:23 PM", + ); } } } } - // #[test] - // fn date_description_apply_changeset_test() { - // let mut type_option = DateTypeOption::default(); - // let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - // let date_timestamp = "1653609600".to_owned(); - // - // let changeset = DateCellContentChangeset { - // date: Some(date_timestamp.clone()), - // time: None, - // }; - // let result = type_option.apply_changeset(changeset, None).unwrap(); - // let content = type_option.decode_cell_data(result.clone(), &field_meta).content; - // assert_eq!(content, "May 27,2022".to_owned()); - // - // type_option.include_time = true; - // let content = type_option.decode_cell_data(result, &field_meta).content; - // assert_eq!(content, "May 27,2022 00:00".to_owned()); - // - // let changeset = DateCellContentChangeset { - // date: Some(date_timestamp.clone()), - // time: Some("1:00".to_owned()), - // }; - // let result = type_option.apply_changeset(changeset, None).unwrap(); - // let content = type_option.decode_cell_data(result, &field_meta).content; - // assert_eq!(content, "May 27,2022 01:00".to_owned()); - // - // let changeset = DateCellContentChangeset { - // date: Some(date_timestamp), - // time: Some("1:00 am".to_owned()), - // }; - // type_option.time_format = TimeFormat::TwelveHour; - // let result = type_option.apply_changeset(changeset, None).unwrap(); - // let content = type_option.decode_cell_data(result, &field_meta).content; - // assert_eq!(content, "May 27,2022 01:00 AM".to_owned()); - // } - // - // #[test] - // #[should_panic] - // fn date_description_apply_changeset_error_test() { - // let mut type_option = DateTypeOption::default(); - // type_option.include_time = true; - // let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - // let date_timestamp = "1653609600".to_owned(); - // - // let changeset = DateCellContentChangeset { - // date: Some(date_timestamp.clone()), - // time: Some("1:a0".to_owned()), - // }; - // let _ = type_option.apply_changeset(changeset, None).unwrap(); - // - // let changeset = DateCellContentChangeset { - // date: Some(date_timestamp.clone()), - // time: Some("1:".to_owned()), - // }; - // let _ = type_option.apply_changeset(changeset, None).unwrap(); - // } - // - // #[test] - // #[should_panic] - // fn date_description_invalid_data_test() { - // let type_option = DateTypeOption::default(); - // type_option.apply_changeset("he", None).unwrap(); - // } - // - fn data(s: i64) -> TypeOptionCellData { - let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(s, None)).unwrap(); - TypeOptionCellData::new(&json, FieldType::DateTime) + #[test] + fn date_description_apply_changeset_test() { + let mut type_option = DateTypeOption::default(); + let field_type = FieldType::DateTime; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + let date_timestamp = "1653609600".to_owned(); + + assert_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp.clone()), + time: None, + }, + &field_type, + &field_meta, + "May 27,2022", + ); + + type_option.include_time = true; + assert_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp.clone()), + time: None, + }, + &field_type, + &field_meta, + "May 27,2022 00:00", + ); + + assert_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp.clone()), + time: Some("1:00".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022 01:00", + ); + + type_option.time_format = TimeFormat::TwelveHour; + assert_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp), + time: Some("1:00 am".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022 01:00 AM", + ); + } + + #[test] + #[should_panic] + fn date_description_apply_changeset_error_test() { + let mut type_option = DateTypeOption::default(); + type_option.include_time = true; + let _field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); + let date_timestamp = "1653609600".to_owned(); + + let changeset = DateCellContentChangeset { + date: Some(date_timestamp.clone()), + time: Some("1:a0".to_owned()), + }; + let _ = type_option.apply_changeset(changeset, None).unwrap(); + + let changeset = DateCellContentChangeset { + date: Some(date_timestamp), + time: Some("1:".to_owned()), + }; + let _ = type_option.apply_changeset(changeset, None).unwrap(); + } + + #[test] + #[should_panic] + fn date_description_invalid_data_test() { + let type_option = DateTypeOption::default(); + type_option.apply_changeset("he", None).unwrap(); + } + + fn data(s: i64) -> String { + serde_json::to_string(&DateCellDataSerde::from_timestamp(s, None)).unwrap() + } + + 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); } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs index b97a3ca092..d500bbcc39 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs @@ -1,9 +1,9 @@ use crate::impl_type_option; 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 flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_error::FlowyError; +use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{ CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, }; @@ -76,38 +76,40 @@ pub struct NumberTypeOption { } impl_type_option!(NumberTypeOption, FieldType::Number); -impl CellDataOperation for NumberTypeOption { - fn decode_cell_data>( +impl CellDataOperation for NumberTypeOption { + fn decode_cell_data( &self, - type_option_cell_data: T, + encoded_data: T, decoded_field_type: &FieldType, _field_meta: &FieldMeta, - ) -> DecodedCellData { - let type_option_cell_data = type_option_cell_data.into(); + ) -> FlowyResult + where + T: Into, + { if decoded_field_type.is_date() { - return DecodedCellData::default(); + return Ok(DecodedCellData::default()); } - let cell_data = type_option_cell_data.data; + let cell_data = encoded_data.into(); match self.format { NumberFormat::Number => { if let Ok(v) = cell_data.parse::() { - return DecodedCellData::from_content(v.to_string()); + return Ok(DecodedCellData::from_content(v.to_string())); } if let Ok(v) = cell_data.parse::() { - return DecodedCellData::from_content(v.to_string()); + return Ok(DecodedCellData::from_content(v.to_string())); } - DecodedCellData::default() + Ok(DecodedCellData::default()) } NumberFormat::Percent => { let content = cell_data.parse::().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); - DecodedCellData::from_content(content) + Ok(DecodedCellData::from_content(content)) } } } @@ -616,163 +618,132 @@ fn make_strip_symbol() -> Vec { symbols } -// #[cfg(test)] -// mod tests { -// use crate::services::field::FieldBuilder; -// use crate::services::field::{NumberFormat, NumberTypeOption}; -// use crate::services::row::{CellDataOperation, TypeOptionCellData}; -// use flowy_grid_data_model::entities::FieldType; -// use strum::IntoEnumIterator; -// -// #[test] -// fn number_description_invalid_input_test() { -// let type_option = NumberTypeOption::default(); -// let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); -// assert_eq!( -// "".to_owned(), -// type_option.decode_cell_data(data(""), &field_meta).content -// ); -// assert_eq!( -// "".to_owned(), -// type_option.decode_cell_data(data("abc"), &field_meta).content -// ); -// } -// -// #[test] -// fn number_description_test() { -// let mut type_option = NumberTypeOption::default(); -// let field_meta = FieldBuilder::from_field_type(&FieldType::Number).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()); -// -// for format in NumberFormat::iter() { -// type_option.format = format; -// match format { -// NumberFormat::Number => { -// assert_eq!( -// type_option.decode_cell_data(data("18443"), &field_meta).content, -// "18443".to_owned() -// ); -// } -// NumberFormat::USD => { -// assert_eq!( -// type_option.decode_cell_data(data("18443"), &field_meta).content, -// "$18,443".to_owned() -// ); -// 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 => { -// assert_eq!( -// type_option.decode_cell_data(data("18443"), &field_meta).content, -// "¥18,443".to_owned() -// ); -// } -// NumberFormat::Yuan => { -// assert_eq!( -// type_option.decode_cell_data(data("18443"), &field_meta).content, -// "CN¥18,443".to_owned() -// ); -// } -// NumberFormat::EUR => { -// assert_eq!( -// 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] -// fn number_description_scale_test() { -// let mut type_option = NumberTypeOption { -// scale: 1, -// ..Default::default() -// }; -// let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); -// -// for format in NumberFormat::iter() { -// type_option.format = format; -// match format { -// NumberFormat::Number => { -// assert_eq!( -// type_option.decode_cell_data(data("18443"), &field_meta).content, -// "18443".to_owned() -// ); -// } -// NumberFormat::USD => { -// assert_eq!( -// type_option.decode_cell_data(data("18443"), &field_meta).content, -// "$1,844.3".to_owned() -// ); -// } -// NumberFormat::Yen => { -// assert_eq!( -// type_option.decode_cell_data(data("18443"), &field_meta).content, -// "¥1,844.3".to_owned() -// ); -// } -// NumberFormat::EUR => { -// assert_eq!( -// type_option.decode_cell_data(data("18443"), &field_meta).content, -// "€1.844,3".to_owned() -// ); -// } -// _ => {} -// } -// } -// } -// -// #[test] -// fn number_description_sign_test() { -// let mut type_option = NumberTypeOption { -// sign_positive: false, -// ..Default::default() -// }; -// let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); -// -// for format in NumberFormat::iter() { -// type_option.format = format; -// match format { -// NumberFormat::Number => { -// assert_eq!( -// type_option.decode_cell_data(data("18443"), &field_meta).content, -// "18443".to_owned() -// ); -// } -// NumberFormat::USD => { -// assert_eq!( -// type_option.decode_cell_data(data("18443"), &field_meta).content, -// "-$18,443".to_owned() -// ); -// } -// NumberFormat::Yen => { -// assert_eq!( -// type_option.decode_cell_data(data("18443"), &field_meta).content, -// "-¥18,443".to_owned() -// ); -// } -// NumberFormat::EUR => { -// assert_eq!( -// type_option.decode_cell_data(data("18443"), &field_meta).content, -// "-€18.443".to_owned() -// ); -// } -// _ => {} -// } -// } -// } -// } +#[cfg(test)] +mod tests { + use crate::services::field::FieldBuilder; + use crate::services::field::{NumberFormat, NumberTypeOption}; + use crate::services::row::CellDataOperation; + use flowy_grid_data_model::entities::{FieldMeta, FieldType}; + use strum::IntoEnumIterator; + + #[test] + fn number_description_invalid_input_test() { + let type_option = NumberTypeOption::default(); + let field_type = FieldType::Number; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + assert_equal(&type_option, "", "", &field_type, &field_meta); + assert_equal(&type_option, "abc", "", &field_type, &field_meta); + } + + #[test] + fn number_description_test() { + let mut type_option = NumberTypeOption::default(); + 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()); + + for format in NumberFormat::iter() { + type_option.format = format; + match format { + NumberFormat::Number => { + assert_equal(&type_option, "18443", "18443", &field_type, &field_meta); + } + NumberFormat::USD => { + assert_equal(&type_option, "18443", "$18,443", &field_type, &field_meta); + assert_equal(&type_option, "", "", &field_type, &field_meta); + assert_equal(&type_option, "abc", "", &field_type, &field_meta); + } + NumberFormat::Yen => { + assert_equal(&type_option, "18443", "¥18,443", &field_type, &field_meta); + } + NumberFormat::Yuan => { + assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_meta); + } + NumberFormat::EUR => { + assert_equal(&type_option, "18443", "€18.443", &field_type, &field_meta); + } + _ => {} + } + } + } + + #[test] + fn number_description_scale_test() { + let mut type_option = NumberTypeOption { + scale: 1, + ..Default::default() + }; + let field_type = FieldType::Number; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + + for format in NumberFormat::iter() { + type_option.format = format; + match format { + NumberFormat::Number => { + assert_equal(&type_option, "18443", "18443", &field_type, &field_meta); + } + NumberFormat::USD => { + assert_equal(&type_option, "18443", "$1,844.3", &field_type, &field_meta); + } + NumberFormat::Yen => { + assert_equal(&type_option, "18443", "¥1,844.3", &field_type, &field_meta); + } + NumberFormat::EUR => { + assert_equal(&type_option, "18443", "€1.844,3", &field_type, &field_meta); + } + _ => {} + } + } + } + + #[test] + fn number_description_sign_test() { + let mut type_option = NumberTypeOption { + sign_positive: false, + ..Default::default() + }; + let field_type = FieldType::Number; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + + for format in NumberFormat::iter() { + type_option.format = format; + match format { + NumberFormat::Number => { + assert_equal(&type_option, "18443", "18443", &field_type, &field_meta); + } + NumberFormat::USD => { + assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_meta); + } + NumberFormat::Yen => { + assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_meta); + } + NumberFormat::EUR => { + assert_equal(&type_option, "18443", "-€18.443", &field_type, &field_meta); + } + _ => {} + } + } + } + + 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() + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs index 3d9cee701f..c73dd1802f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs @@ -95,26 +95,30 @@ impl SelectOptionOperation for SingleSelectTypeOption { } } -impl CellDataOperation for SingleSelectTypeOption { - fn decode_cell_data>( +impl CellDataOperation for SingleSelectTypeOption { + fn decode_cell_data( &self, - type_option_cell_data: T, + encoded_data: T, decoded_field_type: &FieldType, _field_meta: &FieldMeta, - ) -> DecodedCellData { - let type_option_cell_data = type_option_cell_data.into(); + ) -> FlowyResult + where + T: Into, + { if !decoded_field_type.is_select_option() { - return DecodedCellData::default(); + return Ok(DecodedCellData::default()); } - if let Some(option_id) = select_option_ids(type_option_cell_data.data).first() { - return match self.options.iter().find(|option| &option.id == option_id) { + let cell_data = encoded_data.into(); + 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(), Some(option) => DecodedCellData::from_content(option.name.clone()), }; + Ok(data) + } else { + Ok(DecodedCellData::default()) } - - DecodedCellData::default() } fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result @@ -187,19 +191,21 @@ impl SelectOptionOperation for MultiSelectTypeOption { } } -impl CellDataOperation for MultiSelectTypeOption { - fn decode_cell_data>( +impl CellDataOperation for MultiSelectTypeOption { + fn decode_cell_data( &self, - type_option_cell_data: T, + encoded_data: T, decoded_field_type: &FieldType, _field_meta: &FieldMeta, - ) -> DecodedCellData { - let type_option_cell_data = type_option_cell_data.into(); + ) -> FlowyResult + where + T: Into, + { if !decoded_field_type.is_select_option() { - return DecodedCellData::default(); + 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 .options .iter() @@ -208,7 +214,7 @@ impl CellDataOperation for MultiSelectTypeOption { .collect::>() .join(SELECTION_IDS_SEPARATOR); - DecodedCellData::from_content(content) + Ok(DecodedCellData::from_content(content)) } fn apply_changeset(&self, changeset: T, cell_meta: Option) -> Result @@ -491,108 +497,144 @@ fn make_select_context_from(cell_meta: &Option, options: &[SelectOptio } } -// #[cfg(test)] -// mod tests { -// use crate::services::field::FieldBuilder; -// use crate::services::field::{ -// MultiSelectTypeOption, MultiSelectTypeOptionBuilder, SelectOption, SelectOptionCellContentChangeset, -// SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR, -// }; -// use crate::services::row::CellDataOperation; -// -// #[test] -// fn single_select_test() { -// let google_option = SelectOption::new("Google"); -// let facebook_option = SelectOption::new("Facebook"); -// let twitter_option = SelectOption::new("Twitter"); -// let single_select = SingleSelectTypeOptionBuilder::default() -// .option(google_option.clone()) -// .option(facebook_option.clone()) -// .option(twitter_option); -// -// let field_meta = FieldBuilder::new(single_select) -// .name("Platform") -// .visibility(true) -// .build(); -// -// let type_option = SingleSelectTypeOption::from(&field_meta); -// -// let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR); -// let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); -// let cell_data = type_option.apply_changeset(data, None).unwrap(); -// assert_eq!( -// type_option.decode_cell_data(cell_data, &field_meta).content, -// google_option.name, -// ); -// -// let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); -// let cell_data = type_option.apply_changeset(data, None).unwrap(); -// assert_eq!( -// type_option.decode_cell_data(cell_data, &field_meta).content, -// google_option.name, -// ); -// -// // Invalid option id -// let cell_data = type_option -// .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) -// .unwrap(); -// assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); -// -// // Invalid option id -// let cell_data = type_option -// .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None) -// .unwrap(); -// assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); -// -// // Invalid changeset -// assert!(type_option.apply_changeset("123", None).is_err()); -// } -// -// #[test] -// fn multi_select_test() { -// let google_option = SelectOption::new("Google"); -// let facebook_option = SelectOption::new("Facebook"); -// let twitter_option = SelectOption::new("Twitter"); -// let multi_select = MultiSelectTypeOptionBuilder::default() -// .option(google_option.clone()) -// .option(facebook_option.clone()) -// .option(twitter_option); -// -// let field_meta = FieldBuilder::new(multi_select) -// .name("Platform") -// .visibility(true) -// .build(); -// -// let type_option = MultiSelectTypeOption::from(&field_meta); -// -// let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); -// let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); -// let cell_data = type_option.apply_changeset(data, None).unwrap(); -// assert_eq!( -// type_option.decode_cell_data(cell_data, &field_meta).content, -// vec![google_option.name.clone(), facebook_option.name].join(SELECTION_IDS_SEPARATOR), -// ); -// -// let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); -// let cell_data = type_option.apply_changeset(data, None).unwrap(); -// assert_eq!( -// type_option.decode_cell_data(cell_data, &field_meta).content, -// google_option.name, -// ); -// -// // Invalid option id -// let cell_data = type_option -// .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) -// .unwrap(); -// assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); -// -// // Invalid option id -// let cell_data = type_option -// .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None) -// .unwrap(); -// assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); -// -// // Invalid changeset -// assert!(type_option.apply_changeset("123", None).is_err()); -// } -// } +#[cfg(test)] +mod tests { + use crate::services::field::FieldBuilder; + use crate::services::field::{ + MultiSelectTypeOption, MultiSelectTypeOptionBuilder, SelectOption, SelectOptionCellContentChangeset, + SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR, + }; + use crate::services::row::CellDataOperation; + + #[test] + fn single_select_test() { + let google_option = SelectOption::new("Google"); + let facebook_option = SelectOption::new("Facebook"); + let twitter_option = SelectOption::new("Twitter"); + let single_select = SingleSelectTypeOptionBuilder::default() + .option(google_option.clone()) + .option(facebook_option.clone()) + .option(twitter_option); + + let field_meta = FieldBuilder::new(single_select) + .name("Platform") + .visibility(true) + .build(); + + let type_option = SingleSelectTypeOption::from(&field_meta); + + let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR); + let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); + let cell_data = type_option.apply_changeset(data, None).unwrap(); + assert_eq!( + type_option + .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) + .unwrap() + .content, + google_option.name, + ); + + let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); + let cell_data = type_option.apply_changeset(data, None).unwrap(); + assert_eq!( + type_option + .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) + .unwrap() + .content, + google_option.name, + ); + + // Invalid option id + let cell_data = type_option + .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) + .unwrap(); + assert_eq!( + type_option + .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) + .unwrap() + .content, + "", + ); + + // Invalid option id + let cell_data = type_option + .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None) + .unwrap(); + assert_eq!( + type_option + .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) + .unwrap() + .content, + "", + ); + + // Invalid changeset + assert!(type_option.apply_changeset("123", None).is_err()); + } + + #[test] + fn multi_select_test() { + let google_option = SelectOption::new("Google"); + let facebook_option = SelectOption::new("Facebook"); + let twitter_option = SelectOption::new("Twitter"); + let multi_select = MultiSelectTypeOptionBuilder::default() + .option(google_option.clone()) + .option(facebook_option.clone()) + .option(twitter_option); + + let field_meta = FieldBuilder::new(multi_select) + .name("Platform") + .visibility(true) + .build(); + + let type_option = MultiSelectTypeOption::from(&field_meta); + + let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); + let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); + let cell_data = type_option.apply_changeset(data, None).unwrap(); + assert_eq!( + 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), + ); + + let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); + let cell_data = type_option.apply_changeset(data, None).unwrap(); + assert_eq!( + type_option + .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) + .unwrap() + .content, + google_option.name, + ); + + // Invalid option id + let cell_data = type_option + .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) + .unwrap(); + assert_eq!( + type_option + .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) + .unwrap() + .content, + "", + ); + + // Invalid option id + let cell_data = type_option + .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None) + .unwrap(); + assert_eq!( + type_option + .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) + .unwrap() + .content, + "", + ); + + // Invalid changeset + assert!(type_option.apply_changeset("123", None).is_err()); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs index 7cf4fc3d3e..32d45a3e29 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs @@ -1,16 +1,13 @@ use crate::impl_type_option; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{ - decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData, -}; +use crate::services::row::{decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData}; use bytes::Bytes; use flowy_derive::ProtoBuf; -use flowy_error::FlowyError; +use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{ CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, }; use serde::{Deserialize, Serialize}; -use std::str::FromStr; #[derive(Default)] pub struct RichTextTypeOptionBuilder(RichTextTypeOption); @@ -34,23 +31,25 @@ pub struct RichTextTypeOption { } impl_type_option!(RichTextTypeOption, FieldType::RichText); -impl CellDataOperation for RichTextTypeOption { - fn decode_cell_data>( +impl CellDataOperation for RichTextTypeOption { + fn decode_cell_data( &self, - type_option_cell_data: T, + encoded_data: T, decoded_field_type: &FieldType, field_meta: &FieldMeta, - ) -> DecodedCellData { - let type_option_cell_data = type_option_cell_data.into(); + ) -> FlowyResult + where + T: Into, + { if decoded_field_type.is_date() || decoded_field_type.is_single_select() || decoded_field_type.is_multi_select() || decoded_field_type.is_number() { - let field_type = type_option_cell_data.field_type.clone(); - decode_cell_data(type_option_cell_data, field_meta, &field_type).unwrap_or_default() + decode_cell_data(encoded_data, decoded_field_type, decoded_field_type, field_meta) } else { - DecodedCellData::from_content(type_option_cell_data.data) + let cell_data = encoded_data.into(); + Ok(DecodedCellData::from_content(cell_data)) } } @@ -67,64 +66,71 @@ impl CellDataOperation for RichTextTypeOption { } } -// #[cfg(test)] -// mod tests { -// use crate::services::field::FieldBuilder; -// use crate::services::field::*; -// use crate::services::row::{CellDataOperation, TypeOptionCellData}; -// use flowy_grid_data_model::entities::FieldType; -// -// #[test] -// fn text_description_test() { -// let type_option = RichTextTypeOption::default(); -// -// // date -// let date_time_field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); -// let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(1647251762, None)).unwrap(); -// let data = TypeOptionCellData::new(&json, FieldType::DateTime).json(); -// assert_eq!( -// type_option.decode_cell_data(data, &date_time_field_meta).content, -// "Mar 14,2022".to_owned() -// ); -// -// // Single select -// let done_option = SelectOption::new("Done"); -// let done_option_id = done_option.id.clone(); -// let single_select = SingleSelectTypeOptionBuilder::default().option(done_option); -// let single_select_field_meta = FieldBuilder::new(single_select).build(); -// let cell_data = TypeOptionCellData::new(&done_option_id, FieldType::SingleSelect).json(); -// assert_eq!( -// type_option -// .decode_cell_data(cell_data, &single_select_field_meta) -// .content, -// "Done".to_owned() -// ); -// -// // Multiple select -// let google_option = SelectOption::new("Google"); -// let facebook_option = SelectOption::new("Facebook"); -// let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); -// let cell_data_changeset = SelectOptionCellContentChangeset::from_insert(&ids).to_str(); -// let multi_select = MultiSelectTypeOptionBuilder::default() -// .option(google_option) -// .option(facebook_option); -// let multi_select_field_meta = FieldBuilder::new(multi_select).build(); -// let multi_type_option = MultiSelectTypeOption::from(&multi_select_field_meta); -// let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap(); -// assert_eq!( -// type_option -// .decode_cell_data(cell_data, &multi_select_field_meta) -// .content, -// "Google,Facebook".to_owned() -// ); -// -// //Number -// let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); -// let number_field_meta = FieldBuilder::new(number).build(); -// let data = TypeOptionCellData::new("18443", FieldType::Number).json(); -// assert_eq!( -// type_option.decode_cell_data(data, &number_field_meta).content, -// "$18,443".to_owned() -// ); -// } -// } +#[cfg(test)] +mod tests { + use crate::services::field::FieldBuilder; + use crate::services::field::*; + use crate::services::row::CellDataOperation; + use flowy_grid_data_model::entities::FieldType; + + #[test] + fn text_description_test() { + let type_option = RichTextTypeOption::default(); + + // date + 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(); + assert_eq!( + type_option + .decode_cell_data(json, &field_type, &date_time_field_meta) + .unwrap() + .content, + "Mar 14,2022".to_owned() + ); + + // Single select + let done_option = SelectOption::new("Done"); + let done_option_id = done_option.id.clone(); + let single_select = SingleSelectTypeOptionBuilder::default().option(done_option); + let single_select_field_meta = FieldBuilder::new(single_select).build(); + + assert_eq!( + type_option + .decode_cell_data(done_option_id, &FieldType::SingleSelect, &single_select_field_meta) + .unwrap() + .content, + "Done".to_owned() + ); + + // Multiple select + let google_option = SelectOption::new("Google"); + let facebook_option = SelectOption::new("Facebook"); + let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); + let cell_data_changeset = SelectOptionCellContentChangeset::from_insert(&ids).to_str(); + let multi_select = MultiSelectTypeOptionBuilder::default() + .option(google_option) + .option(facebook_option); + let multi_select_field_meta = FieldBuilder::new(multi_select).build(); + let multi_type_option = MultiSelectTypeOption::from(&multi_select_field_meta); + let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap(); + assert_eq!( + type_option + .decode_cell_data(cell_data, &FieldType::MultiSelect, &multi_select_field_meta) + .unwrap() + .content, + "Google,Facebook".to_owned() + ); + + //Number + let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); + let number_field_meta = FieldBuilder::new(number).build(); + assert_eq!( + type_option + .decode_cell_data("18443".to_owned(), &FieldType::Number, &number_field_meta) + .unwrap() + .content, + "$18,443".to_owned() + ); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs index 2bb78a0bf0..9911e4cab3 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs @@ -1,22 +1,26 @@ use crate::services::field::*; -use flowy_error::FlowyError; +use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{CellMeta, FieldMeta, FieldType}; use serde::{Deserialize, Serialize}; use std::fmt::Formatter; use std::str::FromStr; pub trait CellDataOperation { - fn decode_cell_data>( + fn decode_cell_data( &self, - data: T, + encoded_data: T, decoded_field_type: &FieldType, field_meta: &FieldMeta, - ) -> DecodedCellData; + ) -> FlowyResult + where + T: Into; + + // fn apply_changeset>( &self, changeset: C, cell_meta: Option, - ) -> Result; + ) -> FlowyResult; } #[derive(Debug)] @@ -72,14 +76,6 @@ impl std::convert::TryInto for String { } } -// impl std::convert::Into for String { -// type Error = FlowyError; -// -// fn try_into(self) -> Result { -// TypeOptionCellData::from_str(&self) -// } -// } - impl TypeOptionCellData { pub fn new(data: T, field_type: FieldType) -> Self { TypeOptionCellData { @@ -142,40 +138,89 @@ pub fn apply_cell_data_changeset>( Ok(TypeOptionCellData::new(s, field_meta.field_type.clone()).json()) } -pub fn decode_cell_data>( +pub fn decode_cell_data_from_type_option_cell_data>( data: T, field_meta: &FieldMeta, field_type: &FieldType, -) -> Option { +) -> DecodedCellData { if let Ok(type_option_cell_data) = data.try_into() { - let (decoded_field_type, data) = &type_option_cell_data.split(); - let s = match field_type { - FieldType::RichText => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta), - FieldType::Number => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta), - FieldType::DateTime => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta), - FieldType::SingleSelect => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta), - FieldType::MultiSelect => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta), - FieldType::Checkbox => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta), - }; - tracing::Span::current().record( - "content", - &format!("{:?}: {}", field_meta.field_type, s.content).as_str(), - ); - Some(s) + 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 { - Some(DecodedCellData::default()) + DecodedCellData::default() + } +} + +pub fn decode_cell_data>( + encoded_data: T, + s_field_type: &FieldType, + t_field_type: &FieldType, + field_meta: &FieldMeta, +) -> FlowyResult { + let encoded_data = encoded_data.into(); + let get_cell_data = || { + let data = match t_field_type { + FieldType::RichText => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::Number => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::DateTime => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::SingleSelect => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::MultiSelect => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::Checkbox => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + }; + Some(data) + }; + + match get_cell_data() { + Some(Ok(data)) => { + tracing::Span::current().record( + "content", + &format!("{:?}: {}", field_meta.field_type, data.content).as_str(), + ); + Ok(data) + } + Some(Err(err)) => { + tracing::error!("{:?}", err); + Ok(DecodedCellData::default()) + } + None => Ok(DecodedCellData::default()), + } +} + +pub(crate) struct EncodedCellData(pub Option); + +impl EncodedCellData { + pub fn try_into_inner(self) -> FlowyResult { + match self.0 { + None => Err(ErrorCode::InvalidData.into()), + Some(data) => Ok(data), + } + } +} + +impl std::convert::From for EncodedCellData +where + T: FromStr, +{ + 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) + } + } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs index edcb102595..af4af0e491 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs @@ -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_grid_data_model::entities::{ Cell, CellMeta, FieldMeta, GridBlock, GridBlockOrder, RepeatedGridBlock, Row, RowMeta, RowOrder, @@ -31,14 +31,16 @@ pub fn make_cell_by_field_id( cell_meta: CellMeta, ) -> Option<(String, Cell)> { 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); Some((field_id, cell)) } pub fn make_cell(field_id: &str, field_meta: &FieldMeta, row_meta: &RowMeta) -> Option { 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)) } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs index a954694a16..f4549cb726 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs @@ -5,7 +5,7 @@ use flowy_grid::services::field::{ DateCellContentChangeset, MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset, 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::{ CellChangeset, FieldChangesetParams, FieldType, GridBlockMeta, GridBlockMetaChangeset, RowMetaChangeset, TypeOptionDataEntry, @@ -291,8 +291,7 @@ async fn grid_row_add_date_cell_test() { let date_field = date_field.unwrap(); let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone(); assert_eq!( - decode_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type) - .unwrap() + decode_cell_data_from_type_option_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type) .split() .1, "2022/03/16", diff --git a/shared-lib/flowy-grid-data-model/src/entities/grid.rs b/shared-lib/flowy-grid-data-model/src/entities/grid.rs index 5cde193623..e8f3d615c4 100644 --- a/shared-lib/flowy-grid-data-model/src/entities/grid.rs +++ b/shared-lib/flowy-grid-data-model/src/entities/grid.rs @@ -912,6 +912,34 @@ impl FieldType { _ => 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)] diff --git a/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs b/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs index a65d4d5bd9..a44f0cb267 100644 --- a/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs +++ b/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs @@ -153,27 +153,22 @@ pub fn check_pb_dart_plugin() { let output = Command::new("sh").arg("-c").arg("echo $PATH").output(); let paths = String::from_utf8(output.unwrap().stdout) .unwrap() - .split(":") + .split(':') .map(|s| s.to_string()) .collect::>(); paths.iter().for_each(|s| msg.push_str(&format!("{}\n", s))); - match Command::new("sh").arg("-c").arg("which protoc-gen-dart").output() { - Ok(output) => { - msg.push_str(&format!( - "Installed protoc-gen-dart path: {:?}\n", - String::from_utf8(output.stdout).unwrap() - )); - } - Err(_) => {} + if let Ok(output) = Command::new("sh").arg("-c").arg("which protoc-gen-dart").output() { + msg.push_str(&format!( + "Installed protoc-gen-dart path: {:?}\n", + String::from_utf8(output.stdout).unwrap() + )); } - msg.push_str(&format!("✅ You can fix that by adding:")); - msg.push_str(&format!("\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n",)); - msg.push_str(&format!( - "to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)" - )); + msg.push_str(&"✅ You can fix that by adding:".to_string()); + msg.push_str(&"\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n".to_string()); + msg.push_str(&"to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)".to_string()); panic!("{}", msg) } } From 2698fb43ad8832a3c99bf2859431a93e170a9b3d Mon Sep 17 00:00:00 2001 From: appflowy Date: Sat, 14 May 2022 18:21:32 +0800 Subject: [PATCH 08/23] chore: upgrade to flutter 3.0 --- .../user/presentation/skip_log_in_screen.dart | 5 +- .../workspace/application/app/app_bloc.dart | 2 + .../grid/cell/select_option_editor_bloc.dart | 1 + .../application/grid/grid_service.dart | 4 +- .../home/menu/app/section/section.dart | 2 +- .../doc/src/widget/toolbar/tool_bar.dart | 4 +- .../emoji_picker/src/emoji_picker.dart | 2 +- .../widgets/float_bubble/question_bubble.dart | 5 +- .../linux/flutter/generated_plugins.cmake | 8 + .../macos/Runner.xcodeproj/project.pbxproj | 3 + frontend/app_flowy/pubspec.lock | 237 ++++++++++-------- frontend/app_flowy/pubspec.yaml | 6 +- 12 files changed, 158 insertions(+), 121 deletions(-) diff --git a/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart b/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart index bad8000643..4a78815beb 100644 --- a/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart +++ b/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart @@ -88,8 +88,9 @@ class _SkipLogInScreenState extends State { } _launchURL(String url) async { - if (await canLaunch(url)) { - await launch(url); + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); } else { throw 'Could not launch $url'; } diff --git a/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart b/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart index 663e630a6a..e5eabe98e4 100644 --- a/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + import 'package:app_flowy/plugin/plugin.dart'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/app/app_listener.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart index 2702e6006f..a1e1315682 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart @@ -6,6 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; import 'select_option_service.dart'; +import 'package:collection/collection.dart'; part 'select_option_editor_bloc.freezed.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart index 4ed54c5f3b..38ebfc63fc 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; @@ -6,8 +8,6 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - import 'cell/cell_service/cell_service.dart'; import 'row/row_service.dart'; diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart index d9fd618e9a..d352db6620 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart @@ -26,7 +26,7 @@ class ViewSection extends StatelessWidget { listenWhen: (p, c) => p.selectedView != c.selectedView, listener: (context, state) { if (state.selectedView != null) { - WidgetsBinding.instance?.addPostFrameCallback((_) { + WidgetsBinding.instance.addPostFrameCallback((_) { getIt().setPlugin(state.selectedView!.plugin()); }); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/tool_bar.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/tool_bar.dart index 2ab0b78cc9..8dae41f986 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/tool_bar.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/tool_bar.dart @@ -184,7 +184,7 @@ class _ToolbarButtonListState extends State with WidgetsBindi // Listening to the WidgetsBinding instance is necessary so that we can // hide the arrows when the window gets a new size and thus the toolbar // becomes scrollable/unscrollable. - WidgetsBinding.instance!.addObserver(this); + WidgetsBinding.instance.addObserver(this); // Workaround to allow the scroll controller attach to our ListView so that // we can detect if overflow arrows need to be shown on init. @@ -226,7 +226,7 @@ class _ToolbarButtonListState extends State with WidgetsBindi @override void dispose() { _controller.dispose(); - WidgetsBinding.instance!.removeObserver(this); + WidgetsBinding.instance.removeObserver(this); super.dispose(); } diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker.dart index f1e4420252..650cd185f1 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker.dart @@ -135,7 +135,7 @@ class _EmojiPickerState extends State { if (!loaded) { // Load emojis updateEmojiFuture.then( - (value) => WidgetsBinding.instance!.addPostFrameCallback((_) { + (value) => WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; setState(() { loaded = true; diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart index 91ce8d07ec..762a978693 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart @@ -62,8 +62,9 @@ class QuestionBubble extends StatelessWidget { } _launchURL(String url) async { - if (await canLaunch(url)) { - await launch(url); + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); } else { throw 'Could not launch $url'; } diff --git a/frontend/app_flowy/linux/flutter/generated_plugins.cmake b/frontend/app_flowy/linux/flutter/generated_plugins.cmake index 5562f19113..c7ae414da2 100644 --- a/frontend/app_flowy/linux/flutter/generated_plugins.cmake +++ b/frontend/app_flowy/linux/flutter/generated_plugins.cmake @@ -8,6 +8,9 @@ list(APPEND FLUTTER_PLUGIN_LIST window_size ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -16,3 +19,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj b/frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj index 08e2493683..2e7ab66fee 100644 --- a/frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj +++ b/frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj @@ -421,6 +421,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + EXCLUDED_ARCHS = arm64; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -552,6 +553,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + EXCLUDED_ARCHS = arm64; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -575,6 +577,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + EXCLUDED_ARCHS = arm64; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/frontend/app_flowy/pubspec.lock b/frontend/app_flowy/pubspec.lock index 8f7e6d6db2..65d66c67a5 100644 --- a/frontend/app_flowy/pubspec.lock +++ b/frontend/app_flowy/pubspec.lock @@ -7,28 +7,28 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "31.0.0" + version: "38.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "2.8.0" + version: "3.4.1" animations: dependency: transitive description: name: animations url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.3" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "2.3.0" + version: "2.3.1" async: dependency: transitive description: @@ -42,14 +42,14 @@ packages: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "8.0.2" + version: "8.0.3" bloc_test: dependency: "direct dev" description: name: bloc_test url: "https://pub.dartlang.org" source: hosted - version: "9.0.2" + version: "9.0.3" boolean_selector: dependency: transitive description: @@ -63,7 +63,7 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.3.0" build_config: dependency: transitive description: @@ -77,21 +77,21 @@ packages: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.1.0" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.8" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "2.1.7" + version: "2.1.11" build_runner_core: dependency: transitive description: @@ -112,7 +112,7 @@ packages: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.1.4" + version: "8.3.2" characters: dependency: transitive description: @@ -134,13 +134,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" - cli_util: - dependency: transitive - description: - name: cli_util - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.5" clipboard: dependency: "direct main" description: @@ -168,7 +161,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" connectivity_plus: dependency: "direct main" description: @@ -182,14 +175,14 @@ packages: name: connectivity_plus_linux url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" connectivity_plus_macos: dependency: transitive description: name: connectivity_plus_macos url: "https://pub.dartlang.org" source: hosted - version: "1.2.1" + version: "1.2.2" connectivity_plus_platform_interface: dependency: transitive description: @@ -224,21 +217,21 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.3.2" cross_file: dependency: transitive description: name: cross_file url: "https://pub.dartlang.org" source: hosted - version: "0.3.2" + version: "0.3.3+1" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" csslib: dependency: transitive description: @@ -259,7 +252,7 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.2.3" dartz: dependency: transitive description: @@ -273,14 +266,14 @@ packages: name: dbus url: "https://pub.dartlang.org" source: hosted - version: "0.6.8" + version: "0.7.3" device_info_plus: dependency: "direct main" description: name: device_info_plus url: "https://pub.dartlang.org" source: hosted - version: "3.2.1" + version: "3.2.3" device_info_plus_linux: dependency: transitive description: @@ -294,7 +287,7 @@ packages: name: device_info_plus_macos url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.2.3" device_info_plus_platform_interface: dependency: transitive description: @@ -329,7 +322,7 @@ packages: name: easy_localization url: "https://pub.dartlang.org" source: hosted - version: "3.0.1-dev" + version: "3.0.1" easy_logger: dependency: transitive description: @@ -357,14 +350,14 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" ffi: dependency: transitive description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.2.1" file: dependency: transitive description: @@ -378,7 +371,7 @@ packages: name: fixnum url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" flowy_infra: dependency: "direct main" description: @@ -439,14 +432,14 @@ packages: name: flutter_inappwebview url: "https://pub.dartlang.org" source: hosted - version: "5.3.2" + version: "5.4.3+7" flutter_keyboard_visibility: dependency: transitive description: name: flutter_keyboard_visibility url: "https://pub.dartlang.org" source: hosted - version: "5.1.1" + version: "5.2.0" flutter_keyboard_visibility_platform_interface: dependency: transitive description: @@ -479,13 +472,13 @@ packages: name: flutter_plugin_android_lifecycle url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.6" flutter_quill: dependency: "direct main" description: path: "." - ref: dbc309f5e382963fa0122409a0aaf8e1417d51c1 - resolved-ref: dbc309f5e382963fa0122409a0aaf8e1417d51c1 + ref: "306fd78b7a134abdde0fed6be67f59e8a6068509" + resolved-ref: "306fd78b7a134abdde0fed6be67f59e8a6068509" url: "https://github.com/appflowy/flutter-quill.git" source: git version: "2.0.13" @@ -519,21 +512,21 @@ packages: name: freezed url: "https://pub.dartlang.org" source: hosted - version: "0.14.5" + version: "2.0.3+1" freezed_annotation: dependency: "direct main" description: name: freezed_annotation url: "https://pub.dartlang.org" source: hosted - version: "0.14.3" + version: "2.0.3" frontend_server_client: dependency: transitive description: name: frontend_server_client url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.1.3" get_it: dependency: "direct main" description: @@ -582,42 +575,56 @@ packages: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.2.0" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.0.1" i18n_extension: dependency: transitive description: name: i18n_extension url: "https://pub.dartlang.org" source: hosted - version: "4.2.0" + version: "4.2.1" image_picker: dependency: transitive description: name: image_picker url: "https://pub.dartlang.org" source: hosted - version: "0.8.4+4" + version: "0.8.5+3" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.4+13" image_picker_for_web: dependency: transitive description: name: image_picker_for_web url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "2.1.8" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.5+5" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.4.3" + version: "2.5.0" intl: dependency: "direct main" description: @@ -645,14 +652,14 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3" + version: "0.6.4" json_annotation: dependency: transitive description: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.4.0" + version: "4.5.0" linked_scroll_controller: dependency: "direct main" description: @@ -666,7 +673,7 @@ packages: name: lint url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" lints: dependency: transitive description: @@ -680,7 +687,7 @@ packages: name: loading_indicator url: "https://pub.dartlang.org" source: hosted - version: "3.0.2" + version: "3.1.0" logger: dependency: transitive description: @@ -708,7 +715,7 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "0.1.4" meta: dependency: transitive description: @@ -722,14 +729,14 @@ packages: name: mime url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" mocktail: dependency: transitive description: name: mocktail url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "0.3.0" nested: dependency: transitive description: @@ -743,7 +750,7 @@ packages: name: nm url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.5.0" node_preamble: dependency: transitive description: @@ -764,14 +771,14 @@ packages: name: package_info_plus url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.4.2" package_info_plus_linux: dependency: transitive description: name: package_info_plus_linux url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.5" package_info_plus_macos: dependency: transitive description: @@ -792,21 +799,21 @@ packages: name: package_info_plus_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" package_info_plus_windows: dependency: transitive description: name: package_info_plus_windows url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" path_drawing: dependency: transitive description: @@ -827,49 +834,49 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.0.10" path_provider_android: dependency: transitive description: name: path_provider_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.11" + version: "2.0.14" path_provider_ios: dependency: transitive description: name: path_provider_ios url: "https://pub.dartlang.org" source: hosted - version: "2.0.7" + version: "2.0.9" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "2.1.6" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.6" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.6" pedantic: dependency: transitive description: @@ -883,7 +890,7 @@ packages: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "4.4.0" + version: "5.0.0" photo_view: dependency: transitive description: @@ -932,14 +939,14 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "6.0.2" + version: "6.0.3" pub_semver: dependency: transitive description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" pubspec_parse: dependency: transitive description: @@ -953,49 +960,49 @@ packages: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "3.0.1+1" + version: "3.1.0" reorderables: dependency: "direct main" description: name: reorderables url: "https://pub.dartlang.org" source: hosted - version: "0.4.3" + version: "0.5.0" shared_preferences: dependency: transitive description: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "2.0.12" + version: "2.0.15" shared_preferences_android: dependency: transitive description: name: shared_preferences_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.10" + version: "2.0.12" shared_preferences_ios: dependency: transitive description: name: shared_preferences_ios url: "https://pub.dartlang.org" source: hosted - version: "2.0.9" + version: "2.1.1" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.1.1" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.4" shared_preferences_platform_interface: dependency: transitive description: @@ -1009,21 +1016,21 @@ packages: name: shared_preferences_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.1.1" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" shelf_packages_handler: dependency: transitive description: @@ -1070,7 +1077,7 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "1.2.1" + version: "1.2.2" source_map_stack_trace: dependency: transitive description: @@ -1091,7 +1098,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" sprintf: dependency: transitive description: @@ -1161,28 +1168,28 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.19.5" + version: "1.21.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.8" + version: "0.4.9" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.13" textfield_tags: dependency: "direct main" description: name: textfield_tags url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.0+1" textstyle_extensions: dependency: transitive description: @@ -1217,7 +1224,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" universal_platform: dependency: transitive description: @@ -1231,35 +1238,35 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.0.18" + version: "6.1.2" url_launcher_android: dependency: transitive description: name: url_launcher_android url: "https://pub.dartlang.org" source: hosted - version: "6.0.14" + version: "6.0.17" url_launcher_ios: dependency: transitive description: name: url_launcher_ios url: "https://pub.dartlang.org" source: hosted - version: "6.0.14" + version: "6.0.17" url_launcher_linux: dependency: transitive description: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "3.0.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "3.0.1" url_launcher_platform_interface: dependency: transitive description: @@ -1273,56 +1280,70 @@ packages: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.11" url_launcher_windows: dependency: transitive description: name: url_launcher_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "3.0.1" uuid: dependency: transitive description: name: uuid url: "https://pub.dartlang.org" source: hosted - version: "3.0.5" + version: "3.0.6" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" video_player: dependency: transitive description: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "2.2.11" + version: "2.4.2" + video_player_android: + dependency: transitive + description: + name: video_player_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.4" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.4" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "5.0.1" + version: "5.1.2" video_player_web: dependency: transitive description: name: video_player_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.10" vm_service: dependency: transitive description: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "7.5.0" + version: "8.3.0" watcher: dependency: transitive description: @@ -1336,21 +1357,21 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.2.0" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.0" win32: dependency: transitive description: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.3.6" + version: "2.6.1" window_size: dependency: "direct main" description: @@ -1366,28 +1387,28 @@ packages: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "0.2.0+1" xml: dependency: transitive description: name: xml url: "https://pub.dartlang.org" source: hosted - version: "5.3.1" + version: "5.4.1" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "3.1.1" youtube_player_flutter: dependency: transitive description: name: youtube_player_flutter url: "https://pub.dartlang.org" source: hosted - version: "8.0.0" + version: "8.1.0" sdks: - dart: ">=2.15.0-116.0.dev <3.0.0" - flutter: ">=2.5.0" + dart: ">=2.17.1 <3.0.0" + flutter: ">=3.0.0" diff --git a/frontend/app_flowy/pubspec.yaml b/frontend/app_flowy/pubspec.yaml index fd2b75b9e0..f06eedf614 100644 --- a/frontend/app_flowy/pubspec.yaml +++ b/frontend/app_flowy/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.15.0-116.0.dev <3.0.0" + sdk: ">=2.17.1 <3.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -40,7 +40,7 @@ dependencies: flutter_quill: git: url: https://github.com/appflowy/flutter-quill.git - ref: dbc309f5e382963fa0122409a0aaf8e1417d51c1 + ref: 306fd78b7a134abdde0fed6be67f59e8a6068509 # third party packages intl: ^0.17.0 @@ -74,7 +74,7 @@ dependencies: device_info_plus: ^3.2.1 fluttertoast: ^8.0.8 table_calendar: ^3.0.5 - reorderables: + reorderables: ^0.5.0 linked_scroll_controller: ^0.2.0 dev_dependencies: From ef2e9cb57ee87b0e0250f061898f643d2c57ec81 Mon Sep 17 00:00:00 2001 From: appflowy Date: Wed, 25 May 2022 16:10:47 +0800 Subject: [PATCH 09/23] chore: update flutter version in ci --- .github/workflows/dart_lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dart_lint.yml b/.github/workflows/dart_lint.yml index 81460cec1c..ca0536906e 100644 --- a/.github/workflows/dart_lint.yml +++ b/.github/workflows/dart_lint.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v2 - uses: subosito/flutter-action@v1 with: - flutter-version: '2.10.0' + flutter-version: '3.0.0' channel: "stable" - name: Deps Flutter run: flutter packages pub get From 896123860dd45bfa652226d3e4805f4c8c818de3 Mon Sep 17 00:00:00 2001 From: appflowy Date: Wed, 25 May 2022 16:18:47 +0800 Subject: [PATCH 10/23] chore: update flutter sdk version --- .github/workflows/dart_test.yml | 1 + frontend/app_flowy/pubspec.lock | 2 +- frontend/app_flowy/pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dart_test.yml b/.github/workflows/dart_test.yml index db479d2905..9938f02091 100644 --- a/.github/workflows/dart_test.yml +++ b/.github/workflows/dart_test.yml @@ -25,6 +25,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: 'stable' + flutter-version: '3.0.0' cache: true - name: Cache Cargo diff --git a/frontend/app_flowy/pubspec.lock b/frontend/app_flowy/pubspec.lock index 65d66c67a5..958debd9dd 100644 --- a/frontend/app_flowy/pubspec.lock +++ b/frontend/app_flowy/pubspec.lock @@ -1410,5 +1410,5 @@ packages: source: hosted version: "8.1.0" sdks: - dart: ">=2.17.1 <3.0.0" + dart: ">=2.17.0 <3.0.0" flutter: ">=3.0.0" diff --git a/frontend/app_flowy/pubspec.yaml b/frontend/app_flowy/pubspec.yaml index f06eedf614..eb7b17dc21 100644 --- a/frontend/app_flowy/pubspec.yaml +++ b/frontend/app_flowy/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.17.1 <3.0.0" + sdk: ">=2.16.2 <3.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions From aad33f6746ac916134099891a5dbc2bcbeaecc91 Mon Sep 17 00:00:00 2001 From: appflowy Date: Wed, 25 May 2022 17:01:34 +0800 Subject: [PATCH 11/23] chore: update flutter toolchain version to flutter 3.0 --- .github/workflows/ci.yaml | 1 + .github/workflows/release.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 40f51c8132..ab1becf24f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -34,6 +34,7 @@ jobs: with: channel: 'stable' cache: true + flutter-version: '3.0.0' - name: Cache Cargo uses: actions/cache@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 93dedaa43d..4d500c4659 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,6 +50,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: 'stable' + flutter-version: '3.0.0' - name: Pre build working-directory: frontend @@ -98,6 +99,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: 'stable' + flutter-version: '3.0.0' - name: Pre build working-directory: frontend From 89ff8517fa485707c2ee71d343fc46997b9659aa Mon Sep 17 00:00:00 2001 From: appflowy Date: Thu, 26 May 2022 15:58:13 +0800 Subject: [PATCH 12/23] chore: udpate launch task --- frontend/app_flowy/.vscode/launch.json | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/frontend/app_flowy/.vscode/launch.json b/frontend/app_flowy/.vscode/launch.json index b271a1e6a1..e5ea3cdf6f 100644 --- a/frontend/app_flowy/.vscode/launch.json +++ b/frontend/app_flowy/.vscode/launch.json @@ -5,18 +5,30 @@ "version": "0.2.0", "configurations": [ { - "name": "app_flowy", + // This task builds the Rust and Dart code of AppFlowy. + "name": "Build", "request": "launch", "program": "${workspaceRoot}/lib/main.dart", - "type": "dart", "preLaunchTask": "build_flowy_sdk", + "type": "dart", "env": { "RUST_LOG": "debug" }, "cwd": "${workspaceRoot}" }, { - "name": "app_flowy(trace)", + // This task only build the Dart code of AppFlowy. + "name": "Build (Dart)", + "request": "launch", + "program": "${workspaceRoot}/lib/main.dart", + "type": "dart", + "env": { + "RUST_LOG": "debug" + }, + "cwd": "${workspaceRoot}" + }, + { + "name": "Build (trace log)", "request": "launch", "program": "${workspaceRoot}/lib/main.dart", "type": "dart", @@ -27,7 +39,7 @@ "cwd": "${workspaceRoot}" }, { - "name": "app_flowy (profile mode)", + "name": "Build (profile mode)", "request": "launch", "type": "dart", "flutterMode": "profile" From 8706b81ac0cb208247d939322ae208eb1b2bebc5 Mon Sep 17 00:00:00 2001 From: appflowy Date: Thu, 26 May 2022 17:28:44 +0800 Subject: [PATCH 13/23] refactor: rename struct --- frontend/rust-lib/flowy-folder/src/manager.rs | 9 ++- .../src/services/app/event_handler.rs | 2 +- .../src/services/folder_editor.rs | 6 +- .../src/services/persistence/mod.rs | 9 +-- .../services/persistence/version_2/v2_impl.rs | 4 +- .../src/services/view/controller.rs | 4 +- .../flowy-folder/tests/workspace/script.rs | 4 +- .../rust-lib/flowy-grid/src/event_handler.rs | 36 +++++------ frontend/rust-lib/flowy-grid/src/manager.rs | 64 ++++++------------- .../src/services/block_meta_editor.rs | 5 +- .../src/services/block_meta_manager.rs | 36 +++++------ .../flowy-grid/src/services/grid_editor.rs | 54 ++++++++-------- .../src/services/persistence/block_index.rs | 6 +- .../rust-lib/flowy-grid/tests/grid/script.rs | 6 +- .../rust-lib/flowy-text-block/src/editor.rs | 8 +-- .../rust-lib/flowy-text-block/src/manager.rs | 18 +++--- .../flowy-text-block/tests/document/script.rs | 4 +- .../src/client_grid/grid_block_meta_pad.rs | 2 +- 18 files changed, 124 insertions(+), 153 deletions(-) diff --git a/frontend/rust-lib/flowy-folder/src/manager.rs b/frontend/rust-lib/flowy-folder/src/manager.rs index 05847f8cc1..fe9a160b39 100644 --- a/frontend/rust-lib/flowy-folder/src/manager.rs +++ b/frontend/rust-lib/flowy-folder/src/manager.rs @@ -4,7 +4,7 @@ use crate::{ errors::FlowyResult, event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser}, services::{ - folder_editor::ClientFolderEditor, persistence::FolderPersistence, set_current_workspace, AppController, + folder_editor::FolderEditor, persistence::FolderPersistence, set_current_workspace, AppController, TrashController, ViewController, WorkspaceController, }, }; @@ -61,7 +61,7 @@ pub struct FolderManager { pub(crate) view_controller: Arc, pub(crate) trash_controller: Arc, web_socket: Arc, - folder_editor: Arc>>>, + folder_editor: Arc>>>, data_processors: ViewDataProcessorMap, } @@ -166,8 +166,7 @@ impl FolderManager { let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), disk_cache)); let rev_manager = RevisionManager::new(user_id, folder_id.as_ref(), rev_persistence); - let folder_editor = - ClientFolderEditor::new(user_id, &folder_id, token, rev_manager, self.web_socket.clone()).await?; + let folder_editor = FolderEditor::new(user_id, &folder_id, token, rev_manager, self.web_socket.clone()).await?; *self.folder_editor.write().await = Some(Arc::new(folder_editor)); let _ = self.app_controller.initialize()?; @@ -228,7 +227,7 @@ impl DefaultFolderBuilder { #[cfg(feature = "flowy_unit_test")] impl FolderManager { - pub async fn folder_editor(&self) -> Arc { + pub async fn folder_editor(&self) -> Arc { self.folder_editor.read().await.clone().unwrap() } } diff --git a/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs index 45034646b0..b95b91dc2e 100644 --- a/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs @@ -46,7 +46,7 @@ pub(crate) async fn update_app_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, app_controller, view_controller))] +#[tracing::instrument(level = "trace", skip(data, app_controller, view_controller))] pub(crate) async fn read_app_handler( data: Data, app_controller: AppData>, diff --git a/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs b/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs index 6ea1a7e2ec..ef94c1d316 100644 --- a/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs +++ b/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs @@ -17,7 +17,7 @@ use lib_ot::core::PlainTextAttributes; use parking_lot::RwLock; use std::sync::Arc; -pub struct ClientFolderEditor { +pub struct FolderEditor { user_id: String, #[allow(dead_code)] pub(crate) folder_id: FolderId, @@ -27,7 +27,7 @@ pub struct ClientFolderEditor { ws_manager: Arc, } -impl ClientFolderEditor { +impl FolderEditor { #[allow(unused_variables)] pub async fn new( user_id: &str, @@ -129,7 +129,7 @@ impl RevisionCloudService for FolderRevisionCloudService { } #[cfg(feature = "flowy_unit_test")] -impl ClientFolderEditor { +impl FolderEditor { pub fn rev_manager(&self) -> Arc { self.rev_manager.clone() } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs index b272168e33..d5bbb3a277 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs @@ -5,7 +5,7 @@ mod version_2; use crate::{ event_map::WorkspaceDatabase, manager::FolderId, - services::{folder_editor::ClientFolderEditor, persistence::migration::FolderMigration}, + services::{folder_editor::FolderEditor, persistence::migration::FolderMigration}, }; use flowy_database::ConnectionPool; use flowy_error::{FlowyError, FlowyResult}; @@ -50,14 +50,11 @@ pub trait FolderPersistenceTransaction { pub struct FolderPersistence { database: Arc, - folder_editor: Arc>>>, + folder_editor: Arc>>>, } impl FolderPersistence { - pub fn new( - database: Arc, - folder_editor: Arc>>>, - ) -> Self { + pub fn new(database: Arc, folder_editor: Arc>>>) -> Self { Self { database, folder_editor, diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs index 8dafda3c93..40f72e10b0 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs @@ -1,5 +1,5 @@ use crate::services::{ - folder_editor::ClientFolderEditor, + folder_editor::FolderEditor, persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset}, }; use flowy_error::{FlowyError, FlowyResult}; @@ -11,7 +11,7 @@ use flowy_folder_data_model::entities::{ }; use std::sync::Arc; -impl FolderPersistenceTransaction for ClientFolderEditor { +impl FolderPersistenceTransaction for FolderEditor { fn create_workspace(&self, _user_id: &str, workspace: Workspace) -> FlowyResult<()> { if let Some(change) = self.folder.write().create_workspace(workspace)? { let _ = self.apply_change(change)?; diff --git a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs index 603b76ec2c..ae88073afc 100644 --- a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs @@ -129,7 +129,7 @@ impl ViewController { .await } - #[tracing::instrument(level = "debug", skip(self), err)] + #[tracing::instrument(level = "trace", skip(self), err)] pub(crate) fn set_latest_view(&self, view_id: &str) -> Result<(), FlowyError> { KV::set_str(LATEST_VIEW_ID, view_id.to_owned()); Ok(()) @@ -193,7 +193,7 @@ impl ViewController { } // belong_to_id will be the app_id or view_id. - #[tracing::instrument(level = "debug", skip(self), err)] + #[tracing::instrument(level = "trace", skip(self), err)] pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result { self.persistence .begin_transaction(|transaction| { diff --git a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs b/frontend/rust-lib/flowy-folder/tests/workspace/script.rs index a6fd1fbe29..39f023ca7d 100644 --- a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs +++ b/frontend/rust-lib/flowy-folder/tests/workspace/script.rs @@ -1,5 +1,5 @@ use flowy_folder::event_map::FolderEvent::*; -use flowy_folder::{errors::ErrorCode, services::folder_editor::ClientFolderEditor}; +use flowy_folder::{errors::ErrorCode, services::folder_editor::FolderEditor}; use flowy_folder_data_model::entities::view::{RepeatedViewId, ViewId}; use flowy_folder_data_model::entities::workspace::WorkspaceId; use flowy_folder_data_model::entities::{ @@ -125,7 +125,7 @@ impl FolderTest { pub async fn run_script(&mut self, script: FolderScript) { let sdk = &self.sdk; - let folder_editor: Arc = sdk.folder_manager.folder_editor().await; + let folder_editor: Arc = sdk.folder_manager.folder_editor().await; let rev_manager = folder_editor.rev_manager(); let cache = rev_manager.revision_cache().await; diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index 44123a45d9..cf5c095ee9 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -7,7 +7,7 @@ use flowy_grid_data_model::entities::*; use lib_dispatch::prelude::{data_result, AppData, Data, DataResult}; use std::sync::Arc; -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_grid_data_handler( data: Data, manager: AppData>, @@ -34,7 +34,7 @@ pub(crate) async fn get_grid_blocks_handler( data_result(repeated_grid_block) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_fields_handler( data: Data, manager: AppData>, @@ -47,7 +47,7 @@ pub(crate) async fn get_fields_handler( data_result(repeated_field) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_field_handler( data: Data, manager: AppData>, @@ -58,7 +58,7 @@ pub(crate) async fn update_field_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn insert_field_handler( data: Data, manager: AppData>, @@ -69,7 +69,7 @@ pub(crate) async fn insert_field_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_field_type_option_handler( data: Data, manager: AppData>, @@ -82,7 +82,7 @@ pub(crate) async fn update_field_type_option_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn delete_field_handler( data: Data, manager: AppData>, @@ -93,7 +93,7 @@ pub(crate) async fn delete_field_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn switch_to_field_handler( data: Data, manager: AppData>, @@ -120,7 +120,7 @@ pub(crate) async fn switch_to_field_handler( data_result(data) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn duplicate_field_handler( data: Data, manager: AppData>, @@ -132,7 +132,7 @@ pub(crate) async fn duplicate_field_handler( } /// Return the FieldTypeOptionData if the Field exists otherwise return record not found error. -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_field_type_option_data_handler( data: Data, manager: AppData>, @@ -154,7 +154,7 @@ pub(crate) async fn get_field_type_option_data_handler( } /// Create FieldMeta and save it. Return the FieldTypeOptionData. -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn create_field_type_option_data_handler( data: Data, manager: AppData>, @@ -171,7 +171,7 @@ pub(crate) async fn create_field_type_option_data_handler( }) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn move_item_handler( data: Data, manager: AppData>, @@ -252,7 +252,7 @@ pub(crate) async fn get_cell_handler( } } -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_cell_handler( data: Data, manager: AppData>, @@ -263,7 +263,7 @@ pub(crate) async fn update_cell_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_date_cell_data_handler( data: Data, manager: AppData>, @@ -284,7 +284,7 @@ pub(crate) async fn get_date_cell_data_handler( } } -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn new_select_option_handler( data: Data, manager: AppData>, @@ -301,7 +301,7 @@ pub(crate) async fn new_select_option_handler( } } -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_select_option_handler( data: Data, manager: AppData>, @@ -341,7 +341,7 @@ pub(crate) async fn update_select_option_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_select_option_handler( data: Data, manager: AppData>, @@ -362,7 +362,7 @@ pub(crate) async fn get_select_option_handler( } } -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_select_option_cell_handler( data: Data, manager: AppData>, @@ -373,7 +373,7 @@ pub(crate) async fn update_select_option_cell_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_date_cell_handler( data: Data, manager: AppData>, diff --git a/frontend/rust-lib/flowy-grid/src/manager.rs b/frontend/rust-lib/flowy-grid/src/manager.rs index 218dff8782..f8ea9e70ae 100644 --- a/frontend/rust-lib/flowy-grid/src/manager.rs +++ b/frontend/rust-lib/flowy-grid/src/manager.rs @@ -1,5 +1,5 @@ -use crate::services::grid_editor::ClientGridEditor; -use crate::services::persistence::block_index::BlockIndexPersistence; +use crate::services::grid_editor::GridMetaEditor; +use crate::services::persistence::block_index::BlockIndexCache; use crate::services::persistence::kv::GridKVPersistence; use crate::services::persistence::GridDatabase; use bytes::Bytes; @@ -20,9 +20,9 @@ pub trait GridUser: Send + Sync { } pub struct GridManager { - editor_map: Arc, + editor_map: Arc>>, grid_user: Arc, - block_index_persistence: Arc, + block_index_cache: Arc, #[allow(dead_code)] kv_persistence: Arc, } @@ -33,14 +33,14 @@ impl GridManager { _rev_web_socket: Arc, database: Arc, ) -> Self { - let grid_editors = Arc::new(GridEditorMap::new()); + let grid_editors = Arc::new(DashMap::new()); let kv_persistence = Arc::new(GridKVPersistence::new(database.clone())); - let block_index_persistence = Arc::new(BlockIndexPersistence::new(database)); + let block_index_persistence = Arc::new(BlockIndexCache::new(database)); Self { editor_map: grid_editors, grid_user, kv_persistence, - block_index_persistence, + block_index_cache: block_index_persistence, } } @@ -67,7 +67,7 @@ impl GridManager { } #[tracing::instrument(level = "debug", skip_all, fields(grid_id), err)] - pub async fn open_grid>(&self, grid_id: T) -> FlowyResult> { + pub async fn open_grid>(&self, grid_id: T) -> FlowyResult> { let grid_id = grid_id.as_ref(); tracing::Span::current().record("grid_id", &grid_id); self.get_or_create_grid_editor(grid_id).await @@ -90,23 +90,27 @@ impl GridManager { } // #[tracing::instrument(level = "debug", skip(self), err)] - pub fn get_grid_editor(&self, grid_id: &str) -> FlowyResult> { + pub fn get_grid_editor(&self, grid_id: &str) -> FlowyResult> { match self.editor_map.get(grid_id) { None => Err(FlowyError::internal().context("Should call open_grid function first")), - Some(editor) => Ok(editor), + Some(editor) => Ok(editor.clone()), } } - async fn get_or_create_grid_editor(&self, grid_id: &str) -> FlowyResult> { + async fn get_or_create_grid_editor(&self, grid_id: &str) -> FlowyResult> { match self.editor_map.get(grid_id) { None => { tracing::trace!("Create grid editor with id: {}", grid_id); let db_pool = self.grid_user.db_pool()?; let editor = self.make_grid_editor(grid_id, db_pool).await?; - self.editor_map.insert(grid_id, &editor); + + if self.editor_map.contains_key(grid_id) { + tracing::warn!("Grid:{} already exists in cache", grid_id); + } + self.editor_map.insert(grid_id.to_string(), editor.clone()); Ok(editor) } - Some(editor) => Ok(editor), + Some(editor) => Ok(editor.clone()), } } @@ -115,11 +119,10 @@ impl GridManager { &self, grid_id: &str, pool: Arc, - ) -> Result, FlowyError> { + ) -> Result, FlowyError> { let user = self.grid_user.clone(); let rev_manager = self.make_grid_rev_manager(grid_id, pool.clone())?; - let grid_editor = - ClientGridEditor::new(grid_id, user, rev_manager, self.block_index_persistence.clone()).await?; + let grid_editor = GridMetaEditor::new(grid_id, user, rev_manager, self.block_index_cache.clone()).await?; Ok(grid_editor) } @@ -145,31 +148,6 @@ impl GridManager { } } -pub struct GridEditorMap { - inner: DashMap>, -} - -impl GridEditorMap { - fn new() -> Self { - Self { inner: DashMap::new() } - } - - pub(crate) fn insert(&self, grid_id: &str, grid_editor: &Arc) { - if self.inner.contains_key(grid_id) { - tracing::warn!("Grid:{} already exists in cache", grid_id); - } - self.inner.insert(grid_id.to_string(), grid_editor.clone()); - } - - pub(crate) fn get(&self, grid_id: &str) -> Option> { - Some(self.inner.get(grid_id)?.clone()) - } - - pub(crate) fn remove(&self, grid_id: &str) { - self.inner.remove(grid_id); - } -} - pub async fn make_grid_view_data( user_id: &str, view_id: &str, @@ -192,9 +170,7 @@ pub async fn make_grid_view_data( // Indexing the block's rows build_context.block_meta_data.rows.iter().for_each(|row| { - let _ = grid_manager - .block_index_persistence - .insert_or_update(&row.block_id, &row.id); + let _ = grid_manager.block_index_cache.insert(&row.block_id, &row.id); }); // Create grid's block diff --git a/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs b/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs index ce7c5c8e8d..d1cb847b37 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs @@ -8,18 +8,17 @@ use flowy_sync::util::make_delta_from_revisions; use lib_infra::future::FutureResult; use lib_ot::core::PlainTextAttributes; use std::borrow::Cow; - use std::sync::Arc; use tokio::sync::RwLock; -pub struct ClientGridBlockMetaEditor { +pub struct GridBlockMetaEditor { user_id: String, pub block_id: String, pad: Arc>, rev_manager: Arc, } -impl ClientGridBlockMetaEditor { +impl GridBlockMetaEditor { pub async fn new( user_id: &str, token: &str, diff --git a/frontend/rust-lib/flowy-grid/src/services/block_meta_manager.rs b/frontend/rust-lib/flowy-grid/src/services/block_meta_manager.rs index 229afe75a2..6ce36488c2 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_meta_manager.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_meta_manager.rs @@ -1,7 +1,7 @@ use crate::dart_notification::{send_dart_notification, GridNotification}; use crate::manager::GridUser; -use crate::services::block_meta_editor::ClientGridBlockMetaEditor; -use crate::services::persistence::block_index::BlockIndexPersistence; +use crate::services::block_meta_editor::GridBlockMetaEditor; +use crate::services::persistence::block_index::BlockIndexCache; use crate::services::row::{group_row_orders, GridBlockSnapshot}; use dashmap::DashMap; use flowy_error::FlowyResult; @@ -15,20 +15,20 @@ use std::borrow::Cow; use std::collections::HashMap; use std::sync::Arc; -pub(crate) struct GridBlockMetaEditorManager { +type BlockId = String; +pub(crate) struct GridBlockManager { grid_id: String, user: Arc, - // Key: block_id - editor_map: DashMap>, - persistence: Arc, + persistence: Arc, + block_editor_map: DashMap>, } -impl GridBlockMetaEditorManager { +impl GridBlockManager { pub(crate) async fn new( grid_id: &str, user: &Arc, blocks: Vec, - persistence: Arc, + persistence: Arc, ) -> FlowyResult { let editor_map = make_block_meta_editor_map(user, blocks).await?; let user = user.clone(); @@ -36,27 +36,27 @@ impl GridBlockMetaEditorManager { let manager = Self { grid_id, user, - editor_map, + block_editor_map: editor_map, persistence, }; Ok(manager) } // #[tracing::instrument(level = "trace", skip(self))] - pub(crate) async fn get_editor(&self, block_id: &str) -> FlowyResult> { + pub(crate) async fn get_editor(&self, block_id: &str) -> FlowyResult> { debug_assert!(!block_id.is_empty()); - match self.editor_map.get(block_id) { + match self.block_editor_map.get(block_id) { None => { tracing::error!("The is a fatal error, block is not exist"); let editor = Arc::new(make_block_meta_editor(&self.user, block_id).await?); - self.editor_map.insert(block_id.to_owned(), editor.clone()); + self.block_editor_map.insert(block_id.to_owned(), editor.clone()); Ok(editor) } Some(editor) => Ok(editor.clone()), } } - async fn get_editor_from_row_id(&self, row_id: &str) -> FlowyResult> { + async fn get_editor_from_row_id(&self, row_id: &str) -> FlowyResult> { let block_id = self.persistence.get_block_id(row_id)?; Ok(self.get_editor(&block_id).await?) } @@ -67,7 +67,7 @@ impl GridBlockMetaEditorManager { row_meta: RowMeta, start_row_id: Option, ) -> FlowyResult { - let _ = self.persistence.insert_or_update(&row_meta.block_id, &row_meta.id)?; + let _ = self.persistence.insert(&row_meta.block_id, &row_meta.id)?; let editor = self.get_editor(&row_meta.block_id).await?; let mut index_row_order = IndexRowOrder::from(&row_meta); @@ -90,7 +90,7 @@ impl GridBlockMetaEditorManager { let editor = self.get_editor(&block_id).await?; let mut row_count = 0; for row in row_metas { - let _ = self.persistence.insert_or_update(&row.block_id, &row.id)?; + let _ = self.persistence.insert(&row.block_id, &row.id)?; let mut row_order = IndexRowOrder::from(&row); let (count, index) = editor.create_row(row, None).await?; row_count = count; @@ -256,7 +256,7 @@ impl GridBlockMetaEditorManager { async fn make_block_meta_editor_map( user: &Arc, blocks: Vec, -) -> FlowyResult>> { +) -> FlowyResult>> { let editor_map = DashMap::new(); for block in blocks { let editor = make_block_meta_editor(user, &block.block_id).await?; @@ -266,7 +266,7 @@ async fn make_block_meta_editor_map( Ok(editor_map) } -async fn make_block_meta_editor(user: &Arc, block_id: &str) -> FlowyResult { +async fn make_block_meta_editor(user: &Arc, block_id: &str) -> FlowyResult { let token = user.token()?; let user_id = user.user_id()?; let pool = user.db_pool()?; @@ -274,5 +274,5 @@ async fn make_block_meta_editor(user: &Arc, block_id: &str) -> Flo let disk_cache = Arc::new(SQLiteGridBlockMetaRevisionPersistence::new(&user_id, pool)); let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, block_id, disk_cache)); let rev_manager = RevisionManager::new(&user_id, block_id, rev_persistence); - ClientGridBlockMetaEditor::new(&user_id, &token, block_id, rev_manager).await + GridBlockMetaEditor::new(&user_id, &token, block_id, rev_manager).await } diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index c161e191c4..e4cee15a6e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -1,9 +1,9 @@ use crate::dart_notification::{send_dart_notification, GridNotification}; use crate::entities::CellIdentifier; use crate::manager::GridUser; -use crate::services::block_meta_manager::GridBlockMetaEditorManager; +use crate::services::block_meta_manager::GridBlockManager; 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::BlockIndexCache; use crate::services::row::*; use bytes::Bytes; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; @@ -19,20 +19,26 @@ use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; -pub struct ClientGridEditor { +pub struct GridMetaEditor { grid_id: String, user: Arc, grid_pad: Arc>, rev_manager: Arc, - block_meta_manager: Arc, + block_manager: Arc, } -impl ClientGridEditor { +impl Drop for GridMetaEditor { + fn drop(&mut self) { + tracing::trace!("Drop GridMetaEditor"); + } +} + +impl GridMetaEditor { pub async fn new( grid_id: &str, user: Arc, mut rev_manager: RevisionManager, - persistence: Arc, + persistence: Arc, ) -> FlowyResult> { let token = user.token()?; let cloud = Arc::new(GridRevisionCloudService { token }); @@ -41,13 +47,13 @@ impl ClientGridEditor { let grid_pad = Arc::new(RwLock::new(grid_pad)); let blocks = grid_pad.read().await.get_block_metas(); - let block_meta_manager = Arc::new(GridBlockMetaEditorManager::new(grid_id, &user, blocks, persistence).await?); + let block_meta_manager = Arc::new(GridBlockManager::new(grid_id, &user, blocks, persistence).await?); Ok(Arc::new(Self { grid_id: grid_id.to_owned(), user, grid_pad, rev_manager, - block_meta_manager, + block_manager: block_meta_manager, })) } @@ -254,10 +260,7 @@ impl ClientGridEditor { let row_order = RowOrder::from(&row_meta); // insert the row - let row_count = self - .block_meta_manager - .create_row(&block_id, row_meta, start_row_id) - .await?; + let row_count = self.block_manager.create_row(&block_id, row_meta, start_row_id).await?; // update block row count let changeset = GridBlockMetaChangeset::from_row_count(&block_id, row_count); @@ -277,7 +280,7 @@ impl ClientGridEditor { .or_insert_with(Vec::new) .push(row_meta); } - let changesets = self.block_meta_manager.insert_row(rows_by_block_id).await?; + let changesets = self.block_manager.insert_row(rows_by_block_id).await?; for changeset in changesets { let _ = self.update_block(changeset).await?; } @@ -286,7 +289,7 @@ impl ClientGridEditor { pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> { let field_metas = self.get_field_metas::(None).await?; - self.block_meta_manager + self.block_manager .update_row(changeset, |row_meta| make_row_from_row_meta(&field_metas, row_meta)) .await } @@ -309,7 +312,7 @@ impl ClientGridEditor { } pub async fn get_row(&self, row_id: &str) -> FlowyResult> { - match self.block_meta_manager.get_row_meta(row_id).await? { + match self.block_manager.get_row_meta(row_id).await? { None => Ok(None), Some(row_meta) => { let field_metas = self.get_field_metas::(None).await?; @@ -321,7 +324,7 @@ impl ClientGridEditor { } } pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> { - let _ = self.block_meta_manager.delete_row(row_id).await?; + let _ = self.block_manager.delete_row(row_id).await?; Ok(()) } @@ -331,12 +334,12 @@ impl ClientGridEditor { pub async fn get_cell(&self, params: &CellIdentifier) -> Option { let field_meta = self.get_field_meta(¶ms.field_id).await?; - let row_meta = self.block_meta_manager.get_row_meta(¶ms.row_id).await.ok()??; + let row_meta = self.block_manager.get_row_meta(¶ms.row_id).await.ok()??; make_cell(¶ms.field_id, &field_meta, &row_meta) } pub async fn get_cell_meta(&self, row_id: &str, field_id: &str) -> FlowyResult> { - let row_meta = self.block_meta_manager.get_row_meta(row_id).await?; + let row_meta = self.block_manager.get_row_meta(row_id).await?; match row_meta { None => Ok(None), Some(row_meta) => { @@ -382,7 +385,7 @@ impl ClientGridEditor { cell_content_changeset, }; let _ = self - .block_meta_manager + .block_manager .update_cell(cell_changeset, |row_meta| { make_row_from_row_meta(&field_metas, row_meta) }) @@ -403,7 +406,7 @@ impl ClientGridEditor { } pub async fn delete_rows(&self, row_orders: Vec) -> FlowyResult<()> { - let changesets = self.block_meta_manager.delete_rows(row_orders).await?; + let changesets = self.block_manager.delete_rows(row_orders).await?; for changeset in changesets { let _ = self.update_block(changeset).await?; } @@ -415,7 +418,7 @@ impl ClientGridEditor { let field_orders = pad_read_guard.get_field_orders(); let mut block_orders = vec![]; for block_order in pad_read_guard.get_block_metas() { - let row_orders = self.block_meta_manager.get_row_orders(&block_order.block_id).await?; + let row_orders = self.block_manager.get_row_orders(&block_order.block_id).await?; let block_order = GridBlockOrder { block_id: block_order.block_id, row_orders, @@ -442,7 +445,7 @@ impl ClientGridEditor { .collect::>(), Some(block_ids) => block_ids, }; - let snapshots = self.block_meta_manager.make_block_snapshots(block_ids).await?; + let snapshots = self.block_manager.make_block_snapshots(block_ids).await?; Ok(snapshots) } @@ -476,10 +479,7 @@ impl ClientGridEditor { } pub async fn move_row(&self, row_id: &str, from: i32, to: i32) -> FlowyResult<()> { - let _ = self - .block_meta_manager - .move_row(row_id, from as usize, to as usize) - .await?; + let _ = self.block_manager.move_row(row_id, from as usize, to as usize).await?; Ok(()) } @@ -565,7 +565,7 @@ impl ClientGridEditor { } #[cfg(feature = "flowy_unit_test")] -impl ClientGridEditor { +impl GridMetaEditor { pub fn rev_manager(&self) -> Arc { self.rev_manager.clone() } diff --git a/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs b/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs index df75ec629c..c62dc502ad 100644 --- a/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs +++ b/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs @@ -7,11 +7,11 @@ use flowy_database::{ use flowy_error::FlowyResult; use std::sync::Arc; -pub struct BlockIndexPersistence { +pub struct BlockIndexCache { database: Arc, } -impl BlockIndexPersistence { +impl BlockIndexCache { pub fn new(database: Arc) -> Self { Self { database } } @@ -26,7 +26,7 @@ impl BlockIndexPersistence { Ok(block_id) } - pub fn insert_or_update(&self, block_id: &str, row_id: &str) -> FlowyResult<()> { + pub fn insert(&self, block_id: &str, row_id: &str) -> FlowyResult<()> { let conn = self.database.db_connection()?; let item = IndexItem { row_id: row_id.to_string(), diff --git a/frontend/rust-lib/flowy-grid/tests/grid/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/script.rs index 611ee937e2..49b6ee7c90 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/script.rs @@ -1,6 +1,6 @@ use bytes::Bytes; use flowy_grid::services::field::*; -use flowy_grid::services::grid_editor::{ClientGridEditor, GridPadBuilder}; +use flowy_grid::services::grid_editor::{GridMetaEditor, GridPadBuilder}; use flowy_grid::services::row::CreateRowMetaPayload; use flowy_grid_data_model::entities::{ BuildGridContext, CellChangeset, Field, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, GridBlockMeta, @@ -72,7 +72,7 @@ pub enum EditorScript { pub struct GridEditorTest { pub sdk: FlowySDKTest, pub grid_id: String, - pub editor: Arc, + pub editor: Arc, pub field_metas: Vec, pub grid_blocks: Vec, pub row_metas: Vec>, @@ -239,7 +239,7 @@ impl GridEditorTest { } } -async fn get_row_metas(editor: &Arc) -> Vec> { +async fn get_row_metas(editor: &Arc) -> Vec> { editor .grid_block_snapshots(None) .await diff --git a/frontend/rust-lib/flowy-text-block/src/editor.rs b/frontend/rust-lib/flowy-text-block/src/editor.rs index d43fc24f1c..c1e50536a5 100644 --- a/frontend/rust-lib/flowy-text-block/src/editor.rs +++ b/frontend/rust-lib/flowy-text-block/src/editor.rs @@ -21,7 +21,7 @@ use lib_ws::WSConnectState; use std::sync::Arc; use tokio::sync::{mpsc, oneshot}; -pub struct ClientTextBlockEditor { +pub struct TextBlockEditor { pub doc_id: String, #[allow(dead_code)] rev_manager: Arc, @@ -30,7 +30,7 @@ pub struct ClientTextBlockEditor { edit_cmd_tx: EditorCommandSender, } -impl ClientTextBlockEditor { +impl TextBlockEditor { #[allow(unused_variables)] pub(crate) async fn new( doc_id: &str, @@ -185,7 +185,7 @@ impl ClientTextBlockEditor { pub(crate) fn receive_ws_state(&self, _state: &WSConnectState) {} } -impl std::ops::Drop for ClientTextBlockEditor { +impl std::ops::Drop for TextBlockEditor { fn drop(&mut self) { tracing::trace!("{} ClientBlockEditor was dropped", self.doc_id) } @@ -204,7 +204,7 @@ fn spawn_edit_queue( } #[cfg(feature = "flowy_unit_test")] -impl ClientTextBlockEditor { +impl TextBlockEditor { pub async fn text_block_delta(&self) -> FlowyResult { let (ret, rx) = oneshot::channel::>(); let msg = EditorCommand::ReadDelta { ret }; diff --git a/frontend/rust-lib/flowy-text-block/src/manager.rs b/frontend/rust-lib/flowy-text-block/src/manager.rs index 8a34c6916d..9325fe6001 100644 --- a/frontend/rust-lib/flowy-text-block/src/manager.rs +++ b/frontend/rust-lib/flowy-text-block/src/manager.rs @@ -1,4 +1,4 @@ -use crate::{editor::ClientTextBlockEditor, errors::FlowyError, BlockCloudService}; +use crate::{editor::TextBlockEditor, errors::FlowyError, BlockCloudService}; use bytes::Bytes; use dashmap::DashMap; use flowy_database::ConnectionPool; @@ -47,8 +47,8 @@ impl TextBlockManager { Ok(()) } - #[tracing::instrument(level = "debug", skip(self, block_id), fields(block_id), err)] - pub async fn open_block>(&self, block_id: T) -> Result, FlowyError> { + #[tracing::instrument(level = "trace", skip(self, block_id), fields(block_id), err)] + pub async fn open_block>(&self, block_id: T) -> Result, FlowyError> { let block_id = block_id.as_ref(); tracing::Span::current().record("block_id", &block_id); self.get_block_editor(block_id).await @@ -108,7 +108,7 @@ impl TextBlockManager { } impl TextBlockManager { - async fn get_block_editor(&self, block_id: &str) -> FlowyResult> { + async fn get_block_editor(&self, block_id: &str) -> FlowyResult> { match self.editor_map.get(block_id) { None => { let db_pool = self.user.db_pool()?; @@ -123,7 +123,7 @@ impl TextBlockManager { &self, block_id: &str, pool: Arc, - ) -> Result, FlowyError> { + ) -> Result, FlowyError> { let user = self.user.clone(); let token = self.user.token()?; let rev_manager = self.make_rev_manager(block_id, pool.clone())?; @@ -132,7 +132,7 @@ impl TextBlockManager { server: self.cloud_service.clone(), }); let doc_editor = - ClientTextBlockEditor::new(block_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?; + TextBlockEditor::new(block_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?; self.editor_map.insert(block_id, &doc_editor); Ok(doc_editor) } @@ -180,7 +180,7 @@ impl RevisionCloudService for TextBlockRevisionCloudService { } pub struct TextBlockEditorMap { - inner: DashMap>, + inner: DashMap>, } impl TextBlockEditorMap { @@ -188,14 +188,14 @@ impl TextBlockEditorMap { Self { inner: DashMap::new() } } - pub(crate) fn insert(&self, block_id: &str, doc: &Arc) { + pub(crate) fn insert(&self, block_id: &str, doc: &Arc) { if self.inner.contains_key(block_id) { log::warn!("Doc:{} already exists in cache", block_id); } self.inner.insert(block_id.to_string(), doc.clone()); } - pub(crate) fn get(&self, block_id: &str) -> Option> { + pub(crate) fn get(&self, block_id: &str) -> Option> { Some(self.inner.get(block_id)?.clone()) } diff --git a/frontend/rust-lib/flowy-text-block/tests/document/script.rs b/frontend/rust-lib/flowy-text-block/tests/document/script.rs index 5511896fc2..1722724c3f 100644 --- a/frontend/rust-lib/flowy-text-block/tests/document/script.rs +++ b/frontend/rust-lib/flowy-text-block/tests/document/script.rs @@ -1,6 +1,6 @@ use flowy_revision::disk::RevisionState; use flowy_test::{helper::ViewTest, FlowySDKTest}; -use flowy_text_block::editor::ClientTextBlockEditor; +use flowy_text_block::editor::TextBlockEditor; use flowy_text_block::TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS; use lib_ot::{core::Interval, rich_text::RichTextDelta}; use std::sync::Arc; @@ -19,7 +19,7 @@ pub enum EditorScript { pub struct TextBlockEditorTest { pub sdk: FlowySDKTest, - pub editor: Arc, + pub editor: Arc, } impl TextBlockEditorTest { diff --git a/shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs index 3f727faf59..3370719e68 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs @@ -175,7 +175,7 @@ impl GridBlockMetaPad { match cal_diff::(old, new) { None => Ok(None), Some(delta) => { - tracing::debug!("[GridBlockMeta] Composing delta {}", delta.to_delta_str()); + tracing::trace!("[GridBlockMeta] Composing delta {}", delta.to_delta_str()); // tracing::debug!( // "[GridBlockMeta] current delta: {}", // self.delta.to_str().unwrap_or_else(|_| "".to_string()) From 9a93a72c33d96d327597b6c13aa5ddc46fdeff34 Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 27 May 2022 19:03:48 +0800 Subject: [PATCH 14/23] feat: add new field type --- .../assets/images/grid/field/url.svg | 3 + .../app_flowy/assets/translations/en.json | 1 + .../grid/cell/cell_service/cell_service.dart | 1 - .../cell/cell_service/context_builder.dart | 14 +- .../application/grid/cell/url_cell_bloc.dart | 72 ++++ .../grid/src/widgets/cell/cell_builder.dart | 8 +- .../grid/src/widgets/cell/url_cell.dart | 127 ++++++ .../widgets/header/field_editor_pannel.dart | 18 +- .../widgets/header/field_type_extension.dart | 10 +- .../src/widgets/header/type_option/url.dart | 20 + .../grid/src/widgets/row/row_detail.dart | 9 +- .../flowy_sdk/lib/dispatch/dispatch.dart | 1 + .../flowy-grid-data-model/grid.pbenum.dart | 2 + .../flowy-grid-data-model/grid.pbjson.dart | 3 +- .../lib/protobuf/flowy-grid/protobuf.dart | 1 + .../flowy-grid/text_type_option.pb.dart | 16 +- .../flowy-grid/text_type_option.pbjson.dart | 4 +- .../flowy-grid/url_type_option.pb.dart | 119 ++++++ .../flowy-grid/url_type_option.pbenum.dart | 7 + .../flowy-grid/url_type_option.pbjson.dart | 31 ++ .../flowy-grid/url_type_option.pbserver.dart | 9 + frontend/rust-lib/Cargo.lock | 1 + frontend/rust-lib/flowy-grid/Cargo.toml | 1 + .../rust-lib/flowy-grid/src/event_handler.rs | 6 +- .../flowy-grid/src/protobuf/model/mod.rs | 3 + .../src/protobuf/model/text_type_option.rs | 46 +- .../src/protobuf/model/url_type_option.rs | 403 ++++++++++++++++++ .../src/protobuf/proto/text_type_option.proto | 2 +- .../src/protobuf/proto/url_type_option.proto | 9 + .../src/services/field/field_builder.rs | 3 + .../src/services/field/type_options/mod.rs | 2 + .../field/type_options/number_type_option.rs | 6 +- .../field/type_options/text_type_option.rs | 2 +- .../field/type_options/url_type_option.rs | 101 +++++ .../src/services/row/cell_data_operation.rs | 4 + .../src/entities/grid.rs | 5 + .../src/protobuf/model/grid.rs | 7 +- .../src/protobuf/proto/grid.proto | 1 + 38 files changed, 1016 insertions(+), 62 deletions(-) create mode 100644 frontend/app_flowy/assets/images/grid/field/url.svg create mode 100644 frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart create mode 100644 frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell.dart create mode 100644 frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart create mode 100644 frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pb.dart create mode 100644 frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbenum.dart create mode 100644 frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbjson.dart create mode 100644 frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbserver.dart create mode 100644 frontend/rust-lib/flowy-grid/src/protobuf/model/url_type_option.rs create mode 100644 frontend/rust-lib/flowy-grid/src/protobuf/proto/url_type_option.proto create mode 100644 frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs diff --git a/frontend/app_flowy/assets/images/grid/field/url.svg b/frontend/app_flowy/assets/images/grid/field/url.svg new file mode 100644 index 0000000000..f00f5c7aa2 --- /dev/null +++ b/frontend/app_flowy/assets/images/grid/field/url.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index 8fc9e90581..4e6c8f3420 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -160,6 +160,7 @@ "numberFieldName": "Numbers", "singleSelectFieldName": "Select", "multiSelectFieldName": "Multiselect", + "urlFieldName": "URL", "numberFormat": " Number format", "dateFormat": " Date format", "includeTime": " Include time", diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart index 07be14efa2..23b68f2f87 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart @@ -12,7 +12,6 @@ import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; - import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart index ed191c7d60..8680c7db76 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart @@ -3,6 +3,7 @@ part of 'cell_service.dart'; typedef GridCellContext = _GridCellContext; typedef GridSelectOptionCellContext = _GridCellContext; typedef GridDateCellContext = _GridCellContext; +typedef GridURLCellContext = _GridCellContext; class GridCellContextBuilder { final GridCellCache _cellCache; @@ -58,12 +59,21 @@ class GridCellContextBuilder { cellDataLoader: SelectOptionCellDataLoader(gridCell: _gridCell), cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); - default: - throw UnimplementedError; + + case FieldType.URL: + return GridURLCellContext( + gridCell: _gridCell, + cellCache: _cellCache, + cellDataLoader: GridCellDataLoader(gridCell: _gridCell), + cellDataPersistence: CellDataPersistence(gridCell: _gridCell), + ); } + throw UnimplementedError; } } +// T: the type of the CellData +// D: the type of the data that will be save to disk // ignore: must_be_immutable class _GridCellContext extends Equatable { final GridCell gridCell; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart new file mode 100644 index 0000000000..8b4245540a --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart @@ -0,0 +1,72 @@ +import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'dart:async'; +import 'cell_service/cell_service.dart'; + +part 'url_cell_bloc.freezed.dart'; + +class URLCellBloc extends Bloc { + final GridURLCellContext cellContext; + void Function()? _onCellChangedFn; + URLCellBloc({ + required this.cellContext, + }) : super(URLCellState.initial(cellContext)) { + on( + (event, emit) async { + event.when( + initial: () { + _startListening(); + }, + updateText: (text) { + cellContext.saveCellData(text); + emit(state.copyWith(content: text)); + }, + didReceiveCellUpdate: (cellData) { + emit(state.copyWith(content: cellData.content)); + }, + ); + }, + ); + } + + @override + Future close() async { + if (_onCellChangedFn != null) { + cellContext.removeListener(_onCellChangedFn!); + _onCellChangedFn = null; + } + cellContext.dispose(); + return super.close(); + } + + void _startListening() { + _onCellChangedFn = cellContext.startListening( + onCellChanged: ((cellData) { + if (!isClosed) { + add(URLCellEvent.didReceiveCellUpdate(cellData)); + } + }), + ); + } +} + +@freezed +class URLCellEvent with _$URLCellEvent { + const factory URLCellEvent.initial() = _InitialCell; + const factory URLCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate; + const factory URLCellEvent.updateText(String text) = _UpdateText; +} + +@freezed +class URLCellState with _$URLCellState { + const factory URLCellState({ + required String content, + required String url, + }) = _URLCellState; + + factory URLCellState.initial(GridURLCellContext context) { + final cellData = context.getCellData(); + return URLCellState(content: cellData?.content ?? "", url: ""); + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart index 6f74d23ad0..8ae79541a3 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart @@ -13,6 +13,7 @@ import 'date_cell/date_cell.dart'; import 'number_cell.dart'; import 'select_option_cell/select_option_cell.dart'; import 'text_cell.dart'; +import 'url_cell.dart'; GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) { final key = ValueKey(gridCell.cellId()); @@ -32,10 +33,11 @@ GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, { return NumberCell(cellContextBuilder: cellContextBuilder, key: key); case FieldType.RichText: return GridTextCell(cellContextBuilder: cellContextBuilder, style: style, key: key); - - default: - throw UnimplementedError; + case FieldType.URL: + return GridURLCell(cellContextBuilder: cellContextBuilder, style: style, key: key); + } + throw UnimplementedError; } class BlankCell extends StatelessWidget { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell.dart new file mode 100644 index 0000000000..328d0e7180 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell.dart @@ -0,0 +1,127 @@ +import 'dart:async'; +import 'package:app_flowy/workspace/application/grid/cell/url_cell_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:app_flowy/workspace/application/grid/prelude.dart'; +import 'cell_builder.dart'; + +class GridURLCellStyle extends GridCellStyle { + String? placeholder; + + GridURLCellStyle({ + this.placeholder, + }); +} + +class GridURLCell extends StatefulWidget with GridCellWidget { + final GridCellContextBuilder cellContextBuilder; + late final GridURLCellStyle? cellStyle; + GridURLCell({ + required this.cellContextBuilder, + GridCellStyle? style, + Key? key, + }) : super(key: key) { + if (style != null) { + cellStyle = (style as GridURLCellStyle); + } else { + cellStyle = null; + } + } + + @override + State createState() => _GridURLCellState(); +} + +class _GridURLCellState extends State { + late URLCellBloc _cellBloc; + late TextEditingController _controller; + late CellSingleFocusNode _focusNode; + Timer? _delayOperation; + + @override + void initState() { + final cellContext = widget.cellContextBuilder.build() as GridURLCellContext; + _cellBloc = URLCellBloc(cellContext: cellContext); + _cellBloc.add(const URLCellEvent.initial()); + _controller = TextEditingController(text: _cellBloc.state.content); + _focusNode = CellSingleFocusNode(); + + _listenFocusNode(); + _listenRequestFocus(context); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: _cellBloc, + child: BlocListener( + listener: (context, state) { + if (_controller.text != state.content) { + _controller.text = state.content; + } + }, + child: TextField( + controller: _controller, + focusNode: _focusNode, + onChanged: (value) => focusChanged(), + onEditingComplete: () => _focusNode.unfocus(), + maxLines: null, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + decoration: InputDecoration( + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + hintText: widget.cellStyle?.placeholder, + isDense: true, + ), + ), + ), + ); + } + + @override + Future dispose() async { + widget.requestFocus.removeAllListener(); + _delayOperation?.cancel(); + _cellBloc.close(); + _focusNode.removeSingleListener(); + _focusNode.dispose(); + + super.dispose(); + } + + @override + void didUpdateWidget(covariant GridURLCell oldWidget) { + if (oldWidget != widget) { + _listenFocusNode(); + } + super.didUpdateWidget(oldWidget); + } + + void _listenFocusNode() { + widget.onFocus.value = _focusNode.hasFocus; + _focusNode.setSingleListener(() { + widget.onFocus.value = _focusNode.hasFocus; + focusChanged(); + }); + } + + void _listenRequestFocus(BuildContext context) { + widget.requestFocus.addListener(() { + if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) { + FocusScope.of(context).requestFocus(_focusNode); + } + }); + } + + Future focusChanged() async { + if (mounted) { + _delayOperation?.cancel(); + _delayOperation = Timer(const Duration(milliseconds: 300), () { + if (_cellBloc.isClosed == false && _controller.text != _cellBloc.state.content) { + _cellBloc.add(URLCellEvent.updateText(_controller.text)); + } + }); + } + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart index eb42267445..63b790b02c 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart @@ -22,6 +22,7 @@ import 'type_option/multi_select.dart'; import 'type_option/number.dart'; import 'type_option/rich_text.dart'; import 'type_option/single_select.dart'; +import 'type_option/url.dart'; typedef UpdateFieldCallback = void Function(Field, Uint8List); typedef SwitchToFieldCallback = Future> Function( @@ -168,9 +169,12 @@ TypeOptionBuilder _makeTypeOptionBuild({ typeOptionContext as RichTextTypeOptionContext, ); - default: - throw UnimplementedError; + case FieldType.URL: + return URLTypeOptionBuilder( + typeOptionContext as URLTypeOptionContext, + ); } + throw UnimplementedError; } TypeOptionContext _makeTypeOptionContext(GridFieldContext fieldContext) { @@ -205,9 +209,15 @@ TypeOptionContext _makeTypeOptionContext(GridFieldContext fieldContext) { fieldContext: fieldContext, dataBuilder: SingleSelectTypeOptionDataBuilder(), ); - default: - throw UnimplementedError(); + + case FieldType.URL: + return URLTypeOptionContext( + fieldContext: fieldContext, + dataBuilder: URLTypeOptionDataBuilder(), + ); } + + throw UnimplementedError(); } abstract class TypeOptionWidget extends StatelessWidget { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart index a4da8fa1b9..035d101544 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart @@ -17,9 +17,10 @@ extension FieldTypeListExtension on FieldType { return "grid/field/text"; case FieldType.SingleSelect: return "grid/field/single_select"; - default: - throw UnimplementedError; + case FieldType.URL: + return "grid/field/url"; } + throw UnimplementedError; } String title() { @@ -36,8 +37,9 @@ extension FieldTypeListExtension on FieldType { return LocaleKeys.grid_field_textFieldName.tr(); case FieldType.SingleSelect: return LocaleKeys.grid_field_singleSelectFieldName.tr(); - default: - throw UnimplementedError; + case FieldType.URL: + return LocaleKeys.grid_field_urlFieldName.tr(); } + throw UnimplementedError; } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart new file mode 100644 index 0000000000..f4e73f7fdc --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart @@ -0,0 +1,20 @@ +import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; +import 'package:flutter/material.dart'; + +typedef URLTypeOptionContext = TypeOptionContext; + +class URLTypeOptionDataBuilder extends TypeOptionDataBuilder { + @override + URLTypeOption fromBuffer(List buffer) { + return URLTypeOption.fromBuffer(buffer); + } +} + +class URLTypeOptionBuilder extends TypeOptionBuilder { + URLTypeOptionBuilder(URLTypeOptionContext typeOptionContext); + + @override + Widget? get customWidget => null; +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart index f2b93e5018..255553f3d2 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart @@ -4,6 +4,7 @@ import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart'; import 'package:app_flowy/workspace/application/grid/row/row_service.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/url_cell.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart'; import 'package:flowy_infra/image.dart'; @@ -212,7 +213,11 @@ GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) { return SelectOptionCellStyle( placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), ); - default: - return null; + + case FieldType.URL: + return GridURLCellStyle( + placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), + ); } + return null; } diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart index ca3596e48a..35457ff989 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart @@ -9,6 +9,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-net/event.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-net/network_state.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user/event_map.pb.dart'; diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbenum.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbenum.dart index e6cb17314b..78331a46e5 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbenum.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbenum.dart @@ -31,6 +31,7 @@ class FieldType extends $pb.ProtobufEnum { static const FieldType SingleSelect = FieldType._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SingleSelect'); static const FieldType MultiSelect = FieldType._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MultiSelect'); static const FieldType Checkbox = FieldType._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Checkbox'); + static const FieldType URL = FieldType._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'URL'); static const $core.List values = [ RichText, @@ -39,6 +40,7 @@ class FieldType extends $pb.ProtobufEnum { SingleSelect, MultiSelect, Checkbox, + URL, ]; static final $core.Map<$core.int, FieldType> _byValue = $pb.ProtobufEnum.initByValue(values); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart index 3a925cb282..c4f25d7fe3 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart @@ -29,11 +29,12 @@ const FieldType$json = const { const {'1': 'SingleSelect', '2': 3}, const {'1': 'MultiSelect', '2': 4}, const {'1': 'Checkbox', '2': 5}, + const {'1': 'URL', '2': 6}, ], }; /// Descriptor for `FieldType`. Decode as a `google.protobuf.EnumDescriptorProto`. -final $typed_data.Uint8List fieldTypeDescriptor = $convert.base64Decode('CglGaWVsZFR5cGUSDAoIUmljaFRleHQQABIKCgZOdW1iZXIQARIMCghEYXRlVGltZRACEhAKDFNpbmdsZVNlbGVjdBADEg8KC011bHRpU2VsZWN0EAQSDAoIQ2hlY2tib3gQBQ=='); +final $typed_data.Uint8List fieldTypeDescriptor = $convert.base64Decode('CglGaWVsZFR5cGUSDAoIUmljaFRleHQQABIKCgZOdW1iZXIQARIMCghEYXRlVGltZRACEhAKDFNpbmdsZVNlbGVjdBADEg8KC011bHRpU2VsZWN0EAQSDAoIQ2hlY2tib3gQBRIHCgNVUkwQBg=='); @$core.Deprecated('Use gridDescriptor instead') const Grid$json = const { '1': 'Grid', diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/protobuf.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/protobuf.dart index af6583c106..c056e2799a 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/protobuf.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/protobuf.dart @@ -5,6 +5,7 @@ export './dart_notification.pb.dart'; export './selection_type_option.pb.dart'; export './row_entities.pb.dart'; export './cell_entities.pb.dart'; +export './url_type_option.pb.dart'; export './checkbox_type_option.pb.dart'; export './event_map.pb.dart'; export './text_type_option.pb.dart'; diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pb.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pb.dart index c30f2eb6e1..a38a68be36 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pb.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pb.dart @@ -11,17 +11,17 @@ import 'package:protobuf/protobuf.dart' as $pb; class RichTextTypeOption extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RichTextTypeOption', createEmptyInstance: create) - ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'format') + ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data') ..hasRequiredFields = false ; RichTextTypeOption._() : super(); factory RichTextTypeOption({ - $core.String? format, + $core.String? data, }) { final _result = create(); - if (format != null) { - _result.format = format; + if (data != null) { + _result.data = data; } return _result; } @@ -47,12 +47,12 @@ class RichTextTypeOption extends $pb.GeneratedMessage { static RichTextTypeOption? _defaultInstance; @$pb.TagNumber(1) - $core.String get format => $_getSZ(0); + $core.String get data => $_getSZ(0); @$pb.TagNumber(1) - set format($core.String v) { $_setString(0, v); } + set data($core.String v) { $_setString(0, v); } @$pb.TagNumber(1) - $core.bool hasFormat() => $_has(0); + $core.bool hasData() => $_has(0); @$pb.TagNumber(1) - void clearFormat() => clearField(1); + void clearData() => clearField(1); } diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pbjson.dart index e4ba6956ee..5999ce87e0 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pbjson.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pbjson.dart @@ -12,9 +12,9 @@ import 'dart:typed_data' as $typed_data; const RichTextTypeOption$json = const { '1': 'RichTextTypeOption', '2': const [ - const {'1': 'format', '3': 1, '4': 1, '5': 9, '10': 'format'}, + const {'1': 'data', '3': 1, '4': 1, '5': 9, '10': 'data'}, ], }; /// Descriptor for `RichTextTypeOption`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List richTextTypeOptionDescriptor = $convert.base64Decode('ChJSaWNoVGV4dFR5cGVPcHRpb24SFgoGZm9ybWF0GAEgASgJUgZmb3JtYXQ='); +final $typed_data.Uint8List richTextTypeOptionDescriptor = $convert.base64Decode('ChJSaWNoVGV4dFR5cGVPcHRpb24SEgoEZGF0YRgBIAEoCVIEZGF0YQ=='); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pb.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pb.dart new file mode 100644 index 0000000000..c43474a92a --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pb.dart @@ -0,0 +1,119 @@ +/// +// Generated code. Do not modify. +// source: url_type_option.proto +// +// @dart = 2.12 +// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +class URLTypeOption extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'URLTypeOption', createEmptyInstance: create) + ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data') + ..hasRequiredFields = false + ; + + URLTypeOption._() : super(); + factory URLTypeOption({ + $core.String? data, + }) { + final _result = create(); + if (data != null) { + _result.data = data; + } + return _result; + } + factory URLTypeOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory URLTypeOption.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + URLTypeOption clone() => URLTypeOption()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + URLTypeOption copyWith(void Function(URLTypeOption) updates) => super.copyWith((message) => updates(message as URLTypeOption)) as URLTypeOption; // ignore: deprecated_member_use + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static URLTypeOption create() => URLTypeOption._(); + URLTypeOption createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static URLTypeOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static URLTypeOption? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get data => $_getSZ(0); + @$pb.TagNumber(1) + set data($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasData() => $_has(0); + @$pb.TagNumber(1) + void clearData() => clearField(1); +} + +class URLCellData extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'URLCellData', createEmptyInstance: create) + ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'url') + ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'content') + ..hasRequiredFields = false + ; + + URLCellData._() : super(); + factory URLCellData({ + $core.String? url, + $core.String? content, + }) { + final _result = create(); + if (url != null) { + _result.url = url; + } + if (content != null) { + _result.content = content; + } + return _result; + } + factory URLCellData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory URLCellData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + URLCellData clone() => URLCellData()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + URLCellData copyWith(void Function(URLCellData) updates) => super.copyWith((message) => updates(message as URLCellData)) as URLCellData; // ignore: deprecated_member_use + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static URLCellData create() => URLCellData._(); + URLCellData createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static URLCellData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static URLCellData? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get url => $_getSZ(0); + @$pb.TagNumber(1) + set url($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasUrl() => $_has(0); + @$pb.TagNumber(1) + void clearUrl() => clearField(1); + + @$pb.TagNumber(2) + $core.String get content => $_getSZ(1); + @$pb.TagNumber(2) + set content($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasContent() => $_has(1); + @$pb.TagNumber(2) + void clearContent() => clearField(2); +} + diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbenum.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbenum.dart new file mode 100644 index 0000000000..de8793d432 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbenum.dart @@ -0,0 +1,7 @@ +/// +// Generated code. Do not modify. +// source: url_type_option.proto +// +// @dart = 2.12 +// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields + diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbjson.dart new file mode 100644 index 0000000000..30ac81dfb2 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbjson.dart @@ -0,0 +1,31 @@ +/// +// Generated code. Do not modify. +// source: url_type_option.proto +// +// @dart = 2.12 +// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package + +import 'dart:core' as $core; +import 'dart:convert' as $convert; +import 'dart:typed_data' as $typed_data; +@$core.Deprecated('Use uRLTypeOptionDescriptor instead') +const URLTypeOption$json = const { + '1': 'URLTypeOption', + '2': const [ + const {'1': 'data', '3': 1, '4': 1, '5': 9, '10': 'data'}, + ], +}; + +/// Descriptor for `URLTypeOption`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List uRLTypeOptionDescriptor = $convert.base64Decode('Cg1VUkxUeXBlT3B0aW9uEhIKBGRhdGEYASABKAlSBGRhdGE='); +@$core.Deprecated('Use uRLCellDataDescriptor instead') +const URLCellData$json = const { + '1': 'URLCellData', + '2': const [ + const {'1': 'url', '3': 1, '4': 1, '5': 9, '10': 'url'}, + const {'1': 'content', '3': 2, '4': 1, '5': 9, '10': 'content'}, + ], +}; + +/// Descriptor for `URLCellData`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List uRLCellDataDescriptor = $convert.base64Decode('CgtVUkxDZWxsRGF0YRIQCgN1cmwYASABKAlSA3VybBIYCgdjb250ZW50GAIgASgJUgdjb250ZW50'); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbserver.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbserver.dart new file mode 100644 index 0000000000..6889e31393 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbserver.dart @@ -0,0 +1,9 @@ +/// +// Generated code. Do not modify. +// source: url_type_option.proto +// +// @dart = 2.12 +// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package + +export 'url_type_option.pb.dart'; + diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 554def4acd..23fe8ed62b 100755 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -953,6 +953,7 @@ dependencies = [ "strum_macros", "tokio", "tracing", + "url", ] [[package]] diff --git a/frontend/rust-lib/flowy-grid/Cargo.toml b/frontend/rust-lib/flowy-grid/Cargo.toml index 1691b3eee7..2bde4a3f36 100644 --- a/frontend/rust-lib/flowy-grid/Cargo.toml +++ b/frontend/rust-lib/flowy-grid/Cargo.toml @@ -35,6 +35,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = {version = "1.0"} serde_repr = "0.1" indexmap = {version = "1.8.1", features = ["serde"]} +url = { version = "2"} [dev-dependencies] flowy-test = { path = "../flowy-test" } diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index 44123a45d9..eda9840747 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -263,7 +263,7 @@ pub(crate) async fn update_cell_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_date_cell_data_handler( data: Data, manager: AppData>, @@ -272,7 +272,7 @@ pub(crate) async fn get_date_cell_data_handler( let editor = manager.get_grid_editor(¶ms.grid_id)?; match editor.get_field_meta(¶ms.field_id).await { None => { - tracing::error!("Can't find the corresponding field with id: {}", params.field_id); + tracing::error!("Can't find the date field with id: {}", params.field_id); data_result(DateCellData::default()) } Some(field_meta) => { @@ -350,7 +350,7 @@ pub(crate) async fn get_select_option_handler( let editor = manager.get_grid_editor(¶ms.grid_id)?; match editor.get_field_meta(¶ms.field_id).await { None => { - tracing::error!("Can't find the corresponding field with id: {}", params.field_id); + tracing::error!("Can't find the select option field with id: {}", params.field_id); data_result(SelectOptionCellData::default()) } Some(field_meta) => { diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs index 99d0ecd1b6..c0f74e1e9c 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs @@ -19,6 +19,9 @@ pub use row_entities::*; mod cell_entities; pub use cell_entities::*; +mod url_type_option; +pub use url_type_option::*; + mod checkbox_type_option; pub use checkbox_type_option::*; diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/model/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/text_type_option.rs index febc180e03..b6bb5e55ab 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/model/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/text_type_option.rs @@ -26,7 +26,7 @@ #[derive(PartialEq,Clone,Default)] pub struct RichTextTypeOption { // message fields - pub format: ::std::string::String, + pub data: ::std::string::String, // special fields pub unknown_fields: ::protobuf::UnknownFields, pub cached_size: ::protobuf::CachedSize, @@ -43,30 +43,30 @@ impl RichTextTypeOption { ::std::default::Default::default() } - // string format = 1; + // string data = 1; - pub fn get_format(&self) -> &str { - &self.format + pub fn get_data(&self) -> &str { + &self.data } - pub fn clear_format(&mut self) { - self.format.clear(); + pub fn clear_data(&mut self) { + self.data.clear(); } // Param is passed by value, moved - pub fn set_format(&mut self, v: ::std::string::String) { - self.format = v; + pub fn set_data(&mut self, v: ::std::string::String) { + self.data = v; } // Mutable pointer to the field. // If field is not initialized, it is initialized with default value first. - pub fn mut_format(&mut self) -> &mut ::std::string::String { - &mut self.format + pub fn mut_data(&mut self) -> &mut ::std::string::String { + &mut self.data } // Take field - pub fn take_format(&mut self) -> ::std::string::String { - ::std::mem::replace(&mut self.format, ::std::string::String::new()) + pub fn take_data(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.data, ::std::string::String::new()) } } @@ -80,7 +80,7 @@ impl ::protobuf::Message for RichTextTypeOption { let (field_number, wire_type) = is.read_tag_unpack()?; match field_number { 1 => { - ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.format)?; + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?; }, _ => { ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; @@ -94,8 +94,8 @@ impl ::protobuf::Message for RichTextTypeOption { #[allow(unused_variables)] fn compute_size(&self) -> u32 { let mut my_size = 0; - if !self.format.is_empty() { - my_size += ::protobuf::rt::string_size(1, &self.format); + if !self.data.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.data); } my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); self.cached_size.set(my_size); @@ -103,8 +103,8 @@ impl ::protobuf::Message for RichTextTypeOption { } fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { - if !self.format.is_empty() { - os.write_string(1, &self.format)?; + if !self.data.is_empty() { + os.write_string(1, &self.data)?; } os.write_unknown_fields(self.get_unknown_fields())?; ::std::result::Result::Ok(()) @@ -145,9 +145,9 @@ impl ::protobuf::Message for RichTextTypeOption { descriptor.get(|| { let mut fields = ::std::vec::Vec::new(); fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( - "format", - |m: &RichTextTypeOption| { &m.format }, - |m: &mut RichTextTypeOption| { &mut m.format }, + "data", + |m: &RichTextTypeOption| { &m.data }, + |m: &mut RichTextTypeOption| { &mut m.data }, )); ::protobuf::reflect::MessageDescriptor::new_pb_name::( "RichTextTypeOption", @@ -165,7 +165,7 @@ impl ::protobuf::Message for RichTextTypeOption { impl ::protobuf::Clear for RichTextTypeOption { fn clear(&mut self) { - self.format.clear(); + self.data.clear(); self.unknown_fields.clear(); } } @@ -183,8 +183,8 @@ impl ::protobuf::reflect::ProtobufValue for RichTextTypeOption { } static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x16text_type_option.proto\",\n\x12RichTextTypeOption\x12\x16\n\x06for\ - mat\x18\x01\x20\x01(\tR\x06formatb\x06proto3\ + \n\x16text_type_option.proto\"(\n\x12RichTextTypeOption\x12\x12\n\x04dat\ + a\x18\x01\x20\x01(\tR\x04datab\x06proto3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/model/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/url_type_option.rs new file mode 100644 index 0000000000..fe83999fd3 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/url_type_option.rs @@ -0,0 +1,403 @@ +// This file is generated by rust-protobuf 2.25.2. Do not edit +// @generated + +// https://github.com/rust-lang/rust-clippy/issues/702 +#![allow(unknown_lints)] +#![allow(clippy::all)] + +#![allow(unused_attributes)] +#![cfg_attr(rustfmt, rustfmt::skip)] + +#![allow(box_pointers)] +#![allow(dead_code)] +#![allow(missing_docs)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(trivial_casts)] +#![allow(unused_imports)] +#![allow(unused_results)] +//! Generated file from `url_type_option.proto` + +/// Generated files are compatible only with the same version +/// of protobuf runtime. +// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2; + +#[derive(PartialEq,Clone,Default)] +pub struct URLTypeOption { + // message fields + pub data: ::std::string::String, + // special fields + pub unknown_fields: ::protobuf::UnknownFields, + pub cached_size: ::protobuf::CachedSize, +} + +impl<'a> ::std::default::Default for &'a URLTypeOption { + fn default() -> &'a URLTypeOption { + ::default_instance() + } +} + +impl URLTypeOption { + pub fn new() -> URLTypeOption { + ::std::default::Default::default() + } + + // string data = 1; + + + pub fn get_data(&self) -> &str { + &self.data + } + pub fn clear_data(&mut self) { + self.data.clear(); + } + + // Param is passed by value, moved + pub fn set_data(&mut self, v: ::std::string::String) { + self.data = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_data(&mut self) -> &mut ::std::string::String { + &mut self.data + } + + // Take field + pub fn take_data(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.data, ::std::string::String::new()) + } +} + +impl ::protobuf::Message for URLTypeOption { + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> { + while !is.eof()? { + let (field_number, wire_type) = is.read_tag_unpack()?; + match field_number { + 1 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?; + }, + _ => { + ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u32 { + let mut my_size = 0; + if !self.data.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.data); + } + my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); + self.cached_size.set(my_size); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { + if !self.data.is_empty() { + os.write_string(1, &self.data)?; + } + os.write_unknown_fields(self.get_unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn get_cached_size(&self) -> u32 { + self.cached_size.get() + } + + fn get_unknown_fields(&self) -> &::protobuf::UnknownFields { + &self.unknown_fields + } + + fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields { + &mut self.unknown_fields + } + + fn as_any(&self) -> &dyn (::std::any::Any) { + self as &dyn (::std::any::Any) + } + fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) { + self as &mut dyn (::std::any::Any) + } + fn into_any(self: ::std::boxed::Box) -> ::std::boxed::Box { + self + } + + fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor { + Self::descriptor_static() + } + + fn new() -> URLTypeOption { + URLTypeOption::new() + } + + fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT; + descriptor.get(|| { + let mut fields = ::std::vec::Vec::new(); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "data", + |m: &URLTypeOption| { &m.data }, + |m: &mut URLTypeOption| { &mut m.data }, + )); + ::protobuf::reflect::MessageDescriptor::new_pb_name::( + "URLTypeOption", + fields, + file_descriptor_proto() + ) + }) + } + + fn default_instance() -> &'static URLTypeOption { + static instance: ::protobuf::rt::LazyV2 = ::protobuf::rt::LazyV2::INIT; + instance.get(URLTypeOption::new) + } +} + +impl ::protobuf::Clear for URLTypeOption { + fn clear(&mut self) { + self.data.clear(); + self.unknown_fields.clear(); + } +} + +impl ::std::fmt::Debug for URLTypeOption { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for URLTypeOption { + fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { + ::protobuf::reflect::ReflectValueRef::Message(self) + } +} + +#[derive(PartialEq,Clone,Default)] +pub struct URLCellData { + // message fields + pub url: ::std::string::String, + pub content: ::std::string::String, + // special fields + pub unknown_fields: ::protobuf::UnknownFields, + pub cached_size: ::protobuf::CachedSize, +} + +impl<'a> ::std::default::Default for &'a URLCellData { + fn default() -> &'a URLCellData { + ::default_instance() + } +} + +impl URLCellData { + pub fn new() -> URLCellData { + ::std::default::Default::default() + } + + // string url = 1; + + + pub fn get_url(&self) -> &str { + &self.url + } + pub fn clear_url(&mut self) { + self.url.clear(); + } + + // Param is passed by value, moved + pub fn set_url(&mut self, v: ::std::string::String) { + self.url = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_url(&mut self) -> &mut ::std::string::String { + &mut self.url + } + + // Take field + pub fn take_url(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.url, ::std::string::String::new()) + } + + // string content = 2; + + + pub fn get_content(&self) -> &str { + &self.content + } + pub fn clear_content(&mut self) { + self.content.clear(); + } + + // Param is passed by value, moved + pub fn set_content(&mut self, v: ::std::string::String) { + self.content = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_content(&mut self) -> &mut ::std::string::String { + &mut self.content + } + + // Take field + pub fn take_content(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.content, ::std::string::String::new()) + } +} + +impl ::protobuf::Message for URLCellData { + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> { + while !is.eof()? { + let (field_number, wire_type) = is.read_tag_unpack()?; + match field_number { + 1 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.url)?; + }, + 2 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.content)?; + }, + _ => { + ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u32 { + let mut my_size = 0; + if !self.url.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.url); + } + if !self.content.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.content); + } + my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); + self.cached_size.set(my_size); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { + if !self.url.is_empty() { + os.write_string(1, &self.url)?; + } + if !self.content.is_empty() { + os.write_string(2, &self.content)?; + } + os.write_unknown_fields(self.get_unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn get_cached_size(&self) -> u32 { + self.cached_size.get() + } + + fn get_unknown_fields(&self) -> &::protobuf::UnknownFields { + &self.unknown_fields + } + + fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields { + &mut self.unknown_fields + } + + fn as_any(&self) -> &dyn (::std::any::Any) { + self as &dyn (::std::any::Any) + } + fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) { + self as &mut dyn (::std::any::Any) + } + fn into_any(self: ::std::boxed::Box) -> ::std::boxed::Box { + self + } + + fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor { + Self::descriptor_static() + } + + fn new() -> URLCellData { + URLCellData::new() + } + + fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT; + descriptor.get(|| { + let mut fields = ::std::vec::Vec::new(); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "url", + |m: &URLCellData| { &m.url }, + |m: &mut URLCellData| { &mut m.url }, + )); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "content", + |m: &URLCellData| { &m.content }, + |m: &mut URLCellData| { &mut m.content }, + )); + ::protobuf::reflect::MessageDescriptor::new_pb_name::( + "URLCellData", + fields, + file_descriptor_proto() + ) + }) + } + + fn default_instance() -> &'static URLCellData { + static instance: ::protobuf::rt::LazyV2 = ::protobuf::rt::LazyV2::INIT; + instance.get(URLCellData::new) + } +} + +impl ::protobuf::Clear for URLCellData { + fn clear(&mut self) { + self.url.clear(); + self.content.clear(); + self.unknown_fields.clear(); + } +} + +impl ::std::fmt::Debug for URLCellData { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for URLCellData { + fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { + ::protobuf::reflect::ReflectValueRef::Message(self) + } +} + +static file_descriptor_proto_data: &'static [u8] = b"\ + \n\x15url_type_option.proto\"#\n\rURLTypeOption\x12\x12\n\x04data\x18\ + \x01\x20\x01(\tR\x04data\"9\n\x0bURLCellData\x12\x10\n\x03url\x18\x01\ + \x20\x01(\tR\x03url\x12\x18\n\x07content\x18\x02\x20\x01(\tR\x07contentb\ + \x06proto3\ +"; + +static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; + +fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto { + ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap() +} + +pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { + file_descriptor_proto_lazy.get(|| { + parse_descriptor_proto() + }) +} diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/proto/text_type_option.proto b/frontend/rust-lib/flowy-grid/src/protobuf/proto/text_type_option.proto index 67cfb438ea..827c569a74 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/proto/text_type_option.proto +++ b/frontend/rust-lib/flowy-grid/src/protobuf/proto/text_type_option.proto @@ -1,5 +1,5 @@ syntax = "proto3"; message RichTextTypeOption { - string format = 1; + string data = 1; } diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/proto/url_type_option.proto b/frontend/rust-lib/flowy-grid/src/protobuf/proto/url_type_option.proto new file mode 100644 index 0000000000..edf1c7e341 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/protobuf/proto/url_type_option.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +message URLTypeOption { + string data = 1; +} +message URLCellData { + string url = 1; + string content = 2; +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs index 5eaabb0294..7978323be1 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs @@ -94,6 +94,7 @@ pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box SingleSelectTypeOption::default().into(), FieldType::MultiSelect => MultiSelectTypeOption::default().into(), FieldType::Checkbox => CheckboxTypeOption::default().into(), + FieldType::URL => URLTypeOption::default().into(), }; type_option_builder_from_json_str(&s, field_type) @@ -107,6 +108,7 @@ pub fn type_option_builder_from_json_str(s: &str, field_type: &FieldType) -> Box FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_json_str(s)), FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_json_str(s)), FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_json_str(s)), + FieldType::URL => Box::new(URLTypeOptionBuilder::from_json_str(s)), } } @@ -119,5 +121,6 @@ pub fn type_option_builder_from_bytes>(bytes: T, field_type: &Fie FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_protobuf_bytes(bytes)), FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_protobuf_bytes(bytes)), FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_protobuf_bytes(bytes)), + FieldType::URL => Box::new(URLTypeOptionBuilder::from_protobuf_bytes(bytes)), } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs index 2c74b2097b..3cfe390b38 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs @@ -3,6 +3,7 @@ mod date_type_option; mod number_type_option; mod selection_type_option; mod text_type_option; +mod url_type_option; mod util; pub use checkbox_type_option::*; @@ -10,3 +11,4 @@ pub use date_type_option::*; pub use number_type_option::*; pub use selection_type_option::*; pub use text_type_option::*; +pub use url_type_option::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs index d500bbcc39..db6f7dffde 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs @@ -736,14 +736,10 @@ mod tests { ) { assert_eq!( type_option - .decode_cell_data(data(cell_data), field_type, field_meta) + .decode_cell_data(cell_data, field_type, field_meta) .unwrap() .content, expected_str.to_owned() ); } - - fn data(s: &str) -> String { - s.to_owned() - } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs index 32d45a3e29..199ddf38de 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs @@ -27,7 +27,7 @@ impl TypeOptionBuilder for RichTextTypeOptionBuilder { #[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)] pub struct RichTextTypeOption { #[pb(index = 1)] - pub format: String, + data: String, //It's not used. } impl_type_option!(RichTextTypeOption, FieldType::RichText); diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs new file mode 100644 index 0000000000..5ca29ee87d --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs @@ -0,0 +1,101 @@ +use crate::impl_type_option; +use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; +use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData}; +use bytes::Bytes; +use flowy_derive::ProtoBuf; +use flowy_error::{FlowyError, FlowyResult}; +use flowy_grid_data_model::entities::{ + CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, +}; + +use serde::{Deserialize, Serialize}; + +#[derive(Default)] +pub struct URLTypeOptionBuilder(URLTypeOption); +impl_into_box_type_option_builder!(URLTypeOptionBuilder); +impl_builder_from_json_str_and_from_bytes!(URLTypeOptionBuilder, URLTypeOption); + +impl TypeOptionBuilder for URLTypeOptionBuilder { + fn field_type(&self) -> FieldType { + self.0.field_type() + } + + fn entry(&self) -> &dyn TypeOptionDataEntry { + &self.0 + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] +pub struct URLTypeOption { + #[pb(index = 1)] + data: String, //It's not used. +} +impl_type_option!(URLTypeOption, FieldType::URL); + +impl CellDataOperation for URLTypeOption { + fn decode_cell_data( + &self, + encoded_data: T, + decoded_field_type: &FieldType, + _field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into, + { + if !decoded_field_type.is_url() { + return Ok(DecodedCellData::default()); + } + + let cell_data = encoded_data.into(); + Ok(DecodedCellData::from_content(cell_data)) + } + + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { + let changeset = changeset.into(); + Ok(changeset.to_string()) + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] +pub struct URLCellData { + #[pb(index = 1)] + pub url: String, + + #[pb(index = 2)] + pub content: String, +} + +#[cfg(test)] +mod tests { + use crate::services::field::FieldBuilder; + use crate::services::field::URLTypeOption; + use crate::services::row::CellDataOperation; + use flowy_grid_data_model::entities::{FieldMeta, FieldType}; + + #[test] + fn url_type_option_format_test() { + let type_option = URLTypeOption::default(); + let field_type = FieldType::URL; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + assert_equal(&type_option, "123", "123", &field_type, &field_meta); + } + + fn assert_equal( + type_option: &URLTypeOption, + cell_data: &str, + expected_str: &str, + field_type: &FieldType, + field_meta: &FieldMeta, + ) { + assert_eq!( + type_option + .decode_cell_data(cell_data, field_type, field_meta) + .unwrap() + .content, + expected_str.to_owned() + ); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs index 9911e4cab3..28fc0527a1 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs @@ -133,6 +133,7 @@ pub fn apply_cell_data_changeset>( FieldType::SingleSelect => SingleSelectTypeOption::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::URL => URLTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), }?; Ok(TypeOptionCellData::new(s, field_meta.field_type.clone()).json()) @@ -178,6 +179,9 @@ pub fn decode_cell_data>( FieldType::Checkbox => field_meta .get_type_option_entry::(t_field_type)? .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::URL => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), }; Some(data) }; diff --git a/shared-lib/flowy-grid-data-model/src/entities/grid.rs b/shared-lib/flowy-grid-data-model/src/entities/grid.rs index e8f3d615c4..e51f2222e2 100644 --- a/shared-lib/flowy-grid-data-model/src/entities/grid.rs +++ b/shared-lib/flowy-grid-data-model/src/entities/grid.rs @@ -880,6 +880,7 @@ pub enum FieldType { SingleSelect = 3, MultiSelect = 4, Checkbox = 5, + URL = 6, } impl std::default::Default for FieldType { @@ -937,6 +938,10 @@ impl FieldType { self == &FieldType::MultiSelect } + pub fn is_url(&self) -> bool { + self == &FieldType::URL + } + pub fn is_select_option(&self) -> bool { self == &FieldType::MultiSelect || self == &FieldType::SingleSelect } diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs index 0cda103da8..8c94e1aa30 100644 --- a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs +++ b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs @@ -8231,6 +8231,7 @@ pub enum FieldType { SingleSelect = 3, MultiSelect = 4, Checkbox = 5, + URL = 6, } impl ::protobuf::ProtobufEnum for FieldType { @@ -8246,6 +8247,7 @@ impl ::protobuf::ProtobufEnum for FieldType { 3 => ::std::option::Option::Some(FieldType::SingleSelect), 4 => ::std::option::Option::Some(FieldType::MultiSelect), 5 => ::std::option::Option::Some(FieldType::Checkbox), + 6 => ::std::option::Option::Some(FieldType::URL), _ => ::std::option::Option::None } } @@ -8258,6 +8260,7 @@ impl ::protobuf::ProtobufEnum for FieldType { FieldType::SingleSelect, FieldType::MultiSelect, FieldType::Checkbox, + FieldType::URL, ]; values } @@ -8380,10 +8383,10 @@ static file_descriptor_proto_data: &'static [u8] = b"\ ld_id\x18\x03\x20\x01(\tR\x07fieldId\x126\n\x16cell_content_changeset\ \x18\x04\x20\x01(\tH\0R\x14cellContentChangesetB\x1f\n\x1done_of_cell_co\ ntent_changeset**\n\x0cMoveItemType\x12\r\n\tMoveField\x10\0\x12\x0b\n\ - \x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\x08RichText\x10\0\x12\n\n\ + \x07MoveRow\x10\x01*m\n\tFieldType\x12\x0c\n\x08RichText\x10\0\x12\n\n\ \x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\x0cSingleSele\ ct\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08Checkbox\x10\ - \x05b\x06proto3\ + \x05\x12\x07\n\x03URL\x10\x06b\x06proto3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto index a2d74e96a3..b19ba4ef82 100644 --- a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto +++ b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto @@ -168,4 +168,5 @@ enum FieldType { SingleSelect = 3; MultiSelect = 4; Checkbox = 5; + URL = 6; } From 40e7b42a63235abdaae9dac0b2d01a35d876c125 Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 27 May 2022 19:57:13 +0800 Subject: [PATCH 15/23] chore: fix flutter warnings --- frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart index 35457ff989..ca3596e48a 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart @@ -9,7 +9,6 @@ import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-net/event.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-net/network_state.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user/event_map.pb.dart'; From 7a7ec1008563839cd153297c1556252353417630 Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 27 May 2022 20:26:33 +0800 Subject: [PATCH 16/23] chore: add test comand in cargo make --- frontend/Makefile.toml | 1 + frontend/scripts/makefile/tests.toml | 36 ++++++++++------------------ frontend/scripts/makefile/tool.toml | 27 +++++++++++++++++++++ 3 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 frontend/scripts/makefile/tool.toml diff --git a/frontend/Makefile.toml b/frontend/Makefile.toml index 7c82e17ba7..c46687a2df 100644 --- a/frontend/Makefile.toml +++ b/frontend/Makefile.toml @@ -7,6 +7,7 @@ extend = [ { path = "scripts/makefile/docker.toml" }, { path = "scripts/makefile/env.toml" }, { path = "scripts/makefile/flutter.toml" }, + { path = "scripts/makefile/tool.toml" }, ] [config] diff --git a/frontend/scripts/makefile/tests.toml b/frontend/scripts/makefile/tests.toml index 89e3ea9140..b8082f651b 100644 --- a/frontend/scripts/makefile/tests.toml +++ b/frontend/scripts/makefile/tests.toml @@ -1,30 +1,18 @@ -[tasks.test_local] -category = "Build" -dependencies = ["rm_cache"] -description = "Build desktop targets." +[tasks.rust_unit_test] +run_task = { name = ["rust_lib_unit_test", "shared_lib_unit_test"] } + +[tasks.rust_lib_unit_test] +description = "Run rust-lib unit tests" script = ''' cd rust-lib -cargo test +RUST_LOG=info cargo test --no-default-features --features="sync" ''' +[tasks.shared_lib_unit_test] +description = "Run shared-lib unit test" +script = ''' +cd ../shared-lib +RUST_LOG=info cargo test --no-default-features +''' -[tasks.test_remote] -dependencies = ["rm_cache"] -script = """ -cd rust-lib -cargo test --features "flowy-folder/http_server","flowy-user/http_server" -""" - - -[tasks.run_server] -script = """ -cd backend -cargo run -""" - - -[tasks.rm_cache] -script = """ -rm -rf rust-lib/flowy-test/temp -""" \ No newline at end of file diff --git a/frontend/scripts/makefile/tool.toml b/frontend/scripts/makefile/tool.toml new file mode 100644 index 0000000000..bd0504f6fc --- /dev/null +++ b/frontend/scripts/makefile/tool.toml @@ -0,0 +1,27 @@ +[tasks.rust_clean] +script = [ + """ + cd rust-lib + cargo clean + + cd ../../shared-lib + cargo clean + + rm -rf lib-infra/.cache + """, +] +script_runner = "@shell" + +[tasks.rust_clean.windows] +script = [ + """ + cd rust-lib + cargo clean + + cd ../../shared-lib + cargo clean + + rmdir /s/q "lib-infra/.cache" + """, +] +script_runner = "@duckscript" \ No newline at end of file From c4db17f73c3634a47d3c9d56744484f361e724eb Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Fri, 27 May 2022 10:34:12 +0800 Subject: [PATCH 17/23] feat: frameless window for mac --- .../app_flowy/lib/core/frameless_window.dart | 67 ++++++++++++++++++ .../workspace/application/home/home_bloc.dart | 6 ++ .../workspace/application/menu/menu_bloc.dart | 7 -- .../presentation/home/home_stack.dart | 15 +++- .../presentation/home/menu/menu.dart | 34 ++++++--- .../presentation/home/navigation.dart | 2 + .../macos/Runner/MainFlutterWindow.swift | 70 +++++++++++++++++++ 7 files changed, 183 insertions(+), 18 deletions(-) create mode 100644 frontend/app_flowy/lib/core/frameless_window.dart diff --git a/frontend/app_flowy/lib/core/frameless_window.dart b/frontend/app_flowy/lib/core/frameless_window.dart new file mode 100644 index 0000000000..a7d6417cd3 --- /dev/null +++ b/frontend/app_flowy/lib/core/frameless_window.dart @@ -0,0 +1,67 @@ +import 'package:flutter/services.dart'; +import 'package:flutter/material.dart'; +import 'dart:io' show Platform; + +class CocoaWindowChannel { + CocoaWindowChannel._(); + + final MethodChannel _channel = const MethodChannel("flutter/cocoaWindow"); + + static final CocoaWindowChannel instance = CocoaWindowChannel._(); + + Future setWindowPosition(Offset offset) async { + await _channel.invokeMethod("setWindowPosition", [offset.dx, offset.dy]); + } + + Future> getWindowPosition() async { + final raw = await _channel.invokeMethod("getWindowPosition"); + final arr = raw as List; + final List result = arr.map((s) => s as double).toList(); + return result; + } + + Future zoom() async { + await _channel.invokeMethod("zoom"); + } +} + +class MoveWindowDetector extends StatefulWidget { + const MoveWindowDetector({Key? key, this.child}) : super(key: key); + + final Widget? child; + + @override + _MoveWindowDetectorState createState() => _MoveWindowDetectorState(); +} + +class _MoveWindowDetectorState extends State { + double winX = 0; + double winY = 0; + + @override + Widget build(BuildContext context) { + if (!Platform.isMacOS) { + return widget.child ?? Container(); + } + return GestureDetector( + // https://stackoverflow.com/questions/52965799/flutter-gesturedetector-not-working-with-containers-in-stack + behavior: HitTestBehavior.translucent, + onDoubleTap: () async { + await CocoaWindowChannel.instance.zoom(); + }, + onPanStart: (DragStartDetails details) { + winX = details.globalPosition.dx; + winY = details.globalPosition.dy; + }, + onPanUpdate: (DragUpdateDetails details) async { + final windowPos = await CocoaWindowChannel.instance.getWindowPosition(); + final double dx = windowPos[0]; + final double dy = windowPos[1]; + final deltaX = details.globalPosition.dx - winX; + final deltaY = details.globalPosition.dy - winY; + await CocoaWindowChannel.instance.setWindowPosition(Offset(dx + deltaX, dy - deltaY)); + }, + child: widget.child, + ); + } +} diff --git a/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart b/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart index 796a0357b9..f3d9930842 100644 --- a/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart @@ -49,6 +49,9 @@ class HomeBloc extends Bloc { unauthorized: (_Unauthorized value) { emit(state.copyWith(unauthorized: true)); }, + collapseMenu: (e) { + emit(state.copyWith(isMenuCollapsed: !state.isMenuCollapsed)); + }, ); }); } @@ -77,6 +80,7 @@ class HomeEvent with _$HomeEvent { const factory HomeEvent.dismissEditPannel() = _DismissEditPannel; const factory HomeEvent.didReceiveWorkspaceSetting(CurrentWorkspaceSetting setting) = _DidReceiveWorkspaceSetting; const factory HomeEvent.unauthorized(String msg) = _Unauthorized; + const factory HomeEvent.collapseMenu() = _CollapseMenu; } @freezed @@ -87,6 +91,7 @@ class HomeState with _$HomeState { required Option pannelContext, required CurrentWorkspaceSetting workspaceSetting, required bool unauthorized, + required bool isMenuCollapsed, }) = _HomeState; factory HomeState.initial(CurrentWorkspaceSetting workspaceSetting) => HomeState( @@ -95,5 +100,6 @@ class HomeState with _$HomeState { pannelContext: none(), workspaceSetting: workspaceSetting, unauthorized: false, + isMenuCollapsed: false, ); } diff --git a/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart b/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart index a2c167cde4..db8f2c534b 100644 --- a/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart @@ -25,10 +25,6 @@ class MenuBloc extends Bloc { listener.start(addAppCallback: _handleAppsOrFail); await _fetchApps(emit); }, - collapse: (e) async { - final isCollapse = state.isCollapse; - emit(state.copyWith(isCollapse: !isCollapse)); - }, openPage: (e) async { emit(state.copyWith(plugin: e.plugin)); }, @@ -94,7 +90,6 @@ class MenuBloc extends Bloc { @freezed class MenuEvent with _$MenuEvent { const factory MenuEvent.initial() = _Initial; - const factory MenuEvent.collapse() = _Collapse; const factory MenuEvent.openPage(Plugin plugin) = _OpenPage; const factory MenuEvent.createApp(String name, {String? desc}) = _CreateApp; const factory MenuEvent.moveApp(int fromIndex, int toIndex) = _MoveApp; @@ -104,14 +99,12 @@ class MenuEvent with _$MenuEvent { @freezed class MenuState with _$MenuState { const factory MenuState({ - required bool isCollapse, required List apps, required Either successOrFailure, required Plugin plugin, }) = _MenuState; factory MenuState.initial() => MenuState( - isCollapse: false, apps: [], successOrFailure: left(unit), plugin: makePlugin(pluginType: DefaultPlugin.blank.type()), diff --git a/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart b/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart index 65e315f56d..c16c965a82 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart @@ -1,8 +1,12 @@ +import 'dart:io' show Platform; + import 'package:app_flowy/startup/startup.dart'; +import 'package:app_flowy/workspace/application/home/home_bloc.dart'; import 'package:app_flowy/workspace/presentation/home/home_screen.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; import 'package:time/time.dart'; import 'package:fluttertoast/fluttertoast.dart'; @@ -11,6 +15,7 @@ import 'package:app_flowy/plugin/plugin.dart'; import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart'; import 'package:app_flowy/workspace/presentation/home/home_sizes.dart'; import 'package:app_flowy/workspace/presentation/home/navigation.dart'; +import 'package:app_flowy/core/frameless_window.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/style_widget/extension.dart'; import 'package:flowy_infra/notifier.dart'; @@ -152,7 +157,7 @@ class HomeStackManager { child: Selector( selector: (context, notifier) => notifier.titleWidget, builder: (context, widget, child) { - return const HomeTopBar(); + return const MoveWindowDetector(child: HomeTopBar()); }, ), ); @@ -191,6 +196,14 @@ class HomeTopBar extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ + BlocBuilder( + buildWhen: ((previous, current) => previous.isMenuCollapsed != current.isMenuCollapsed), + builder: (context, state) { + if (state.isMenuCollapsed && Platform.isMacOS) { + return const HSpace(80); + } + return const HSpace(0); + }), const FlowyNavigation(), const HSpace(16), ChangeNotifierProvider.value( diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart index b888ab7631..0eb22e3d1f 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart @@ -1,6 +1,8 @@ export './app/header/header.dart'; export './app/menu_app.dart'; +import 'dart:io' show Platform; +import 'package:app_flowy/workspace/presentation/home/home_sizes.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:app_flowy/workspace/presentation/plugins/trash/menu.dart'; import 'package:flowy_infra/notifier.dart'; @@ -18,7 +20,9 @@ import 'package:expandable/expandable.dart'; import 'package:flowy_infra/time/duration.dart'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/menu/menu_bloc.dart'; -import 'package:app_flowy/workspace/presentation/home/home_sizes.dart'; +import 'package:app_flowy/workspace/application/home/home_bloc.dart'; +import 'package:app_flowy/core/frameless_window.dart'; +// import 'package:app_flowy/workspace/presentation/home/home_sizes.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; @@ -59,10 +63,10 @@ class HomeMenu extends StatelessWidget { getIt().setPlugin(state.plugin); }, ), - BlocListener( - listenWhen: (p, c) => p.isCollapse != c.isCollapse, + BlocListener( + listenWhen: (p, c) => p.isMenuCollapsed != c.isMenuCollapsed, listener: (context, state) { - _collapsedNotifier.value = state.isCollapse; + _collapsedNotifier.value = state.isMenuCollapsed; }, ) ], @@ -179,6 +183,17 @@ class MenuSharedState { class MenuTopBar extends StatelessWidget { const MenuTopBar({Key? key}) : super(key: key); + + Widget renderIcon(BuildContext context) { + if (Platform.isMacOS) { + return Container(); + } + final theme = context.watch(); + return (theme.isDark + ? svgWithSize("flowy_logo_dark_mode", const Size(92, 17)) + : svgWithSize("flowy_logo_with_text", const Size(92, 17))); + } + @override Widget build(BuildContext context) { final theme = context.watch(); @@ -186,20 +201,19 @@ class MenuTopBar extends StatelessWidget { builder: (context, state) { return SizedBox( height: HomeSizes.topBarHeight, - child: Row( + child: MoveWindowDetector( + child: Row( children: [ - (theme.isDark - ? svgWithSize("flowy_logo_dark_mode", const Size(92, 17)) - : svgWithSize("flowy_logo_with_text", const Size(92, 17))), + renderIcon(context), const Spacer(), FlowyIconButton( width: 28, - onPressed: () => context.read().add(const MenuEvent.collapse()), + onPressed: () => context.read().add(const HomeEvent.collapseMenu()), iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), icon: svgWidget("home/hide_menu", color: theme.iconColor), ) ], - ), + )), ); }, ); diff --git a/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart b/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart index b6947c79fe..f52b7224f6 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart @@ -1,3 +1,4 @@ +import 'package:app_flowy/workspace/application/home/home_bloc.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/notifier.dart'; @@ -95,6 +96,7 @@ class FlowyNavigation extends StatelessWidget { width: 24, onPressed: () { notifier.value = false; + ctx.read().add(const HomeEvent.collapseMenu()); }, iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2), icon: svgWidget("home/hide_menu", color: theme.iconColor), diff --git a/frontend/app_flowy/macos/Runner/MainFlutterWindow.swift b/frontend/app_flowy/macos/Runner/MainFlutterWindow.swift index 2722837ec9..8e357d7ca1 100644 --- a/frontend/app_flowy/macos/Runner/MainFlutterWindow.swift +++ b/frontend/app_flowy/macos/Runner/MainFlutterWindow.swift @@ -1,12 +1,82 @@ import Cocoa import FlutterMacOS +private let kTrafficLightOffetTop = 22 + class MainFlutterWindow: NSWindow { + func registerMethodChannel(flutterViewController: FlutterViewController) { + let cocoaWindowChannel = FlutterMethodChannel(name: "flutter/cocoaWindow", binaryMessenger: flutterViewController.engine.binaryMessenger) + cocoaWindowChannel.setMethodCallHandler({ + (call: FlutterMethodCall, result: FlutterResult) -> Void in + if call.method == "setWindowPosition" { + guard let position = call.arguments as? NSArray else { + result(nil) + return + } + let nX = position[0] as! NSNumber + let nY = position[1] as! NSNumber + let x = nX.doubleValue + let y = nY.doubleValue + + self.setFrameOrigin(NSPoint(x: x, y: y)) + result(nil) + return + } else if call.method == "getWindowPosition" { + let frame = self.frame + result([frame.origin.x, frame.origin.y]) + return + } else if call.method == "zoom" { + self.zoom(self) + result(nil) + return + } + + result(FlutterMethodNotImplemented) + }) + } + + func layoutTrafficLightButton(titlebarView: NSView, button: NSButton, offsetTop: CGFloat, offsetLeft: CGFloat) { + button.translatesAutoresizingMaskIntoConstraints = false; + titlebarView.addConstraint(NSLayoutConstraint.init( + item: button, + attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: titlebarView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: offsetTop)) + titlebarView.addConstraint(NSLayoutConstraint.init( + item: button, + attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: titlebarView, attribute: NSLayoutConstraint.Attribute.left, multiplier: 1, constant: offsetLeft)) + } + + func layoutTrafficLights() { + let closeButton = self.standardWindowButton(ButtonType.closeButton)! + let minButton = self.standardWindowButton(ButtonType.miniaturizeButton)! + let zoomButton = self.standardWindowButton(ButtonType.zoomButton)! + let titlebarView = closeButton.superview! + + self.layoutTrafficLightButton(titlebarView: titlebarView, button: closeButton, offsetTop: CGFloat(kTrafficLightOffetTop), offsetLeft: 20) + self.layoutTrafficLightButton(titlebarView: titlebarView, button: minButton, offsetTop: CGFloat(kTrafficLightOffetTop), offsetLeft: 38) + self.layoutTrafficLightButton(titlebarView: titlebarView, button: zoomButton, offsetTop: CGFloat(kTrafficLightOffetTop), offsetLeft: 56) + + let customToolbar = NSTitlebarAccessoryViewController() + let newView = NSView() + newView.frame = NSRect(origin: CGPoint(), size: CGSize(width: 0, height: 40)) // only the height is cared + customToolbar.view = newView + self.addTitlebarAccessoryViewController(customToolbar) + } + override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController + + self.registerMethodChannel(flutterViewController: flutterViewController) + self.setFrame(windowFrame, display: true) + self.titlebarAppearsTransparent = true + self.titleVisibility = .hidden + self.styleMask.insert(StyleMask.fullSizeContentView) + self.isMovableByWindowBackground = true + self.isMovable = false + + self.layoutTrafficLights() RegisterGeneratedPlugins(registry: flutterViewController) From 166438ad73f9de9a8c9bb458cfdbb713e5dc855e Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 27 May 2022 20:19:22 +0800 Subject: [PATCH 18/23] chore: fix rust unit test --- frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs | 5 +++++ frontend/rust-lib/flowy-grid/tests/grid/script.rs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs index f4549cb726..b78f9be551 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs @@ -262,6 +262,9 @@ async fn grid_row_add_cells_test() { FieldType::Checkbox => { builder.add_cell(&field.id, "false".to_string()).unwrap(); } + FieldType::URL => { + builder.add_cell(&field.id, "1".to_string()).unwrap(); + } } } let context = builder.build(); @@ -327,6 +330,7 @@ async fn grid_cell_update() { SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str() } FieldType::Checkbox => "1".to_string(), + FieldType::URL => "1".to_string(), }; scripts.push(UpdateCell { @@ -348,6 +352,7 @@ async fn grid_cell_update() { FieldType::SingleSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false), FieldType::MultiSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false), FieldType::Checkbox => ("2".to_string(), false), + FieldType::URL => ("2".to_string(), false), }; scripts.push(UpdateCell { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/script.rs index 611ee937e2..956bd4dd6c 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/script.rs @@ -354,6 +354,10 @@ fn make_template_1_grid() -> BuildGridContext { let checkbox = CheckboxTypeOptionBuilder::default(); let checkbox_field = FieldBuilder::new(checkbox).name("is done").visibility(true).build(); + // URL + let url = URLTypeOptionBuilder::default(); + let url_field = FieldBuilder::new(url).name("link").visibility(true).build(); + GridBuilder::default() .add_field(text_field) .add_field(single_select_field) @@ -361,6 +365,7 @@ fn make_template_1_grid() -> BuildGridContext { .add_field(number_field) .add_field(date_field) .add_field(checkbox_field) + .add_field(url_field) .add_empty_row() .add_empty_row() .add_empty_row() From 8d766f3bb43cc9993c53fdff21dc6ac1032c7494 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sat, 28 May 2022 08:35:22 +0800 Subject: [PATCH 19/23] refactor: cell data loader --- .../grid/cell/cell_service/data_loader.dart | 12 +- .../grid/cell/checkbox_cell_bloc.dart | 22 ++-- .../grid/cell/number_cell_bloc.dart | 13 +-- .../application/grid/cell/text_cell_bloc.dart | 13 +-- .../flowy-grid-data-model/grid.pb.dart | 8 +- .../flowy-grid-data-model/grid.pbjson.dart | 4 +- .../src/services/row/cell_data_operation.rs | 13 ++- .../src/entities/grid.rs | 6 +- .../src/protobuf/model/grid.rs | 106 +++++++++--------- .../src/protobuf/proto/grid.proto | 2 +- 10 files changed, 99 insertions(+), 100 deletions(-) diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart index 5be0435323..2e1801103e 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart @@ -30,7 +30,11 @@ abstract class IGridCellDataLoader { IGridCellDataConfig get config; } -class GridCellDataLoader extends IGridCellDataLoader { +abstract class ICellDataParser { + T? parserData(); +} + +class GridCellDataLoader extends IGridCellDataLoader { final CellService service = CellService(); final GridCell gridCell; @@ -43,16 +47,16 @@ class GridCellDataLoader extends IGridCellDataLoader { }); @override - Future loadData() { + Future loadData() { final fut = service.getCell( gridId: gridCell.gridId, fieldId: gridCell.field.id, rowId: gridCell.rowId, ); return fut.then((result) { - return result.fold((data) => data, (err) { + return result.fold((Cell data) => data.content, (err) { Log.error(err); - return null; + return ""; }); }); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart index cecfaa2f04..514ae2ce4e 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart @@ -1,4 +1,3 @@ -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -16,15 +15,15 @@ class CheckboxCellBloc extends Bloc { }) : super(CheckboxCellState.initial(cellContext)) { on( (event, emit) async { - await event.map( - initial: (_Initial value) { + await event.when( + initial: () { _startListening(); }, - select: (_Selected value) async { + select: () async { _updateCellData(); }, - didReceiveCellUpdate: (_DidReceiveCellUpdate value) { - emit(state.copyWith(isSelected: _isSelected(value.cell))); + didReceiveCellUpdate: (cellData) { + emit(state.copyWith(isSelected: _isSelected(cellData))); }, ); }, @@ -43,9 +42,9 @@ class CheckboxCellBloc extends Bloc { } void _startListening() { - _onCellChangedFn = cellContext.startListening(onCellChanged: ((cell) { + _onCellChangedFn = cellContext.startListening(onCellChanged: ((cellData) { if (!isClosed) { - add(CheckboxCellEvent.didReceiveCellUpdate(cell)); + add(CheckboxCellEvent.didReceiveCellUpdate(cellData)); } })); } @@ -59,7 +58,7 @@ class CheckboxCellBloc extends Bloc { class CheckboxCellEvent with _$CheckboxCellEvent { const factory CheckboxCellEvent.initial() = _Initial; const factory CheckboxCellEvent.select() = _Selected; - const factory CheckboxCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate; + const factory CheckboxCellEvent.didReceiveCellUpdate(String cellData) = _DidReceiveCellUpdate; } @freezed @@ -73,7 +72,6 @@ class CheckboxCellState with _$CheckboxCellState { } } -bool _isSelected(Cell? cell) { - final content = cell?.content ?? ""; - return content == "Yes"; +bool _isSelected(String? cellData) { + return cellData == "Yes"; } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart index 217ae4d384..ceb89bc201 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart @@ -1,4 +1,3 @@ -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -20,7 +19,7 @@ class NumberCellBloc extends Bloc { _startListening(); }, didReceiveCellUpdate: (_DidReceiveCellUpdate value) { - emit(state.copyWith(content: value.cell.content)); + emit(state.copyWith(content: value.cellContent)); }, updateCell: (_UpdateCell value) async { await _updateCellValue(value, emit); @@ -46,9 +45,9 @@ class NumberCellBloc extends Bloc { void _startListening() { _onCellChangedFn = cellContext.startListening( - onCellChanged: ((cell) { + onCellChanged: ((cellContent) { if (!isClosed) { - add(NumberCellEvent.didReceiveCellUpdate(cell)); + add(NumberCellEvent.didReceiveCellUpdate(cellContent)); } }), ); @@ -59,7 +58,7 @@ class NumberCellBloc extends Bloc { class NumberCellEvent with _$NumberCellEvent { const factory NumberCellEvent.initial() = _Initial; const factory NumberCellEvent.updateCell(String text) = _UpdateCell; - const factory NumberCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate; + const factory NumberCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate; } @freezed @@ -69,7 +68,7 @@ class NumberCellState with _$NumberCellState { }) = _NumberCellState; factory NumberCellState.initial(GridCellContext context) { - final cell = context.getCellData(); - return NumberCellState(content: cell?.content ?? ""); + final cellContent = context.getCellData() ?? ""; + return NumberCellState(content: cellContent); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart index 0b8b8be5e0..2123802966 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart @@ -1,4 +1,3 @@ -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -26,9 +25,7 @@ class TextCellBloc extends Bloc { emit(state.copyWith(content: value.cellData.cell?.content ?? "")); }, didReceiveCellUpdate: (_DidReceiveCellUpdate value) { - emit(state.copyWith( - content: value.cell.content, - )); + emit(state.copyWith(content: value.cellContent)); }, ); }, @@ -47,9 +44,9 @@ class TextCellBloc extends Bloc { void _startListening() { _onCellChangedFn = cellContext.startListening( - onCellChanged: ((cell) { + onCellChanged: ((cellContent) { if (!isClosed) { - add(TextCellEvent.didReceiveCellUpdate(cell)); + add(TextCellEvent.didReceiveCellUpdate(cellContent)); } }), ); @@ -60,7 +57,7 @@ class TextCellBloc extends Bloc { class TextCellEvent with _$TextCellEvent { const factory TextCellEvent.initial() = _InitialCell; const factory TextCellEvent.didReceiveCellData(GridCell cellData) = _DidReceiveCellData; - const factory TextCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate; + const factory TextCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate; const factory TextCellEvent.updateText(String text) = _UpdateText; } @@ -71,6 +68,6 @@ class TextCellState with _$TextCellState { }) = _TextCellState; factory TextCellState.initial(GridCellContext context) => TextCellState( - content: context.getCellData()?.content ?? "", + content: context.getCellData() ?? "", ); } diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart index a1ecd8c059..19a1aca54d 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart @@ -1367,7 +1367,7 @@ class Cell extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Cell', createEmptyInstance: create) ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId') ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'content') - ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data') + ..a<$core.List<$core.int>>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY) ..hasRequiredFields = false ; @@ -1375,7 +1375,7 @@ class Cell extends $pb.GeneratedMessage { factory Cell({ $core.String? fieldId, $core.String? content, - $core.String? data, + $core.List<$core.int>? data, }) { final _result = create(); if (fieldId != null) { @@ -1429,9 +1429,9 @@ class Cell extends $pb.GeneratedMessage { void clearContent() => clearField(2); @$pb.TagNumber(3) - $core.String get data => $_getSZ(2); + $core.List<$core.int> get data => $_getN(2); @$pb.TagNumber(3) - set data($core.String v) { $_setString(2, v); } + set data($core.List<$core.int> v) { $_setBytes(2, v); } @$pb.TagNumber(3) $core.bool hasData() => $_has(2); @$pb.TagNumber(3) diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart index 3a925cb282..c203258374 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart @@ -290,12 +290,12 @@ const Cell$json = const { '2': const [ const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'}, const {'1': 'content', '3': 2, '4': 1, '5': 9, '10': 'content'}, - const {'1': 'data', '3': 3, '4': 1, '5': 9, '10': 'data'}, + const {'1': 'data', '3': 3, '4': 1, '5': 12, '10': 'data'}, ], }; /// Descriptor for `Cell`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List cellDescriptor = $convert.base64Decode('CgRDZWxsEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEhgKB2NvbnRlbnQYAiABKAlSB2NvbnRlbnQSEgoEZGF0YRgDIAEoCVIEZGF0YQ=='); +final $typed_data.Uint8List cellDescriptor = $convert.base64Decode('CgRDZWxsEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEhgKB2NvbnRlbnQYAiABKAlSB2NvbnRlbnQSEgoEZGF0YRgDIAEoDFIEZGF0YQ=='); @$core.Deprecated('Use repeatedCellDescriptor instead') const RepeatedCell$json = const { '1': 'RepeatedCell', diff --git a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs index 9911e4cab3..f92e4ac8d2 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs @@ -226,23 +226,24 @@ where #[derive(Default)] pub struct DecodedCellData { - raw: String, + pub data: Vec, pub content: String, } impl DecodedCellData { pub fn from_content(content: String) -> Self { Self { - raw: content.clone(), + data: content.as_bytes().to_vec(), content, } } - pub fn new(raw: String, content: String) -> Self { - Self { raw, content } + pub fn new>(data: T, content: String) -> Self { + let data = data.as_ref().to_vec(); + Self { data, content } } - pub fn split(self) -> (String, String) { - (self.raw, self.content) + pub fn split(self) -> (Vec, String) { + (self.data, self.content) } } diff --git a/shared-lib/flowy-grid-data-model/src/entities/grid.rs b/shared-lib/flowy-grid-data-model/src/entities/grid.rs index e8f3d615c4..dab20b772c 100644 --- a/shared-lib/flowy-grid-data-model/src/entities/grid.rs +++ b/shared-lib/flowy-grid-data-model/src/entities/grid.rs @@ -487,11 +487,11 @@ pub struct Cell { pub content: String, #[pb(index = 3)] - pub data: String, + pub data: Vec, } impl Cell { - pub fn new(field_id: &str, content: String, data: String) -> Self { + pub fn new(field_id: &str, content: String, data: Vec) -> Self { Self { field_id: field_id.to_owned(), content, @@ -503,7 +503,7 @@ impl Cell { Self { field_id: field_id.to_owned(), content: "".to_string(), - data: "".to_string(), + data: vec![], } } } diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs index 0cda103da8..e5f9102fd5 100644 --- a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs +++ b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs @@ -4744,7 +4744,7 @@ pub struct Cell { // message fields pub field_id: ::std::string::String, pub content: ::std::string::String, - pub data: ::std::string::String, + pub data: ::std::vec::Vec, // special fields pub unknown_fields: ::protobuf::UnknownFields, pub cached_size: ::protobuf::CachedSize, @@ -4813,10 +4813,10 @@ impl Cell { ::std::mem::replace(&mut self.content, ::std::string::String::new()) } - // string data = 3; + // bytes data = 3; - pub fn get_data(&self) -> &str { + pub fn get_data(&self) -> &[u8] { &self.data } pub fn clear_data(&mut self) { @@ -4824,19 +4824,19 @@ impl Cell { } // Param is passed by value, moved - pub fn set_data(&mut self, v: ::std::string::String) { + pub fn set_data(&mut self, v: ::std::vec::Vec) { self.data = v; } // Mutable pointer to the field. // If field is not initialized, it is initialized with default value first. - pub fn mut_data(&mut self) -> &mut ::std::string::String { + pub fn mut_data(&mut self) -> &mut ::std::vec::Vec { &mut self.data } // Take field - pub fn take_data(&mut self) -> ::std::string::String { - ::std::mem::replace(&mut self.data, ::std::string::String::new()) + pub fn take_data(&mut self) -> ::std::vec::Vec { + ::std::mem::replace(&mut self.data, ::std::vec::Vec::new()) } } @@ -4856,7 +4856,7 @@ impl ::protobuf::Message for Cell { ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.content)?; }, 3 => { - ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?; + ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.data)?; }, _ => { ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; @@ -4877,7 +4877,7 @@ impl ::protobuf::Message for Cell { my_size += ::protobuf::rt::string_size(2, &self.content); } if !self.data.is_empty() { - my_size += ::protobuf::rt::string_size(3, &self.data); + my_size += ::protobuf::rt::bytes_size(3, &self.data); } my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); self.cached_size.set(my_size); @@ -4892,7 +4892,7 @@ impl ::protobuf::Message for Cell { os.write_string(2, &self.content)?; } if !self.data.is_empty() { - os.write_string(3, &self.data)?; + os.write_bytes(3, &self.data)?; } os.write_unknown_fields(self.get_unknown_fields())?; ::std::result::Result::Ok(()) @@ -4942,7 +4942,7 @@ impl ::protobuf::Message for Cell { |m: &Cell| { &m.content }, |m: &mut Cell| { &mut m.content }, )); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>( "data", |m: &Cell| { &m.data }, |m: &mut Cell| { &mut m.data }, @@ -8342,48 +8342,48 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x01(\tR\x02id\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trow\ Orders\"O\n\x04Cell\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\ \x12\x18\n\x07content\x18\x02\x20\x01(\tR\x07content\x12\x12\n\x04data\ - \x18\x03\x20\x01(\tR\x04data\"+\n\x0cRepeatedCell\x12\x1b\n\x05items\x18\ - \x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11CreateGridPayload\x12\x12\ - \n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\x05va\ - lue\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlockId\x12\x14\n\x05value\ - \x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\n\x07grid\ - _id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_row_id\x18\x02\x20\ - \x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\"\xb6\x01\n\x12Ins\ - ertFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\ - \x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05field\x12(\n\x10type\ - _option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\x12&\n\x0estart_fie\ - ld_id\x18\x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\x15one_of_start_fiel\ - d_id\"|\n\x1cUpdateFieldTypeOptionPayload\x12\x17\n\x07grid_id\x18\x01\ - \x20\x01(\tR\x06gridId\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\x07fiel\ - dId\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\"\ - d\n\x11QueryFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gri\ - dId\x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\x13.RepeatedFieldOrderR\ - \x0bfieldOrders\"e\n\x16QueryGridBlocksPayload\x12\x17\n\x07grid_id\x18\ - \x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orders\x18\x02\x20\x03(\x0b2\ - \x0f.GridBlockOrderR\x0bblockOrders\"\xa8\x03\n\x15FieldChangesetPayload\ - \x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x17\n\x07grid_\ - id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04name\x18\x03\x20\x01(\tH\0\ - R\x04name\x12\x14\n\x04desc\x18\x04\x20\x01(\tH\x01R\x04desc\x12+\n\nfie\ - ld_type\x18\x05\x20\x01(\x0e2\n.FieldTypeH\x02R\tfieldType\x12\x18\n\x06\ - frozen\x18\x06\x20\x01(\x08H\x03R\x06frozen\x12\x20\n\nvisibility\x18\ - \x07\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05width\x18\x08\x20\x01(\ - \x05H\x05R\x05width\x12*\n\x10type_option_data\x18\t\x20\x01(\x0cH\x06R\ - \x0etypeOptionDataB\r\n\x0bone_of_nameB\r\n\x0bone_of_descB\x13\n\x11one\ - _of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_of_visibilityB\x0e\n\ - \x0cone_of_widthB\x19\n\x17one_of_type_option_data\"\x9c\x01\n\x0fMoveIt\ - emPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x17\n\ - \x07item_id\x18\x02\x20\x01(\tR\x06itemId\x12\x1d\n\nfrom_index\x18\x03\ - \x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_index\x18\x04\x20\x01(\x05R\ - \x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\x0e2\r.MoveItemTypeR\x02ty\ - \"\xb3\x01\n\rCellChangeset\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06\ - gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08fie\ - ld_id\x18\x03\x20\x01(\tR\x07fieldId\x126\n\x16cell_content_changeset\ - \x18\x04\x20\x01(\tH\0R\x14cellContentChangesetB\x1f\n\x1done_of_cell_co\ - ntent_changeset**\n\x0cMoveItemType\x12\r\n\tMoveField\x10\0\x12\x0b\n\ - \x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\x08RichText\x10\0\x12\n\n\ - \x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\x0cSingleSele\ - ct\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08Checkbox\x10\ - \x05b\x06proto3\ + \x18\x03\x20\x01(\x0cR\x04data\"+\n\x0cRepeatedCell\x12\x1b\n\x05items\ + \x18\x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11CreateGridPayload\x12\ + \x12\n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\ + \x05value\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlockId\x12\x14\n\x05\ + value\x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\n\ + \x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_row_id\x18\ + \x02\x20\x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\"\xb6\x01\ + \n\x12InsertFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gri\ + dId\x12\x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05field\x12(\n\ + \x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\x12&\n\x0es\ + tart_field_id\x18\x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\x15one_of_st\ + art_field_id\"|\n\x1cUpdateFieldTypeOptionPayload\x12\x17\n\x07grid_id\ + \x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\ + \x07fieldId\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOpti\ + onData\"d\n\x11QueryFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\t\ + R\x06gridId\x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\x13.RepeatedFie\ + ldOrderR\x0bfieldOrders\"e\n\x16QueryGridBlocksPayload\x12\x17\n\x07grid\ + _id\x18\x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orders\x18\x02\x20\x03\ + (\x0b2\x0f.GridBlockOrderR\x0bblockOrders\"\xa8\x03\n\x15FieldChangesetP\ + ayload\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x17\n\ + \x07grid_id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04name\x18\x03\x20\ + \x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\x04\x20\x01(\tH\x01R\x04desc\ + \x12+\n\nfield_type\x18\x05\x20\x01(\x0e2\n.FieldTypeH\x02R\tfieldType\ + \x12\x18\n\x06frozen\x18\x06\x20\x01(\x08H\x03R\x06frozen\x12\x20\n\nvis\ + ibility\x18\x07\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05width\x18\ + \x08\x20\x01(\x05H\x05R\x05width\x12*\n\x10type_option_data\x18\t\x20\ + \x01(\x0cH\x06R\x0etypeOptionDataB\r\n\x0bone_of_nameB\r\n\x0bone_of_des\ + cB\x13\n\x11one_of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_of_vis\ + ibilityB\x0e\n\x0cone_of_widthB\x19\n\x17one_of_type_option_data\"\x9c\ + \x01\n\x0fMoveItemPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gr\ + idId\x12\x17\n\x07item_id\x18\x02\x20\x01(\tR\x06itemId\x12\x1d\n\nfrom_\ + index\x18\x03\x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_index\x18\x04\x20\ + \x01(\x05R\x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\x0e2\r.MoveItemTy\ + peR\x02ty\"\xb3\x01\n\rCellChangeset\x12\x17\n\x07grid_id\x18\x01\x20\ + \x01(\tR\x06gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\ + \x19\n\x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x126\n\x16cell_content\ + _changeset\x18\x04\x20\x01(\tH\0R\x14cellContentChangesetB\x1f\n\x1done_\ + of_cell_content_changeset**\n\x0cMoveItemType\x12\r\n\tMoveField\x10\0\ + \x12\x0b\n\x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\x08RichText\x10\0\ + \x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\x0cSi\ + ngleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08Checkbo\ + x\x10\x05b\x06proto3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto index a2d74e96a3..d762e65e70 100644 --- a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto +++ b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto @@ -96,7 +96,7 @@ message GridBlock { message Cell { string field_id = 1; string content = 2; - string data = 3; + bytes data = 3; } message RepeatedCell { repeated Cell items = 1; From 62c322ab219479be2f065f8171da9aeb749ead94 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sat, 28 May 2022 15:30:15 +0800 Subject: [PATCH 20/23] refactor: Date & SingleSelect & Multi-Select type option --- .../grid/cell/cell_service/cell_service.dart | 2 +- .../cell/cell_service/context_builder.dart | 40 ++-- .../grid/cell/cell_service/data_loader.dart | 86 ++++---- .../grid/cell/select_option_cell_bloc.dart | 5 - .../grid/cell/select_option_editor_bloc.dart | 21 ++ .../application/grid/cell/text_cell_bloc.dart | 18 +- .../dart_event/flowy-grid/dart_event.dart | 17 -- .../flowy-grid-data-model/grid.pb.dart | 24 +-- .../flowy-grid-data-model/grid.pbjson.dart | 5 +- .../protobuf/flowy-grid/event_map.pbenum.dart | 2 - .../protobuf/flowy-grid/event_map.pbjson.dart | 3 +- .../rust-lib/flowy-grid/src/event_handler.rs | 21 -- frontend/rust-lib/flowy-grid/src/event_map.rs | 4 - .../src/protobuf/model/event_map.rs | 7 +- .../src/protobuf/proto/event_map.proto | 1 - .../type_options/checkbox_type_option.rs | 14 +- .../field/type_options/date_type_option.rs | 183 ++++++------------ .../field/type_options/number_type_option.rs | 10 +- .../type_options/selection_type_option.rs | 153 ++++++++------- .../field/type_options/text_type_option.rs | 26 ++- .../src/services/row/cell_data_operation.rs | 55 ++++-- .../flowy-grid/src/services/row/row_loader.rs | 10 +- .../flowy-grid/tests/grid/grid_test.rs | 7 +- .../flowy-derive/src/proto_buf/deserialize.rs | 8 + .../src/entities/grid.rs | 7 +- .../src/protobuf/model/grid.rs | 135 +++++-------- .../src/protobuf/proto/grid.proto | 3 +- 27 files changed, 382 insertions(+), 485 deletions(-) diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart index 07be14efa2..09b8bef584 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart @@ -16,7 +16,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; - +import 'dart:convert' show utf8; part 'cell_service.freezed.dart'; part 'data_loader.dart'; part 'context_builder.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart index ed191c7d60..c6f71a2e32 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart @@ -1,6 +1,6 @@ part of 'cell_service.dart'; -typedef GridCellContext = _GridCellContext; +typedef GridCellContext = _GridCellContext; typedef GridSelectOptionCellContext = _GridCellContext; typedef GridDateCellContext = _GridCellContext; @@ -16,26 +16,33 @@ class GridCellContextBuilder { _GridCellContext build() { switch (_gridCell.field.fieldType) { case FieldType.Checkbox: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: StringCellDataParser(), + ); return GridCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: GridCellDataLoader(gridCell: _gridCell), + cellDataLoader: cellDataLoader, cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); case FieldType.DateTime: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: DateCellDataParser(), + ); + return GridDateCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: DateCellDataLoader(gridCell: _gridCell), + cellDataLoader: cellDataLoader, cellDataPersistence: DateCellDataPersistence(gridCell: _gridCell), ); case FieldType.Number: final cellDataLoader = GridCellDataLoader( gridCell: _gridCell, - config: const GridCellDataConfig( - reloadOnCellChanged: true, - reloadOnFieldChanged: true, - ), + parser: StringCellDataParser(), + config: const GridCellDataConfig(reloadOnCellChanged: true, reloadOnFieldChanged: true), ); return GridCellContext( gridCell: _gridCell, @@ -44,18 +51,28 @@ class GridCellContextBuilder { cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); case FieldType.RichText: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: StringCellDataParser(), + ); return GridCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: GridCellDataLoader(gridCell: _gridCell), + cellDataLoader: cellDataLoader, cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); case FieldType.MultiSelect: case FieldType.SingleSelect: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: SelectOptionCellDataParser(), + config: const GridCellDataConfig(reloadOnFieldChanged: true), + ); + return GridSelectOptionCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: SelectOptionCellDataLoader(gridCell: _gridCell), + cellDataLoader: cellDataLoader, cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); default: @@ -131,10 +148,7 @@ class _GridCellContext extends Equatable { } onCellChangedFn() { - final value = _cellDataNotifier.value; - if (value is T) { - onCellChanged(value); - } + onCellChanged(_cellDataNotifier.value as T); if (cellDataLoader.config.reloadOnCellChanged) { _loadData(); diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart index 2e1801103e..4b66c08224 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart @@ -4,8 +4,8 @@ abstract class IGridCellDataConfig { // The cell data will reload if it receives the field's change notification. bool get reloadOnFieldChanged; - // The cell data will reload if it receives the cell's change notification. - // For example, the number cell should be reloaded after user input the number. + // When the reloadOnCellChanged is true, it will load the cell data after user input. + // For example: The number cell reload the cell data that carries the format // user input: 12 // cell display: $12 bool get reloadOnCellChanged; @@ -31,63 +31,44 @@ abstract class IGridCellDataLoader { } abstract class ICellDataParser { - T? parserData(); + T? parserData(List data); } -class GridCellDataLoader extends IGridCellDataLoader { +class GridCellDataLoader extends IGridCellDataLoader { final CellService service = CellService(); final GridCell gridCell; + final ICellDataParser parser; @override final IGridCellDataConfig config; GridCellDataLoader({ required this.gridCell, + required this.parser, this.config = const GridCellDataConfig(), }); @override - Future loadData() { + Future loadData() { final fut = service.getCell( gridId: gridCell.gridId, fieldId: gridCell.field.id, rowId: gridCell.rowId, ); - return fut.then((result) { - return result.fold((Cell data) => data.content, (err) { - Log.error(err); - return ""; - }); - }); - } -} - -class DateCellDataLoader extends IGridCellDataLoader { - final GridCell gridCell; - final IGridCellDataConfig _config; - DateCellDataLoader({ - required this.gridCell, - }) : _config = const GridCellDataConfig(reloadOnFieldChanged: true); - - @override - IGridCellDataConfig get config => _config; - - @override - Future loadData() { - final payload = CellIdentifierPayload.create() - ..gridId = gridCell.gridId - ..fieldId = gridCell.field.id - ..rowId = gridCell.rowId; - - return GridEventGetDateCellData(payload).send().then((result) { - return result.fold( - (data) => data, - (err) { - Log.error(err); + return fut.then( + (result) => result.fold((Cell cell) { + try { + return parser.parserData(cell.data); + } catch (e, s) { + Log.error('$parser parser cellData failed, $e'); + Log.error('Stack trace \n $s'); return null; - }, - ); - }); + } + }, (err) { + Log.error(err); + return null; + }), + ); } } @@ -113,3 +94,30 @@ class SelectOptionCellDataLoader extends IGridCellDataLoader const GridCellDataConfig(reloadOnFieldChanged: true); } + +class StringCellDataParser implements ICellDataParser { + @override + String? parserData(List data) { + return utf8.decode(data); + } +} + +class DateCellDataParser implements ICellDataParser { + @override + DateCellData? parserData(List data) { + if (data.isEmpty) { + return null; + } + return DateCellData.fromBuffer(data); + } +} + +class SelectOptionCellDataParser implements ICellDataParser { + @override + SelectOptionCellData? parserData(List data) { + if (data.isEmpty) { + return null; + } + return SelectOptionCellData.fromBuffer(data); + } +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart index 056b65e556..0b6b1fd4ab 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart @@ -21,7 +21,6 @@ class SelectOptionCellBloc extends Bloc options, List selectedOptions, ) = _DidReceiveOptions; } @@ -66,7 +63,6 @@ class SelectOptionCellEvent with _$SelectOptionCellEvent { @freezed class SelectOptionCellState with _$SelectOptionCellState { const factory SelectOptionCellState({ - required List options, required List selectedOptions, }) = _SelectOptionCellState; @@ -74,7 +70,6 @@ class SelectOptionCellState with _$SelectOptionCellState { final data = context.getCellData(); return SelectOptionCellState( - options: data?.options ?? [], selectedOptions: data?.selectOptions ?? [], ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart index a1e1315682..d5065aa6fa 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart @@ -24,6 +24,7 @@ class SelectOptionCellEditorBloc extends Bloc filter, List allOptions) { final List options = List.from(allOptions); Option createOption = filter; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart index 2123802966..c29a14d696 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart @@ -13,19 +13,16 @@ class TextCellBloc extends Bloc { }) : super(TextCellState.initial(cellContext)) { on( (event, emit) async { - await event.map( - initial: (_InitialCell value) async { + await event.when( + initial: () async { _startListening(); }, - updateText: (_UpdateText value) { - cellContext.saveCellData(value.text); - emit(state.copyWith(content: value.text)); + updateText: (text) { + cellContext.saveCellData(text); + emit(state.copyWith(content: text)); }, - didReceiveCellData: (_DidReceiveCellData value) { - emit(state.copyWith(content: value.cellData.cell?.content ?? "")); - }, - didReceiveCellUpdate: (_DidReceiveCellUpdate value) { - emit(state.copyWith(content: value.cellContent)); + didReceiveCellUpdate: (content) { + emit(state.copyWith(content: content)); }, ); }, @@ -56,7 +53,6 @@ class TextCellBloc extends Bloc { @freezed class TextCellEvent with _$TextCellEvent { const factory TextCellEvent.initial() = _InitialCell; - const factory TextCellEvent.didReceiveCellData(GridCell cellData) = _DidReceiveCellData; const factory TextCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate; const factory TextCellEvent.updateText(String text) = _UpdateText; } diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart index bf4964054e..0149f63b3a 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart @@ -392,20 +392,3 @@ class GridEventUpdateDateCell { } } -class GridEventGetDateCellData { - CellIdentifierPayload request; - GridEventGetDateCellData(this.request); - - Future> send() { - final request = FFIRequest.create() - ..event = GridEvent.GetDateCellData.toString() - ..payload = requestToBytes(this.request); - - return Dispatch.asyncRequest(request) - .then((bytesResult) => bytesResult.fold( - (okBytes) => left(DateCellData.fromBuffer(okBytes)), - (errBytes) => right(FlowyError.fromBuffer(errBytes)), - )); - } -} - diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart index 19a1aca54d..87f9036c88 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart @@ -1366,24 +1366,19 @@ class GridBlock extends $pb.GeneratedMessage { class Cell extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Cell', createEmptyInstance: create) ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId') - ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'content') - ..a<$core.List<$core.int>>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY) ..hasRequiredFields = false ; Cell._() : super(); factory Cell({ $core.String? fieldId, - $core.String? content, $core.List<$core.int>? data, }) { final _result = create(); if (fieldId != null) { _result.fieldId = fieldId; } - if (content != null) { - _result.content = content; - } if (data != null) { _result.data = data; } @@ -1420,22 +1415,13 @@ class Cell extends $pb.GeneratedMessage { void clearFieldId() => clearField(1); @$pb.TagNumber(2) - $core.String get content => $_getSZ(1); + $core.List<$core.int> get data => $_getN(1); @$pb.TagNumber(2) - set content($core.String v) { $_setString(1, v); } + set data($core.List<$core.int> v) { $_setBytes(1, v); } @$pb.TagNumber(2) - $core.bool hasContent() => $_has(1); + $core.bool hasData() => $_has(1); @$pb.TagNumber(2) - void clearContent() => clearField(2); - - @$pb.TagNumber(3) - $core.List<$core.int> get data => $_getN(2); - @$pb.TagNumber(3) - set data($core.List<$core.int> v) { $_setBytes(2, v); } - @$pb.TagNumber(3) - $core.bool hasData() => $_has(2); - @$pb.TagNumber(3) - void clearData() => clearField(3); + void clearData() => clearField(2); } class RepeatedCell extends $pb.GeneratedMessage { diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart index c203258374..68c1569c9c 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart @@ -289,13 +289,12 @@ const Cell$json = const { '1': 'Cell', '2': const [ const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'}, - const {'1': 'content', '3': 2, '4': 1, '5': 9, '10': 'content'}, - const {'1': 'data', '3': 3, '4': 1, '5': 12, '10': 'data'}, + const {'1': 'data', '3': 2, '4': 1, '5': 12, '10': 'data'}, ], }; /// Descriptor for `Cell`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List cellDescriptor = $convert.base64Decode('CgRDZWxsEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEhgKB2NvbnRlbnQYAiABKAlSB2NvbnRlbnQSEgoEZGF0YRgDIAEoDFIEZGF0YQ=='); +final $typed_data.Uint8List cellDescriptor = $convert.base64Decode('CgRDZWxsEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEhIKBGRhdGEYAiABKAxSBGRhdGE='); @$core.Deprecated('Use repeatedCellDescriptor instead') const RepeatedCell$json = const { '1': 'RepeatedCell', diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart index 341dcf8d6a..863162cc3e 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart @@ -33,7 +33,6 @@ class GridEvent extends $pb.ProtobufEnum { static const GridEvent UpdateCell = GridEvent._(71, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateCell'); static const GridEvent UpdateSelectOptionCell = GridEvent._(72, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateSelectOptionCell'); static const GridEvent UpdateDateCell = GridEvent._(80, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateDateCell'); - static const GridEvent GetDateCellData = GridEvent._(90, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetDateCellData'); static const $core.List values = [ GetGridData, @@ -59,7 +58,6 @@ class GridEvent extends $pb.ProtobufEnum { UpdateCell, UpdateSelectOptionCell, UpdateDateCell, - GetDateCellData, ]; static final $core.Map<$core.int, GridEvent> _byValue = $pb.ProtobufEnum.initByValue(values); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart index 58a712a4da..08de369a01 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart @@ -35,9 +35,8 @@ const GridEvent$json = const { const {'1': 'UpdateCell', '2': 71}, const {'1': 'UpdateSelectOptionCell', '2': 72}, const {'1': 'UpdateDateCell', '2': 80}, - const {'1': 'GetDateCellData', '2': 90}, ], }; /// Descriptor for `GridEvent`. Decode as a `google.protobuf.EnumDescriptorProto`. -final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIZChVVcGRhdGVGaWVsZFR5cGVPcHRpb24QDBIPCgtJbnNlcnRGaWVsZBANEg8KC0RlbGV0ZUZpZWxkEA4SEQoNU3dpdGNoVG9GaWVsZBAUEhIKDkR1cGxpY2F0ZUZpZWxkEBUSDAoITW92ZUl0ZW0QFhIWChJHZXRGaWVsZFR5cGVPcHRpb24QFxIZChVDcmVhdGVGaWVsZFR5cGVPcHRpb24QGBITCg9OZXdTZWxlY3RPcHRpb24QHhIbChdHZXRTZWxlY3RPcHRpb25DZWxsRGF0YRAfEhYKElVwZGF0ZVNlbGVjdE9wdGlvbhAgEg0KCUNyZWF0ZVJvdxAyEgoKBkdldFJvdxAzEg0KCURlbGV0ZVJvdxA0EhAKDER1cGxpY2F0ZVJvdxA1EgsKB0dldENlbGwQRhIOCgpVcGRhdGVDZWxsEEcSGgoWVXBkYXRlU2VsZWN0T3B0aW9uQ2VsbBBIEhIKDlVwZGF0ZURhdGVDZWxsEFASEwoPR2V0RGF0ZUNlbGxEYXRhEFo='); +final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIZChVVcGRhdGVGaWVsZFR5cGVPcHRpb24QDBIPCgtJbnNlcnRGaWVsZBANEg8KC0RlbGV0ZUZpZWxkEA4SEQoNU3dpdGNoVG9GaWVsZBAUEhIKDkR1cGxpY2F0ZUZpZWxkEBUSDAoITW92ZUl0ZW0QFhIWChJHZXRGaWVsZFR5cGVPcHRpb24QFxIZChVDcmVhdGVGaWVsZFR5cGVPcHRpb24QGBITCg9OZXdTZWxlY3RPcHRpb24QHhIbChdHZXRTZWxlY3RPcHRpb25DZWxsRGF0YRAfEhYKElVwZGF0ZVNlbGVjdE9wdGlvbhAgEg0KCUNyZWF0ZVJvdxAyEgoKBkdldFJvdxAzEg0KCURlbGV0ZVJvdxA0EhAKDER1cGxpY2F0ZVJvdxA1EgsKB0dldENlbGwQRhIOCgpVcGRhdGVDZWxsEEcSGgoWVXBkYXRlU2VsZWN0T3B0aW9uQ2VsbBBIEhIKDlVwZGF0ZURhdGVDZWxsEFA='); diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index cf5c095ee9..edc5082ba8 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -263,27 +263,6 @@ pub(crate) async fn update_cell_handler( Ok(()) } -#[tracing::instrument(level = "trace", skip(data, manager), err)] -pub(crate) async fn get_date_cell_data_handler( - data: Data, - manager: AppData>, -) -> DataResult { - let params: CellIdentifier = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; - match editor.get_field_meta(¶ms.field_id).await { - None => { - tracing::error!("Can't find the corresponding field with id: {}", params.field_id); - data_result(DateCellData::default()) - } - Some(field_meta) => { - let cell_meta = editor.get_cell_meta(¶ms.row_id, ¶ms.field_id).await?; - let type_option = DateTypeOption::from(&field_meta); - let date_cell_data = type_option.make_date_cell_data(&cell_meta)?; - data_result(date_cell_data) - } - } -} - #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn new_select_option_handler( data: Data, diff --git a/frontend/rust-lib/flowy-grid/src/event_map.rs b/frontend/rust-lib/flowy-grid/src/event_map.rs index c740eee6d8..48e5fd9b6c 100644 --- a/frontend/rust-lib/flowy-grid/src/event_map.rs +++ b/frontend/rust-lib/flowy-grid/src/event_map.rs @@ -29,7 +29,6 @@ pub fn create(grid_manager: Arc) -> Module { // Cell .event(GridEvent::GetCell, get_cell_handler) .event(GridEvent::UpdateCell, update_cell_handler) - .event(GridEvent::GetDateCellData, get_date_cell_data_handler) // SelectOption .event(GridEvent::NewSelectOption, new_select_option_handler) .event(GridEvent::UpdateSelectOption, update_select_option_handler) @@ -112,7 +111,4 @@ pub enum GridEvent { #[event(input = "DateChangesetPayload")] UpdateDateCell = 80, - - #[event(input = "CellIdentifierPayload", output = "DateCellData")] - GetDateCellData = 90, } diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs index 6762254df7..73eaff920f 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs @@ -48,7 +48,6 @@ pub enum GridEvent { UpdateCell = 71, UpdateSelectOptionCell = 72, UpdateDateCell = 80, - GetDateCellData = 90, } impl ::protobuf::ProtobufEnum for GridEvent { @@ -81,7 +80,6 @@ impl ::protobuf::ProtobufEnum for GridEvent { 71 => ::std::option::Option::Some(GridEvent::UpdateCell), 72 => ::std::option::Option::Some(GridEvent::UpdateSelectOptionCell), 80 => ::std::option::Option::Some(GridEvent::UpdateDateCell), - 90 => ::std::option::Option::Some(GridEvent::GetDateCellData), _ => ::std::option::Option::None } } @@ -111,7 +109,6 @@ impl ::protobuf::ProtobufEnum for GridEvent { GridEvent::UpdateCell, GridEvent::UpdateSelectOptionCell, GridEvent::UpdateDateCell, - GridEvent::GetDateCellData, ]; values } @@ -140,7 +137,7 @@ impl ::protobuf::reflect::ProtobufValue for GridEvent { } static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x0fevent_map.proto*\xdc\x03\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\ + \n\x0fevent_map.proto*\xc7\x03\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\ \0\x12\x11\n\rGetGridBlocks\x10\x01\x12\r\n\tGetFields\x10\n\x12\x0f\n\ \x0bUpdateField\x10\x0b\x12\x19\n\x15UpdateFieldTypeOption\x10\x0c\x12\ \x0f\n\x0bInsertField\x10\r\x12\x0f\n\x0bDeleteField\x10\x0e\x12\x11\n\r\ @@ -151,7 +148,7 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x10\x20\x12\r\n\tCreateRow\x102\x12\n\n\x06GetRow\x103\x12\r\n\tDeleteR\ ow\x104\x12\x10\n\x0cDuplicateRow\x105\x12\x0b\n\x07GetCell\x10F\x12\x0e\ \n\nUpdateCell\x10G\x12\x1a\n\x16UpdateSelectOptionCell\x10H\x12\x12\n\ - \x0eUpdateDateCell\x10P\x12\x13\n\x0fGetDateCellData\x10Zb\x06proto3\ + \x0eUpdateDateCell\x10Pb\x06proto3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto b/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto index 10e98a2934..623bf4b7da 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto +++ b/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto @@ -24,5 +24,4 @@ enum GridEvent { UpdateCell = 71; UpdateSelectOptionCell = 72; UpdateDateCell = 80; - GetDateCellData = 90; } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs index 90bb53cea6..c8d14b6b03 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs @@ -58,7 +58,7 @@ impl CellDataOperation for CheckboxTypeOption { let encoded_data = encoded_data.into(); if encoded_data == YES || encoded_data == NO { - return Ok(DecodedCellData::from_content(encoded_data)); + return Ok(DecodedCellData::new(encoded_data)); } Ok(DecodedCellData::default()) @@ -104,37 +104,37 @@ mod tests { let field_meta = FieldBuilder::from_field_type(&FieldType::Checkbox).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, + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), YES ); let data = apply_cell_data_changeset("1", None, &field_meta).unwrap(); assert_eq!( - decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content, + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), YES ); let data = apply_cell_data_changeset("yes", None, &field_meta).unwrap(); assert_eq!( - decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content, + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), YES ); let data = apply_cell_data_changeset("false", None, &field_meta).unwrap(); assert_eq!( - decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content, + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), NO ); let data = apply_cell_data_changeset("no", None, &field_meta).unwrap(); assert_eq!( - decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content, + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), NO ); let data = apply_cell_data_changeset("12", None, &field_meta).unwrap(); assert_eq!( - decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content, + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), NO ); } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs index 5ecca97105..25a16a0515 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs @@ -1,9 +1,7 @@ use crate::entities::{CellIdentifier, CellIdentifierPayload}; use crate::impl_type_option; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{ - CellContentChangeset, CellDataOperation, DecodedCellData, EncodedCellData, TypeOptionCellData, -}; +use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, EncodedCellData}; use bytes::Bytes; use chrono::format::strftime::StrftimeItems; use chrono::NaiveDateTime; @@ -79,32 +77,12 @@ impl DateTypeOption { } } - pub fn make_date_cell_data(&self, cell_meta: &Option) -> FlowyResult { - if cell_meta.is_none() { - return Ok(DateCellData::default()); - } - - let json = &cell_meta.as_ref().unwrap().data; - let result = TypeOptionCellData::from_str(json); - if result.is_err() { - return Ok(DateCellData::default()); - } - - let serde_cell_data = DateCellDataSerde::from_str(&result.unwrap().data)?; - let date = self.decode_cell_data_from_timestamp(&serde_cell_data).content; - let time = serde_cell_data.time.unwrap_or_else(|| "".to_owned()); - let timestamp = serde_cell_data.timestamp; - - Ok(DateCellData { date, time, timestamp }) - } - - fn decode_cell_data_from_timestamp(&self, serde_cell_data: &DateCellDataSerde) -> DecodedCellData { + fn date_desc_from_timestamp(&self, serde_cell_data: &DateCellDataSerde) -> String { if serde_cell_data.timestamp == 0 { - return DecodedCellData::default(); + return "".to_owned(); } - let cell_content = self.today_desc_from_timestamp(serde_cell_data.timestamp, &serde_cell_data.time); - DecodedCellData::new(serde_cell_data.timestamp.to_string(), cell_content) + self.today_desc_from_timestamp(serde_cell_data.timestamp, &serde_cell_data.time) } fn timestamp_from_utc_with_time( @@ -156,7 +134,11 @@ impl CellDataOperation, DateCellDataSerde> fo } let encoded_data = encoded_data.into().try_into_inner()?; - Ok(self.decode_cell_data_from_timestamp(&encoded_data)) + let date = self.date_desc_from_timestamp(&encoded_data); + let time = encoded_data.time.unwrap_or_else(|| "".to_owned()); + let timestamp = encoded_data.timestamp; + + DecodedCellData::try_from_bytes(DateCellData { date, time, timestamp }) } fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result @@ -417,20 +399,27 @@ impl std::convert::From for CellContentChangeset { #[cfg(test)] mod tests { use crate::services::field::FieldBuilder; - use crate::services::field::{DateCellContentChangeset, 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::field::{ + DateCellContentChangeset, DateCellData, DateCellDataSerde, DateFormat, DateTypeOption, TimeFormat, }; + use crate::services::row::{CellDataOperation, EncodedCellData}; use flowy_grid_data_model::entities::{FieldMeta, FieldType}; use strum::IntoEnumIterator; #[test] fn date_description_invalid_input_test() { - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - let data = apply_cell_data_changeset("1e", None, &field_meta).unwrap(); - assert_eq!( - decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content, - "".to_owned() + let type_option = DateTypeOption::default(); + let field_type = FieldType::DateTime; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some("1e".to_string()), + time: Some("23:00".to_owned()), + }, + &field_type, + &field_meta, + "", ); } @@ -442,40 +431,16 @@ mod tests { type_option.date_format = date_format; match date_format { DateFormat::Friendly => { - assert_eq!( - "Mar 14,2022".to_owned(), - type_option - .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta) - .unwrap() - .content - ); + assert_decode_timestamp(1647251762, &type_option, &field_meta, "Mar 14,2022"); } DateFormat::US => { - assert_eq!( - "2022/03/14".to_owned(), - type_option - .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta) - .unwrap() - .content - ); + assert_decode_timestamp(1647251762, &type_option, &field_meta, "2022/03/14"); } DateFormat::ISO => { - assert_eq!( - "2022-03-14".to_owned(), - type_option - .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta) - .unwrap() - .content - ); + assert_decode_timestamp(1647251762, &type_option, &field_meta, "2022-03-14"); } DateFormat::Local => { - assert_eq!( - "2022/03/14".to_owned(), - type_option - .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta) - .unwrap() - .content - ); + assert_decode_timestamp(1647251762, &type_option, &field_meta, "2022/03/14"); } } } @@ -483,43 +448,6 @@ mod tests { #[test] fn date_description_time_format_test() { - let mut type_option = DateTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - for time_format in TimeFormat::iter() { - type_option.time_format = time_format; - match time_format { - TimeFormat::TwentyFourHour => { - assert_eq!( - "Mar 14,2022".to_owned(), - type_option.today_desc_from_timestamp(1647251762, &None) - ); - assert_eq!( - "Mar 14,2022".to_owned(), - type_option - .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta) - .unwrap() - .content - ); - } - TimeFormat::TwelveHour => { - assert_eq!( - "Mar 14,2022".to_owned(), - type_option.today_desc_from_timestamp(1647251762, &None) - ); - assert_eq!( - "Mar 14,2022".to_owned(), - type_option - .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta) - .unwrap() - .content - ); - } - } - } - } - - #[test] - fn date_description_time_format_test2() { let mut type_option = DateTypeOption::default(); let field_type = FieldType::DateTime; let field_meta = FieldBuilder::from_field_type(&field_type).build(); @@ -529,7 +457,7 @@ mod tests { type_option.include_time = true; match time_format { TimeFormat::TwentyFourHour => { - assert_result( + assert_changeset_result( &type_option, DateCellContentChangeset { date: Some(1653609600.to_string()), @@ -539,7 +467,7 @@ mod tests { &field_meta, "May 27,2022 00:00", ); - assert_result( + assert_changeset_result( &type_option, DateCellContentChangeset { date: Some(1653609600.to_string()), @@ -551,7 +479,7 @@ mod tests { ); } TimeFormat::TwelveHour => { - assert_result( + assert_changeset_result( &type_option, DateCellContentChangeset { date: Some(1653609600.to_string()), @@ -562,7 +490,7 @@ mod tests { "May 27,2022 12:00 AM", ); - assert_result( + assert_changeset_result( &type_option, DateCellContentChangeset { date: Some(1653609600.to_string()), @@ -573,7 +501,7 @@ mod tests { "May 27,2022", ); - assert_result( + assert_changeset_result( &type_option, DateCellContentChangeset { date: Some(1653609600.to_string()), @@ -595,7 +523,7 @@ mod tests { let field_meta = FieldBuilder::from_field_type(&field_type).build(); let date_timestamp = "1653609600".to_owned(); - assert_result( + assert_changeset_result( &type_option, DateCellContentChangeset { date: Some(date_timestamp.clone()), @@ -607,7 +535,7 @@ mod tests { ); type_option.include_time = true; - assert_result( + assert_changeset_result( &type_option, DateCellContentChangeset { date: Some(date_timestamp.clone()), @@ -618,7 +546,7 @@ mod tests { "May 27,2022 00:00", ); - assert_result( + assert_changeset_result( &type_option, DateCellContentChangeset { date: Some(date_timestamp.clone()), @@ -630,7 +558,7 @@ mod tests { ); type_option.time_format = TimeFormat::TwelveHour; - assert_result( + assert_changeset_result( &type_option, DateCellContentChangeset { date: Some(date_timestamp), @@ -670,22 +598,39 @@ mod tests { type_option.apply_changeset("he", None).unwrap(); } - fn data(s: i64) -> String { - serde_json::to_string(&DateCellDataSerde::from_timestamp(s, None)).unwrap() - } - - fn assert_result( + fn assert_changeset_result( type_option: &DateTypeOption, changeset: DateCellContentChangeset, - field_type: &FieldType, + _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) + assert_eq!( + expected.to_owned(), + decode_cell_data(encoded_data, type_option, field_meta) + ); + } + + fn assert_decode_timestamp(timestamp: i64, type_option: &DateTypeOption, field_meta: &FieldMeta, expected: &str) { + let serde_json = DateCellDataSerde { timestamp, time: None }.to_string(); + + assert_eq!( + expected.to_owned(), + decode_cell_data(serde_json, type_option, field_meta) + ); + } + + fn decode_cell_data>>( + encoded_data: T, + type_option: &DateTypeOption, + field_meta: &FieldMeta, + ) -> String { + type_option + .decode_cell_data(encoded_data, &FieldType::DateTime, &field_meta) .unwrap() - .content; - assert_eq!(expected.to_owned(), content); + .parse::() + .unwrap() + .date } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs index d500bbcc39..b889c1c2c0 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs @@ -94,22 +94,22 @@ impl CellDataOperation for NumberTypeOption { match self.format { NumberFormat::Number => { if let Ok(v) = cell_data.parse::() { - return Ok(DecodedCellData::from_content(v.to_string())); + return Ok(DecodedCellData::new(v.to_string())); } if let Ok(v) = cell_data.parse::() { - return Ok(DecodedCellData::from_content(v.to_string())); + return Ok(DecodedCellData::new(v.to_string())); } Ok(DecodedCellData::default()) } NumberFormat::Percent => { let content = cell_data.parse::().map_or(String::new(), |v| v.to_string()); - Ok(DecodedCellData::from_content(content)) + Ok(DecodedCellData::new(content)) } _ => { let content = self.money_from_str(&cell_data); - Ok(DecodedCellData::from_content(content)) + Ok(DecodedCellData::new(content)) } } } @@ -738,7 +738,7 @@ mod tests { type_option .decode_cell_data(data(cell_data), field_type, field_meta) .unwrap() - .content, + .to_string(), expected_str.to_owned() ); } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs index c73dd1802f..1e857e9fd3 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs @@ -109,16 +109,18 @@ impl CellDataOperation for SingleSelectTypeOption { return Ok(DecodedCellData::default()); } - let cell_data = encoded_data.into(); - 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(), - Some(option) => DecodedCellData::from_content(option.name.clone()), - }; - Ok(data) - } else { - Ok(DecodedCellData::default()) + let encoded_data = encoded_data.into(); + let mut cell_data = SelectOptionCellData { + options: self.options.clone(), + select_options: vec![], + }; + if let Some(option_id) = select_option_ids(encoded_data).first() { + if let Some(option) = self.options.iter().find(|option| &option.id == option_id) { + cell_data.select_options.push(option.clone()); + } } + + DecodedCellData::try_from_bytes(cell_data) } fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result @@ -204,17 +206,21 @@ impl CellDataOperation for MultiSelectTypeOption { if !decoded_field_type.is_select_option() { return Ok(DecodedCellData::default()); } - let cell_data = encoded_data.into(); - let option_ids = select_option_ids(cell_data); - let content = self - .options - .iter() - .filter(|option| option_ids.contains(&option.id)) - .map(|option| option.name.clone()) - .collect::>() - .join(SELECTION_IDS_SEPARATOR); - Ok(DecodedCellData::from_content(content)) + tracing::info!("😁{}", self.options.len()); + + let encoded_data = encoded_data.into(); + let select_options = select_option_ids(encoded_data) + .into_iter() + .flat_map(|option_id| self.options.iter().find(|option| option.id == option_id).cloned()) + .collect::>(); + + let cell_data = SelectOptionCellData { + options: self.options.clone(), + select_options, + }; + + DecodedCellData::try_from_bytes(cell_data) } fn apply_changeset(&self, changeset: T, cell_meta: Option) -> Result @@ -280,7 +286,7 @@ fn select_option_ids(data: String) -> Vec { .collect::>() } -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, ProtoBuf)] pub struct SelectOption { #[pb(index = 1)] pub id: String, @@ -446,7 +452,7 @@ pub struct SelectOptionCellData { pub select_options: Vec, } -#[derive(ProtoBuf_Enum, Serialize, Deserialize, Debug, Clone)] +#[derive(ProtoBuf_Enum, PartialEq, Eq, Serialize, Deserialize, Debug, Clone)] #[repr(u8)] pub enum SelectOptionColor { Purple = 0, @@ -502,9 +508,10 @@ mod tests { use crate::services::field::FieldBuilder; use crate::services::field::{ MultiSelectTypeOption, MultiSelectTypeOptionBuilder, SelectOption, SelectOptionCellContentChangeset, - SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR, + SelectOptionCellData, SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR, }; use crate::services::row::CellDataOperation; + use flowy_grid_data_model::entities::FieldMeta; #[test] fn single_select_test() { @@ -526,47 +533,24 @@ mod tests { let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR); let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option - .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) - .unwrap() - .content, - google_option.name, - ); + assert_single_select_options(cell_data, &type_option, &field_meta, vec![google_option.clone()]); let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option - .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) - .unwrap() - .content, - google_option.name, - ); + assert_single_select_options(cell_data, &type_option, &field_meta, vec![google_option.clone()]); // Invalid option id let cell_data = type_option .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) .unwrap(); - assert_eq!( - type_option - .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) - .unwrap() - .content, - "", - ); + assert_single_select_options(cell_data, &type_option, &field_meta, vec![]); // Invalid option id let cell_data = type_option .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None) .unwrap(); - assert_eq!( - type_option - .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) - .unwrap() - .content, - "", - ); + + assert_single_select_options(cell_data, &type_option, &field_meta, vec![]); // Invalid changeset assert!(type_option.apply_changeset("123", None).is_err()); @@ -592,49 +576,64 @@ mod tests { let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - 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), + assert_multi_select_options( + cell_data, + &type_option, + &field_meta, + vec![google_option.clone(), facebook_option.clone()], ); let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option - .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) - .unwrap() - .content, - google_option.name, - ); + assert_multi_select_options(cell_data, &type_option, &field_meta, vec![google_option.clone()]); // Invalid option id let cell_data = type_option .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) .unwrap(); - assert_eq!( - type_option - .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) - .unwrap() - .content, - "", - ); + assert_multi_select_options(cell_data, &type_option, &field_meta, vec![]); // Invalid option id let cell_data = type_option .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None) .unwrap(); - assert_eq!( - type_option - .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) - .unwrap() - .content, - "", - ); + assert_multi_select_options(cell_data, &type_option, &field_meta, vec![]); // Invalid changeset assert!(type_option.apply_changeset("123", None).is_err()); } + + fn assert_multi_select_options( + cell_data: String, + type_option: &MultiSelectTypeOption, + field_meta: &FieldMeta, + expected: Vec, + ) { + assert_eq!( + expected, + type_option + .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) + .unwrap() + .parse::() + .unwrap() + .select_options, + ); + } + + fn assert_single_select_options( + cell_data: String, + type_option: &SingleSelectTypeOption, + field_meta: &FieldMeta, + expected: Vec, + ) { + assert_eq!( + expected, + type_option + .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) + .unwrap() + .parse::() + .unwrap() + .select_options, + ); + } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs index 32d45a3e29..94c55e3664 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs @@ -49,7 +49,7 @@ impl CellDataOperation for RichTextTypeOption { decode_cell_data(encoded_data, decoded_field_type, decoded_field_type, field_meta) } else { let cell_data = encoded_data.into(); - Ok(DecodedCellData::from_content(cell_data)) + Ok(DecodedCellData::new(cell_data)) } } @@ -85,22 +85,26 @@ mod tests { type_option .decode_cell_data(json, &field_type, &date_time_field_meta) .unwrap() - .content, + .parse::() + .unwrap() + .date, "Mar 14,2022".to_owned() ); // Single select let done_option = SelectOption::new("Done"); let done_option_id = done_option.id.clone(); - let single_select = SingleSelectTypeOptionBuilder::default().option(done_option); + let single_select = SingleSelectTypeOptionBuilder::default().option(done_option.clone()); let single_select_field_meta = FieldBuilder::new(single_select).build(); assert_eq!( type_option .decode_cell_data(done_option_id, &FieldType::SingleSelect, &single_select_field_meta) .unwrap() - .content, - "Done".to_owned() + .parse::() + .unwrap() + .select_options, + vec![done_option], ); // Multiple select @@ -109,8 +113,8 @@ mod tests { let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); let cell_data_changeset = SelectOptionCellContentChangeset::from_insert(&ids).to_str(); let multi_select = MultiSelectTypeOptionBuilder::default() - .option(google_option) - .option(facebook_option); + .option(google_option.clone()) + .option(facebook_option.clone()); let multi_select_field_meta = FieldBuilder::new(multi_select).build(); let multi_type_option = MultiSelectTypeOption::from(&multi_select_field_meta); let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap(); @@ -118,8 +122,10 @@ mod tests { type_option .decode_cell_data(cell_data, &FieldType::MultiSelect, &multi_select_field_meta) .unwrap() - .content, - "Google,Facebook".to_owned() + .parse::() + .unwrap() + .select_options, + vec![google_option, facebook_option] ); //Number @@ -129,7 +135,7 @@ mod tests { type_option .decode_cell_data("18443".to_owned(), &FieldType::Number, &number_field_meta) .unwrap() - .content, + .to_string(), "$18,443".to_owned() ); } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs index f92e4ac8d2..0ecc9198dd 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs @@ -1,5 +1,6 @@ use crate::services::field::*; -use flowy_error::{ErrorCode, FlowyError, FlowyResult}; +use bytes::Bytes; +use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{CellMeta, FieldMeta, FieldType}; use serde::{Deserialize, Serialize}; use std::fmt::Formatter; @@ -145,8 +146,15 @@ pub fn decode_cell_data_from_type_option_cell_data 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() + match decode_cell_data(encoded_data, &s_field_type, field_type, field_meta) { + Ok(cell_data) => cell_data, + Err(e) => { + tracing::error!("Decode cell data failed, {:?}", e); + DecodedCellData::default() + } + } } else { + tracing::error!("Decode type option data failed"); DecodedCellData::default() } } @@ -158,6 +166,7 @@ pub fn decode_cell_data>( field_meta: &FieldMeta, ) -> FlowyResult { let encoded_data = encoded_data.into(); + tracing::info!("😁{:?}", field_meta.type_options); let get_cell_data = || { let data = match t_field_type { FieldType::RichText => field_meta @@ -183,13 +192,7 @@ pub fn decode_cell_data>( }; match get_cell_data() { - Some(Ok(data)) => { - tracing::Span::current().record( - "content", - &format!("{:?}: {}", field_meta.field_type, data.content).as_str(), - ); - Ok(data) - } + Some(Ok(data)) => Ok(data), Some(Err(err)) => { tracing::error!("{:?}", err); Ok(DecodedCellData::default()) @@ -227,23 +230,39 @@ where #[derive(Default)] pub struct DecodedCellData { pub data: Vec, - pub content: String, } impl DecodedCellData { - pub fn from_content(content: String) -> Self { + pub fn new>(data: T) -> Self { Self { - data: content.as_bytes().to_vec(), - content, + data: data.as_ref().to_vec(), } } - pub fn new>(data: T, content: String) -> Self { - let data = data.as_ref().to_vec(); - Self { data, content } + pub fn try_from_bytes>(bytes: T) -> FlowyResult + where + >::Error: std::fmt::Debug, + { + let bytes = bytes.try_into().map_err(internal_error)?; + Ok(Self { data: bytes.to_vec() }) } - pub fn split(self) -> (Vec, String) { - (self.data, self.content) + pub fn parse<'a, T: TryFrom<&'a [u8]>>(&'a self) -> FlowyResult + where + >::Error: std::fmt::Debug, + { + T::try_from(self.data.as_ref()).map_err(internal_error) + } +} + +impl ToString for DecodedCellData { + fn to_string(&self) -> String { + match String::from_utf8(self.data.clone()) { + Ok(s) => s, + Err(e) => { + tracing::error!("DecodedCellData to string failed: {:?}", e); + "".to_string() + } + } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs index af4af0e491..865058ffb6 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs @@ -31,17 +31,15 @@ pub fn make_cell_by_field_id( cell_meta: CellMeta, ) -> Option<(String, Cell)> { let field_meta = field_map.get(&field_id)?; - 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 data = decode_cell_data_from_type_option_cell_data(cell_meta.data, field_meta, &field_meta.field_type).data; + let cell = Cell::new(&field_id, data); Some((field_id, cell)) } pub fn make_cell(field_id: &str, field_meta: &FieldMeta, row_meta: &RowMeta) -> Option { let cell_meta = row_meta.cells.get(field_id)?.clone(); - 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)) + let data = decode_cell_data_from_type_option_cell_data(cell_meta.data, field_meta, &field_meta.field_type).data; + Some(Cell::new(field_id, data)) } pub(crate) fn make_row_orders_from_row_metas(row_metas: &[Arc]) -> Vec { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs index f4549cb726..afe89d4623 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs @@ -2,7 +2,7 @@ use crate::grid::script::EditorScript::*; use crate::grid::script::*; use chrono::NaiveDateTime; use flowy_grid::services::field::{ - DateCellContentChangeset, MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset, + DateCellContentChangeset, DateCellData, MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset, SingleSelectTypeOption, SELECTION_IDS_SEPARATOR, }; use flowy_grid::services::row::{decode_cell_data_from_type_option_cell_data, CreateRowMetaBuilder}; @@ -292,8 +292,9 @@ async fn grid_row_add_date_cell_test() { let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone(); assert_eq!( decode_cell_data_from_type_option_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type) - .split() - .1, + .parse::() + .unwrap() + .date, "2022/03/16", ); let scripts = vec![CreateRow { context }]; diff --git a/shared-lib/flowy-derive/src/proto_buf/deserialize.rs b/shared-lib/flowy-derive/src/proto_buf/deserialize.rs index e09f7ea847..f0325db3b0 100644 --- a/shared-lib/flowy-derive/src/proto_buf/deserialize.rs +++ b/shared-lib/flowy-derive/src/proto_buf/deserialize.rs @@ -30,6 +30,14 @@ pub fn make_de_token_steam(ctxt: &Ctxt, ast: &ASTContainer) -> Option for #struct_ident { + type Error = ::protobuf::ProtobufError; + fn try_from(bytes: &[u8]) -> Result { + let pb: crate::protobuf::#pb_ty = ::protobuf::Message::parse_from_bytes(bytes)?; + #struct_ident::try_from(pb) + } + } + impl std::convert::TryFrom for #struct_ident { type Error = ::protobuf::ProtobufError; fn try_from(mut pb: crate::protobuf::#pb_ty) -> Result { diff --git a/shared-lib/flowy-grid-data-model/src/entities/grid.rs b/shared-lib/flowy-grid-data-model/src/entities/grid.rs index dab20b772c..944f52a81f 100644 --- a/shared-lib/flowy-grid-data-model/src/entities/grid.rs +++ b/shared-lib/flowy-grid-data-model/src/entities/grid.rs @@ -484,17 +484,13 @@ pub struct Cell { pub field_id: String, #[pb(index = 2)] - pub content: String, - - #[pb(index = 3)] pub data: Vec, } impl Cell { - pub fn new(field_id: &str, content: String, data: Vec) -> Self { + pub fn new(field_id: &str, data: Vec) -> Self { Self { field_id: field_id.to_owned(), - content, data, } } @@ -502,7 +498,6 @@ impl Cell { pub fn empty(field_id: &str) -> Self { Self { field_id: field_id.to_owned(), - content: "".to_string(), data: vec![], } } diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs index e5f9102fd5..a063df9d04 100644 --- a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs +++ b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs @@ -4743,7 +4743,6 @@ impl ::protobuf::reflect::ProtobufValue for GridBlock { pub struct Cell { // message fields pub field_id: ::std::string::String, - pub content: ::std::string::String, pub data: ::std::vec::Vec, // special fields pub unknown_fields: ::protobuf::UnknownFields, @@ -4787,33 +4786,7 @@ impl Cell { ::std::mem::replace(&mut self.field_id, ::std::string::String::new()) } - // string content = 2; - - - pub fn get_content(&self) -> &str { - &self.content - } - pub fn clear_content(&mut self) { - self.content.clear(); - } - - // Param is passed by value, moved - pub fn set_content(&mut self, v: ::std::string::String) { - self.content = v; - } - - // Mutable pointer to the field. - // If field is not initialized, it is initialized with default value first. - pub fn mut_content(&mut self) -> &mut ::std::string::String { - &mut self.content - } - - // Take field - pub fn take_content(&mut self) -> ::std::string::String { - ::std::mem::replace(&mut self.content, ::std::string::String::new()) - } - - // bytes data = 3; + // bytes data = 2; pub fn get_data(&self) -> &[u8] { @@ -4853,9 +4826,6 @@ impl ::protobuf::Message for Cell { ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?; }, 2 => { - ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.content)?; - }, - 3 => { ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.data)?; }, _ => { @@ -4873,11 +4843,8 @@ impl ::protobuf::Message for Cell { if !self.field_id.is_empty() { my_size += ::protobuf::rt::string_size(1, &self.field_id); } - if !self.content.is_empty() { - my_size += ::protobuf::rt::string_size(2, &self.content); - } if !self.data.is_empty() { - my_size += ::protobuf::rt::bytes_size(3, &self.data); + my_size += ::protobuf::rt::bytes_size(2, &self.data); } my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); self.cached_size.set(my_size); @@ -4888,11 +4855,8 @@ impl ::protobuf::Message for Cell { if !self.field_id.is_empty() { os.write_string(1, &self.field_id)?; } - if !self.content.is_empty() { - os.write_string(2, &self.content)?; - } if !self.data.is_empty() { - os.write_bytes(3, &self.data)?; + os.write_bytes(2, &self.data)?; } os.write_unknown_fields(self.get_unknown_fields())?; ::std::result::Result::Ok(()) @@ -4937,11 +4901,6 @@ impl ::protobuf::Message for Cell { |m: &Cell| { &m.field_id }, |m: &mut Cell| { &mut m.field_id }, )); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( - "content", - |m: &Cell| { &m.content }, - |m: &mut Cell| { &mut m.content }, - )); fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>( "data", |m: &Cell| { &m.data }, @@ -4964,7 +4923,6 @@ impl ::protobuf::Message for Cell { impl ::protobuf::Clear for Cell { fn clear(&mut self) { self.field_id.clear(); - self.content.clear(); self.data.clear(); self.unknown_fields.clear(); } @@ -8340,50 +8298,49 @@ static file_descriptor_proto_data: &'static [u8] = b"\ derR\x0bdeletedRows\x123\n\x0cupdated_rows\x18\x04\x20\x03(\x0b2\x10.Upd\ atedRowOrderR\x0bupdatedRows\"E\n\tGridBlock\x12\x0e\n\x02id\x18\x01\x20\ \x01(\tR\x02id\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trow\ - Orders\"O\n\x04Cell\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\ - \x12\x18\n\x07content\x18\x02\x20\x01(\tR\x07content\x12\x12\n\x04data\ - \x18\x03\x20\x01(\x0cR\x04data\"+\n\x0cRepeatedCell\x12\x1b\n\x05items\ - \x18\x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11CreateGridPayload\x12\ - \x12\n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\ - \x05value\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlockId\x12\x14\n\x05\ - value\x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\n\ - \x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_row_id\x18\ - \x02\x20\x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\"\xb6\x01\ - \n\x12InsertFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gri\ - dId\x12\x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05field\x12(\n\ - \x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\x12&\n\x0es\ - tart_field_id\x18\x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\x15one_of_st\ - art_field_id\"|\n\x1cUpdateFieldTypeOptionPayload\x12\x17\n\x07grid_id\ - \x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\ - \x07fieldId\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOpti\ - onData\"d\n\x11QueryFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\t\ - R\x06gridId\x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\x13.RepeatedFie\ - ldOrderR\x0bfieldOrders\"e\n\x16QueryGridBlocksPayload\x12\x17\n\x07grid\ - _id\x18\x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orders\x18\x02\x20\x03\ - (\x0b2\x0f.GridBlockOrderR\x0bblockOrders\"\xa8\x03\n\x15FieldChangesetP\ - ayload\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x17\n\ - \x07grid_id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04name\x18\x03\x20\ - \x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\x04\x20\x01(\tH\x01R\x04desc\ - \x12+\n\nfield_type\x18\x05\x20\x01(\x0e2\n.FieldTypeH\x02R\tfieldType\ - \x12\x18\n\x06frozen\x18\x06\x20\x01(\x08H\x03R\x06frozen\x12\x20\n\nvis\ - ibility\x18\x07\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05width\x18\ - \x08\x20\x01(\x05H\x05R\x05width\x12*\n\x10type_option_data\x18\t\x20\ - \x01(\x0cH\x06R\x0etypeOptionDataB\r\n\x0bone_of_nameB\r\n\x0bone_of_des\ - cB\x13\n\x11one_of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_of_vis\ - ibilityB\x0e\n\x0cone_of_widthB\x19\n\x17one_of_type_option_data\"\x9c\ - \x01\n\x0fMoveItemPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gr\ - idId\x12\x17\n\x07item_id\x18\x02\x20\x01(\tR\x06itemId\x12\x1d\n\nfrom_\ - index\x18\x03\x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_index\x18\x04\x20\ - \x01(\x05R\x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\x0e2\r.MoveItemTy\ - peR\x02ty\"\xb3\x01\n\rCellChangeset\x12\x17\n\x07grid_id\x18\x01\x20\ - \x01(\tR\x06gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\ - \x19\n\x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x126\n\x16cell_content\ - _changeset\x18\x04\x20\x01(\tH\0R\x14cellContentChangesetB\x1f\n\x1done_\ - of_cell_content_changeset**\n\x0cMoveItemType\x12\r\n\tMoveField\x10\0\ - \x12\x0b\n\x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\x08RichText\x10\0\ - \x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\x0cSi\ - ngleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08Checkbo\ - x\x10\x05b\x06proto3\ + Orders\"5\n\x04Cell\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\ + \x12\x12\n\x04data\x18\x02\x20\x01(\x0cR\x04data\"+\n\x0cRepeatedCell\ + \x12\x1b\n\x05items\x18\x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11Cre\ + ateGridPayload\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06\ + GridId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlock\ + Id\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayl\ + oad\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_\ + row_id\x18\x02\x20\x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\ + \"\xb6\x01\n\x12InsertFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\ + \tR\x06gridId\x12\x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05fie\ + ld\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\ + \x12&\n\x0estart_field_id\x18\x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\ + \x15one_of_start_field_id\"|\n\x1cUpdateFieldTypeOptionPayload\x12\x17\n\ + \x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_id\x18\x02\ + \x20\x01(\tR\x07fieldId\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\ + \x0etypeOptionData\"d\n\x11QueryFieldPayload\x12\x17\n\x07grid_id\x18\ + \x01\x20\x01(\tR\x06gridId\x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\ + \x13.RepeatedFieldOrderR\x0bfieldOrders\"e\n\x16QueryGridBlocksPayload\ + \x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orde\ + rs\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrders\"\xa8\x03\n\ + \x15FieldChangesetPayload\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07f\ + ieldId\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04n\ + ame\x18\x03\x20\x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\x04\x20\x01(\tH\ + \x01R\x04desc\x12+\n\nfield_type\x18\x05\x20\x01(\x0e2\n.FieldTypeH\x02R\ + \tfieldType\x12\x18\n\x06frozen\x18\x06\x20\x01(\x08H\x03R\x06frozen\x12\ + \x20\n\nvisibility\x18\x07\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05w\ + idth\x18\x08\x20\x01(\x05H\x05R\x05width\x12*\n\x10type_option_data\x18\ + \t\x20\x01(\x0cH\x06R\x0etypeOptionDataB\r\n\x0bone_of_nameB\r\n\x0bone_\ + of_descB\x13\n\x11one_of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_\ + of_visibilityB\x0e\n\x0cone_of_widthB\x19\n\x17one_of_type_option_data\"\ + \x9c\x01\n\x0fMoveItemPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\ + \x06gridId\x12\x17\n\x07item_id\x18\x02\x20\x01(\tR\x06itemId\x12\x1d\n\ + \nfrom_index\x18\x03\x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_index\x18\ + \x04\x20\x01(\x05R\x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\x0e2\r.Mo\ + veItemTypeR\x02ty\"\xb3\x01\n\rCellChangeset\x12\x17\n\x07grid_id\x18\ + \x01\x20\x01(\tR\x06gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05ro\ + wId\x12\x19\n\x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x126\n\x16cell_\ + content_changeset\x18\x04\x20\x01(\tH\0R\x14cellContentChangesetB\x1f\n\ + \x1done_of_cell_content_changeset**\n\x0cMoveItemType\x12\r\n\tMoveField\ + \x10\0\x12\x0b\n\x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\x08RichText\ + \x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\ + \x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08C\ + heckbox\x10\x05b\x06proto3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto index d762e65e70..18abbd8e54 100644 --- a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto +++ b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto @@ -95,8 +95,7 @@ message GridBlock { } message Cell { string field_id = 1; - string content = 2; - bytes data = 3; + bytes data = 2; } message RepeatedCell { repeated Cell items = 1; From b8582b9b6845293bb9d77677e7465ac7cfd7b848 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sat, 28 May 2022 15:55:43 +0800 Subject: [PATCH 21/23] chore: set loaded data in SelectOptionCellEditorBloc --- .../cell/cell_service/context_builder.dart | 4 +- .../grid/cell/select_option_editor_bloc.dart | 42 ++++++++++++------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart index c6f71a2e32..aad8137f2e 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart @@ -163,9 +163,9 @@ class _GridCellContext extends Equatable { _cellDataNotifier.removeListener(fn); } - T? getCellData() { + T? getCellData({bool loadIfNoCache = true}) { final data = cellCache.get(_cacheKey); - if (data == null) { + if (data == null && loadIfNoCache) { _loadData(); } return data; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart index d5065aa6fa..87eabdf759 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; @@ -13,11 +14,14 @@ part 'select_option_editor_bloc.freezed.dart'; class SelectOptionCellEditorBloc extends Bloc { final SelectOptionService _selectOptionService; final GridSelectOptionCellContext cellContext; + late final GridFieldsListener _fieldListener; void Function()? _onCellChangedFn; + Timer? _delayOperation; SelectOptionCellEditorBloc({ required this.cellContext, }) : _selectOptionService = SelectOptionService(gridCell: cellContext.gridCell), + _fieldListener = GridFieldsListener(gridId: cellContext.gridId), super(SelectOptionEditorState.initial(cellContext)) { on( (event, emit) async { @@ -64,6 +68,8 @@ class SelectOptionCellEditorBloc extends Bloc add(SelectOptionEditorEvent.didReceiveOptions(data.options, data.selectOptions)), (err) { Log.error(err); return null; }, ); }); - } + }); } _MakeOptionResult _makeOptions(Option filter, List allOptions) { @@ -156,13 +160,21 @@ class SelectOptionCellEditorBloc extends Bloc Log.error(err), + ); + }); } } @@ -189,7 +201,7 @@ class SelectOptionEditorState with _$SelectOptionEditorState { }) = _SelectOptionEditorState; factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) { - final data = context.getCellData(); + final data = context.getCellData(loadIfNoCache: false); return SelectOptionEditorState( options: data?.options ?? [], allOptions: data?.options ?? [], From ae0f71b5ee8c1938c69dc7d659e2a56324dab188 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sun, 29 May 2022 10:56:34 +0800 Subject: [PATCH 22/23] chore: return URLCellData & parse url from cell content --- .../grid/cell/cell_service/cell_service.dart | 1 + .../cell/cell_service/context_builder.dart | 2 +- .../grid/cell/cell_service/data_loader.dart | 21 ++- .../application/grid/cell/url_cell_bloc.dart | 11 +- frontend/rust-lib/Cargo.lock | 2 +- frontend/rust-lib/flowy-grid/Cargo.toml | 2 +- .../field/type_options/date_type_option.rs | 2 +- .../type_options/selection_type_option.rs | 10 +- .../field/type_options/url_type_option.rs | 125 ++++++++++++++---- 9 files changed, 124 insertions(+), 52 deletions(-) diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart index dea8e87712..68f8eada78 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart @@ -10,6 +10,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart index 709df1cb97..e1e7b949f1 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart @@ -3,7 +3,7 @@ part of 'cell_service.dart'; typedef GridCellContext = _GridCellContext; typedef GridSelectOptionCellContext = _GridCellContext; typedef GridDateCellContext = _GridCellContext; -typedef GridURLCellContext = _GridCellContext; +typedef GridURLCellContext = _GridCellContext; class GridCellContextBuilder { final GridCellCache _cellCache; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart index c223347715..92caedc4e9 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart @@ -58,7 +58,11 @@ class GridCellDataLoader extends IGridCellDataLoader { return fut.then( (result) => result.fold((Cell cell) { try { - return parser.parserData(cell.data); + if (cell.data.isEmpty) { + return null; + } else { + return parser.parserData(cell.data); + } } catch (e, s) { Log.error('$parser parser cellData failed, $e'); Log.error('Stack trace \n $s'); @@ -105,9 +109,6 @@ class StringCellDataParser implements ICellDataParser { class DateCellDataParser implements ICellDataParser { @override DateCellData? parserData(List data) { - if (data.isEmpty) { - return null; - } return DateCellData.fromBuffer(data); } } @@ -115,19 +116,13 @@ class DateCellDataParser implements ICellDataParser { class SelectOptionCellDataParser implements ICellDataParser { @override SelectOptionCellData? parserData(List data) { - if (data.isEmpty) { - return null; - } return SelectOptionCellData.fromBuffer(data); } } -class URLCellDataParser implements ICellDataParser { +class URLCellDataParser implements ICellDataParser { @override - Cell? parserData(List data) { - if (data.isEmpty) { - return null; - } - return Cell.fromBuffer(data); + URLCellData? parserData(List data) { + return URLCellData.fromBuffer(data); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart index 8b4245540a..04284b16f7 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart @@ -1,4 +1,4 @@ -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell; +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -23,7 +23,7 @@ class URLCellBloc extends Bloc { emit(state.copyWith(content: text)); }, didReceiveCellUpdate: (cellData) { - emit(state.copyWith(content: cellData.content)); + emit(state.copyWith(content: cellData.content, url: cellData.url)); }, ); }, @@ -54,7 +54,7 @@ class URLCellBloc extends Bloc { @freezed class URLCellEvent with _$URLCellEvent { const factory URLCellEvent.initial() = _InitialCell; - const factory URLCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate; + const factory URLCellEvent.didReceiveCellUpdate(URLCellData cell) = _DidReceiveCellUpdate; const factory URLCellEvent.updateText(String text) = _UpdateText; } @@ -67,6 +67,9 @@ class URLCellState with _$URLCellState { factory URLCellState.initial(GridURLCellContext context) { final cellData = context.getCellData(); - return URLCellState(content: cellData?.content ?? "", url: ""); + return URLCellState( + content: cellData?.content ?? "", + url: cellData?.url ?? "", + ); } } diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 23fe8ed62b..59ca0e3dec 100755 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -928,6 +928,7 @@ dependencies = [ "dart-notify", "dashmap", "diesel", + "fancy-regex", "flowy-database", "flowy-derive", "flowy-error", @@ -953,7 +954,6 @@ dependencies = [ "strum_macros", "tokio", "tracing", - "url", ] [[package]] diff --git a/frontend/rust-lib/flowy-grid/Cargo.toml b/frontend/rust-lib/flowy-grid/Cargo.toml index 2bde4a3f36..a4677d727f 100644 --- a/frontend/rust-lib/flowy-grid/Cargo.toml +++ b/frontend/rust-lib/flowy-grid/Cargo.toml @@ -35,7 +35,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = {version = "1.0"} serde_repr = "0.1" indexmap = {version = "1.8.1", features = ["serde"]} -url = { version = "2"} +fancy-regex = "0.10.0" [dev-dependencies] flowy-test = { path = "../flowy-test" } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs index 25a16a0515..c96166272a 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs @@ -627,7 +627,7 @@ mod tests { field_meta: &FieldMeta, ) -> String { type_option - .decode_cell_data(encoded_data, &FieldType::DateTime, &field_meta) + .decode_cell_data(encoded_data, &FieldType::DateTime, field_meta) .unwrap() .parse::() .unwrap() diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs index 1e857e9fd3..26efd34acc 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs @@ -537,7 +537,7 @@ mod tests { let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_single_select_options(cell_data, &type_option, &field_meta, vec![google_option.clone()]); + assert_single_select_options(cell_data, &type_option, &field_meta, vec![google_option]); // Invalid option id let cell_data = type_option @@ -580,12 +580,12 @@ mod tests { cell_data, &type_option, &field_meta, - vec![google_option.clone(), facebook_option.clone()], + vec![google_option.clone(), facebook_option], ); let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_multi_select_options(cell_data, &type_option, &field_meta, vec![google_option.clone()]); + assert_multi_select_options(cell_data, &type_option, &field_meta, vec![google_option]); // Invalid option id let cell_data = type_option @@ -612,7 +612,7 @@ mod tests { assert_eq!( expected, type_option - .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) + .decode_cell_data(cell_data, &field_meta.field_type, field_meta) .unwrap() .parse::() .unwrap() @@ -629,7 +629,7 @@ mod tests { assert_eq!( expected, type_option - .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) + .decode_cell_data(cell_data, &field_meta.field_type, field_meta) .unwrap() .parse::() .unwrap() diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs index 5ca29ee87d..6cef992fbd 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs @@ -1,14 +1,16 @@ use crate::impl_type_option; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData}; +use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, EncodedCellData}; use bytes::Bytes; +use fancy_regex::Regex; use flowy_derive::ProtoBuf; -use flowy_error::{FlowyError, FlowyResult}; +use flowy_error::{internal_error, FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{ CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, }; - +use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; +use std::str::FromStr; #[derive(Default)] pub struct URLTypeOptionBuilder(URLTypeOption); @@ -32,7 +34,7 @@ pub struct URLTypeOption { } impl_type_option!(URLTypeOption, FieldType::URL); -impl CellDataOperation for URLTypeOption { +impl CellDataOperation, String> for URLTypeOption { fn decode_cell_data( &self, encoded_data: T, @@ -40,14 +42,13 @@ impl CellDataOperation for URLTypeOption { _field_meta: &FieldMeta, ) -> FlowyResult where - T: Into, + T: Into>, { if !decoded_field_type.is_url() { return Ok(DecodedCellData::default()); } - - let cell_data = encoded_data.into(); - Ok(DecodedCellData::from_content(cell_data)) + let cell_data = encoded_data.into().try_into_inner()?; + DecodedCellData::try_from_bytes(cell_data) } fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result @@ -55,7 +56,16 @@ impl CellDataOperation for URLTypeOption { C: Into, { let changeset = changeset.into(); - Ok(changeset.to_string()) + let mut cell_data = URLCellData { + url: "".to_string(), + content: changeset.to_string(), + }; + + if let Ok(Some(m)) = URL_REGEX.find(&changeset) { + cell_data.url = m.as_str().to_string(); + } + + cell_data.to_json() } } @@ -68,34 +78,97 @@ pub struct URLCellData { pub content: String, } +impl URLCellData { + pub fn new(s: &str) -> Self { + Self { + url: "".to_string(), + content: s.to_string(), + } + } + + fn to_json(&self) -> FlowyResult { + serde_json::to_string(self).map_err(internal_error) + } +} + +impl FromStr for URLCellData { + type Err = FlowyError; + + fn from_str(s: &str) -> Result { + serde_json::from_str::(s).map_err(internal_error) + } +} + +lazy_static! { + static ref URL_REGEX: Regex = Regex::new( + "[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)" + ) + .unwrap(); +} + #[cfg(test)] mod tests { use crate::services::field::FieldBuilder; - use crate::services::field::URLTypeOption; - use crate::services::row::CellDataOperation; + use crate::services::field::{URLCellData, URLTypeOption}; + use crate::services::row::{CellDataOperation, EncodedCellData}; use flowy_grid_data_model::entities::{FieldMeta, FieldType}; #[test] - fn url_type_option_format_test() { + fn url_type_option_test_no_url() { let type_option = URLTypeOption::default(); let field_type = FieldType::URL; let field_meta = FieldBuilder::from_field_type(&field_type).build(); - assert_equal(&type_option, "123", "123", &field_type, &field_meta); + assert_changeset(&type_option, "123", &field_type, &field_meta, "123", ""); } - fn assert_equal( - type_option: &URLTypeOption, - cell_data: &str, - expected_str: &str, - field_type: &FieldType, - field_meta: &FieldMeta, - ) { - assert_eq!( - type_option - .decode_cell_data(cell_data, field_type, field_meta) - .unwrap() - .content, - expected_str.to_owned() + #[test] + fn url_type_option_test_contains_url() { + let type_option = URLTypeOption::default(); + let field_type = FieldType::URL; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + assert_changeset( + &type_option, + "AppFlowy website - https://www.appflowy.io", + &field_type, + &field_meta, + "AppFlowy website - https://www.appflowy.io", + "https://www.appflowy.io", + ); + + assert_changeset( + &type_option, + "AppFlowy website appflowy.io", + &field_type, + &field_meta, + "AppFlowy website appflowy.io", + "appflowy.io", ); } + + fn assert_changeset( + type_option: &URLTypeOption, + cell_data: &str, + field_type: &FieldType, + field_meta: &FieldMeta, + expected: &str, + expected_url: &str, + ) { + let encoded_data = type_option.apply_changeset(cell_data, None).unwrap(); + let decode_cell_data = decode_cell_data(encoded_data, type_option, field_meta, field_type); + assert_eq!(expected.to_owned(), decode_cell_data.content); + assert_eq!(expected_url.to_owned(), decode_cell_data.url); + } + + fn decode_cell_data>>( + encoded_data: T, + type_option: &URLTypeOption, + field_meta: &FieldMeta, + field_type: &FieldType, + ) -> URLCellData { + type_option + .decode_cell_data(encoded_data, field_type, field_meta) + .unwrap() + .parse::() + .unwrap() + } } From 4a9627b31d02fda3812513fbc17ceda7afe83015 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sun, 29 May 2022 21:47:38 +0800 Subject: [PATCH 23/23] chore: support open url with https scheme --- .../cell/cell_service/context_builder.dart | 31 +++-- .../grid/cell/checkbox_cell_bloc.dart | 2 +- .../application/grid/cell/date_cal_bloc.dart | 43 +++--- .../application/grid/cell/date_cell_bloc.dart | 10 +- .../grid/cell/number_cell_bloc.dart | 4 +- .../grid/cell/select_option_cell_bloc.dart | 2 +- .../application/grid/cell/text_cell_bloc.dart | 2 +- .../application/grid/cell/url_cell_bloc.dart | 12 +- .../grid/cell/url_cell_editor_bloc.dart | 73 ++++++++++ .../grid/src/widgets/cell/cell_builder.dart | 9 +- .../grid/src/widgets/cell/url_cell.dart | 127 ----------------- .../widgets/cell/url_cell/cell_editor.dart | 96 +++++++++++++ .../src/widgets/cell/url_cell/url_cell.dart | 131 ++++++++++++++++++ .../grid/src/widgets/row/grid_row.dart | 5 +- .../grid/src/widgets/row/row_detail.dart | 2 +- frontend/rust-lib/Cargo.lock | 1 + frontend/rust-lib/flowy-grid/Cargo.toml | 1 + .../type_options/selection_type_option.rs | 2 - .../field/type_options/url_type_option.rs | 18 ++- .../src/services/row/cell_data_operation.rs | 1 - 20 files changed, 388 insertions(+), 184 deletions(-) create mode 100644 frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart delete mode 100644 frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell.dart create mode 100644 frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart create mode 100644 frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart index e1e7b949f1..e4141c3e16 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart @@ -108,7 +108,8 @@ class _GridCellContext extends Equatable { late final ValueNotifier _cellDataNotifier; bool isListening = false; VoidCallback? _onFieldChangedFn; - Timer? _delayOperation; + Timer? _loadDataOperation; + Timer? _saveDataOperation; _GridCellContext({ required this.gridCell, @@ -138,7 +139,7 @@ class _GridCellContext extends Equatable { FieldType get fieldType => gridCell.field.fieldType; - VoidCallback? startListening({required void Function(T) onCellChanged}) { + VoidCallback? startListening({required void Function(T?) onCellChanged}) { if (isListening) { Log.error("Already started. It seems like you should call clone first"); return null; @@ -162,7 +163,7 @@ class _GridCellContext extends Equatable { } onCellChangedFn() { - onCellChanged(_cellDataNotifier.value as T); + onCellChanged(_cellDataNotifier.value); if (cellDataLoader.config.reloadOnCellChanged) { _loadData(); @@ -189,13 +190,26 @@ class _GridCellContext extends Equatable { return _fieldService.getFieldTypeOptionData(fieldType: fieldType); } - Future> saveCellData(D data) { - return cellDataPersistence.save(data); + void saveCellData(D data, {bool deduplicate = false, void Function(Option)? resultCallback}) async { + if (deduplicate) { + _loadDataOperation?.cancel(); + _loadDataOperation = Timer(const Duration(milliseconds: 300), () async { + final result = await cellDataPersistence.save(data); + if (resultCallback != null) { + resultCallback(result); + } + }); + } else { + final result = await cellDataPersistence.save(data); + if (resultCallback != null) { + resultCallback(result); + } + } } void _loadData() { - _delayOperation?.cancel(); - _delayOperation = Timer(const Duration(milliseconds: 10), () { + _loadDataOperation?.cancel(); + _loadDataOperation = Timer(const Duration(milliseconds: 10), () { cellDataLoader.loadData().then((data) { _cellDataNotifier.value = data; cellCache.insert(GridCellCacheData(key: _cacheKey, object: data)); @@ -204,7 +218,8 @@ class _GridCellContext extends Equatable { } void dispose() { - _delayOperation?.cancel(); + _loadDataOperation?.cancel(); + _saveDataOperation?.cancel(); if (_onFieldChangedFn != null) { cellCache.removeFieldListener(_cacheKey, _onFieldChangedFn!); diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart index 514ae2ce4e..b8e2b13bbc 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart @@ -58,7 +58,7 @@ class CheckboxCellBloc extends Bloc { class CheckboxCellEvent with _$CheckboxCellEvent { const factory CheckboxCellEvent.initial() = _Initial; const factory CheckboxCellEvent.select() = _Selected; - const factory CheckboxCellEvent.didReceiveCellUpdate(String cellData) = _DidReceiveCellUpdate; + const factory CheckboxCellEvent.didReceiveCellUpdate(String? cellData) = _DidReceiveCellUpdate; } @freezed diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart index ff001eaa75..15f18707f8 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart @@ -37,7 +37,7 @@ class DateCalBloc extends Bloc { setFocusedDay: (focusedDay) { emit(state.copyWith(focusedDay: focusedDay)); }, - didReceiveCellUpdate: (DateCellData cellData) { + didReceiveCellUpdate: (DateCellData? cellData) { final dateData = dateDataFromCellData(cellData); final time = dateData.foldRight("", (dateData, previous) => dateData.time); emit(state.copyWith(dateData: dateData, time: time)); @@ -83,25 +83,26 @@ class DateCalBloc extends Bloc { return; } - final result = await cellContext.saveCellData(newDateData); - result.fold( - () => emit(state.copyWith( - dateData: Some(newDateData), - timeFormatError: none(), - )), - (err) { - switch (ErrorCode.valueOf(err.code)!) { - case ErrorCode.InvalidDateTimeFormat: - emit(state.copyWith( - dateData: Some(newDateData), - timeFormatError: Some(timeFormatPrompt(err)), - )); - break; - default: - Log.error(err); - } - }, - ); + cellContext.saveCellData(newDateData, resultCallback: (result) { + result.fold( + () => emit(state.copyWith( + dateData: Some(newDateData), + timeFormatError: none(), + )), + (err) { + switch (ErrorCode.valueOf(err.code)!) { + case ErrorCode.InvalidDateTimeFormat: + emit(state.copyWith( + dateData: Some(newDateData), + timeFormatError: Some(timeFormatPrompt(err)), + )); + break; + default: + Log.error(err); + } + }, + ); + }); } String timeFormatPrompt(FlowyError error) { @@ -183,7 +184,7 @@ class DateCalEvent with _$DateCalEvent { const factory DateCalEvent.setDateFormat(DateFormat dateFormat) = _DateFormat; const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime; const factory DateCalEvent.setTime(String time) = _Time; - const factory DateCalEvent.didReceiveCellUpdate(DateCellData data) = _DidReceiveCellUpdate; + const factory DateCalEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate; } @freezed diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart index 4b068dd289..b06a3d60b3 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart @@ -16,7 +16,13 @@ class DateCellBloc extends Bloc { (event, emit) async { event.when( initial: () => _startListening(), - didReceiveCellUpdate: (DateCellData value) => emit(state.copyWith(data: Some(value))), + didReceiveCellUpdate: (DateCellData? cellData) { + if (cellData != null) { + emit(state.copyWith(data: Some(cellData))); + } else { + emit(state.copyWith(data: none())); + } + }, didReceiveFieldUpdate: (Field value) => emit(state.copyWith(field: value)), ); }, @@ -47,7 +53,7 @@ class DateCellBloc extends Bloc { @freezed class DateCellEvent with _$DateCellEvent { const factory DateCellEvent.initial() = _InitialCell; - const factory DateCellEvent.didReceiveCellUpdate(DateCellData data) = _DidReceiveCellUpdate; + const factory DateCellEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate; const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate; } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart index ceb89bc201..8157f6a3f2 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart @@ -19,7 +19,7 @@ class NumberCellBloc extends Bloc { _startListening(); }, didReceiveCellUpdate: (_DidReceiveCellUpdate value) { - emit(state.copyWith(content: value.cellContent)); + emit(state.copyWith(content: value.cellContent ?? "")); }, updateCell: (_UpdateCell value) async { await _updateCellValue(value, emit); @@ -58,7 +58,7 @@ class NumberCellBloc extends Bloc { class NumberCellEvent with _$NumberCellEvent { const factory NumberCellEvent.initial() = _Initial; const factory NumberCellEvent.updateCell(String text) = _UpdateCell; - const factory NumberCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate; + const factory NumberCellEvent.didReceiveCellUpdate(String? cellContent) = _DidReceiveCellUpdate; } @freezed diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart index 0b6b1fd4ab..c6393e4831 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart @@ -44,7 +44,7 @@ class SelectOptionCellBloc extends Bloc { _onCellChangedFn = cellContext.startListening( onCellChanged: ((cellContent) { if (!isClosed) { - add(TextCellEvent.didReceiveCellUpdate(cellContent)); + add(TextCellEvent.didReceiveCellUpdate(cellContent ?? "")); } }), ); diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart index 04284b16f7..609c625001 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart @@ -18,12 +18,11 @@ class URLCellBloc extends Bloc { initial: () { _startListening(); }, - updateText: (text) { - cellContext.saveCellData(text); - emit(state.copyWith(content: text)); - }, didReceiveCellUpdate: (cellData) { - emit(state.copyWith(content: cellData.content, url: cellData.url)); + emit(state.copyWith( + content: cellData?.content ?? "", + url: cellData?.url ?? "", + )); }, ); }, @@ -54,8 +53,7 @@ class URLCellBloc extends Bloc { @freezed class URLCellEvent with _$URLCellEvent { const factory URLCellEvent.initial() = _InitialCell; - const factory URLCellEvent.didReceiveCellUpdate(URLCellData cell) = _DidReceiveCellUpdate; - const factory URLCellEvent.updateText(String text) = _UpdateText; + const factory URLCellEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate; } @freezed diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart new file mode 100644 index 0000000000..6e4990943f --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart @@ -0,0 +1,73 @@ +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'dart:async'; +import 'cell_service/cell_service.dart'; + +part 'url_cell_editor_bloc.freezed.dart'; + +class URLCellEditorBloc extends Bloc { + final GridURLCellContext cellContext; + void Function()? _onCellChangedFn; + URLCellEditorBloc({ + required this.cellContext, + }) : super(URLCellEditorState.initial(cellContext)) { + on( + (event, emit) async { + event.when( + initial: () { + _startListening(); + }, + updateText: (text) { + cellContext.saveCellData(text, deduplicate: true); + emit(state.copyWith(content: text)); + }, + didReceiveCellUpdate: (cellData) { + emit(state.copyWith(content: cellData?.content ?? "")); + }, + ); + }, + ); + } + + @override + Future close() async { + if (_onCellChangedFn != null) { + cellContext.removeListener(_onCellChangedFn!); + _onCellChangedFn = null; + } + cellContext.dispose(); + return super.close(); + } + + void _startListening() { + _onCellChangedFn = cellContext.startListening( + onCellChanged: ((cellData) { + if (!isClosed) { + add(URLCellEditorEvent.didReceiveCellUpdate(cellData)); + } + }), + ); + } +} + +@freezed +class URLCellEditorEvent with _$URLCellEditorEvent { + const factory URLCellEditorEvent.initial() = _InitialCell; + const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate; + const factory URLCellEditorEvent.updateText(String text) = _UpdateText; +} + +@freezed +class URLCellEditorState with _$URLCellEditorState { + const factory URLCellEditorState({ + required String content, + }) = _URLCellEditorState; + + factory URLCellEditorState.initial(GridURLCellContext context) { + final cellData = context.getCellData(); + return URLCellEditorState( + content: cellData?.content ?? "", + ); + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart index 8ae79541a3..f8189e7f02 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart @@ -13,7 +13,7 @@ import 'date_cell/date_cell.dart'; import 'number_cell.dart'; import 'select_option_cell/select_option_cell.dart'; import 'text_cell.dart'; -import 'url_cell.dart'; +import 'url_cell/url_cell.dart'; GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) { final key = ValueKey(gridCell.cellId()); @@ -35,7 +35,6 @@ GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, { return GridTextCell(cellContextBuilder: cellContextBuilder, style: style, key: key); case FieldType.URL: return GridURLCell(cellContextBuilder: cellContextBuilder, style: style, key: key); - } throw UnimplementedError; } @@ -151,7 +150,7 @@ class CellContainer extends StatelessWidget { }); if (expander != null) { - container = _CellEnterRegion(child: container, expander: expander!); + container = CellEnterRegion(child: container, expander: expander!); } return GestureDetector( @@ -181,10 +180,10 @@ class CellContainer extends StatelessWidget { } } -class _CellEnterRegion extends StatelessWidget { +class CellEnterRegion extends StatelessWidget { final Widget child; final Widget expander; - const _CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key); + const CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell.dart deleted file mode 100644 index 328d0e7180..0000000000 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'dart:async'; -import 'package:app_flowy/workspace/application/grid/cell/url_cell_bloc.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:app_flowy/workspace/application/grid/prelude.dart'; -import 'cell_builder.dart'; - -class GridURLCellStyle extends GridCellStyle { - String? placeholder; - - GridURLCellStyle({ - this.placeholder, - }); -} - -class GridURLCell extends StatefulWidget with GridCellWidget { - final GridCellContextBuilder cellContextBuilder; - late final GridURLCellStyle? cellStyle; - GridURLCell({ - required this.cellContextBuilder, - GridCellStyle? style, - Key? key, - }) : super(key: key) { - if (style != null) { - cellStyle = (style as GridURLCellStyle); - } else { - cellStyle = null; - } - } - - @override - State createState() => _GridURLCellState(); -} - -class _GridURLCellState extends State { - late URLCellBloc _cellBloc; - late TextEditingController _controller; - late CellSingleFocusNode _focusNode; - Timer? _delayOperation; - - @override - void initState() { - final cellContext = widget.cellContextBuilder.build() as GridURLCellContext; - _cellBloc = URLCellBloc(cellContext: cellContext); - _cellBloc.add(const URLCellEvent.initial()); - _controller = TextEditingController(text: _cellBloc.state.content); - _focusNode = CellSingleFocusNode(); - - _listenFocusNode(); - _listenRequestFocus(context); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return BlocProvider.value( - value: _cellBloc, - child: BlocListener( - listener: (context, state) { - if (_controller.text != state.content) { - _controller.text = state.content; - } - }, - child: TextField( - controller: _controller, - focusNode: _focusNode, - onChanged: (value) => focusChanged(), - onEditingComplete: () => _focusNode.unfocus(), - maxLines: null, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - decoration: InputDecoration( - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - hintText: widget.cellStyle?.placeholder, - isDense: true, - ), - ), - ), - ); - } - - @override - Future dispose() async { - widget.requestFocus.removeAllListener(); - _delayOperation?.cancel(); - _cellBloc.close(); - _focusNode.removeSingleListener(); - _focusNode.dispose(); - - super.dispose(); - } - - @override - void didUpdateWidget(covariant GridURLCell oldWidget) { - if (oldWidget != widget) { - _listenFocusNode(); - } - super.didUpdateWidget(oldWidget); - } - - void _listenFocusNode() { - widget.onFocus.value = _focusNode.hasFocus; - _focusNode.setSingleListener(() { - widget.onFocus.value = _focusNode.hasFocus; - focusChanged(); - }); - } - - void _listenRequestFocus(BuildContext context) { - widget.requestFocus.addListener(() { - if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) { - FocusScope.of(context).requestFocus(_focusNode); - } - }); - } - - Future focusChanged() async { - if (mounted) { - _delayOperation?.cancel(); - _delayOperation = Timer(const Duration(milliseconds: 300), () { - if (_cellBloc.isClosed == false && _controller.text != _cellBloc.state.content) { - _cellBloc.add(URLCellEvent.updateText(_controller.text)); - } - }); - } - } -} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart new file mode 100644 index 0000000000..055a4947c8 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart @@ -0,0 +1,96 @@ +import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; +import 'package:app_flowy/workspace/application/grid/cell/url_cell_editor_bloc.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter_bloc/flutter_bloc.dart'; + +class URLCellEditor extends StatefulWidget { + final GridURLCellContext cellContext; + const URLCellEditor({required this.cellContext, Key? key}) : super(key: key); + + @override + State createState() => _URLCellEditorState(); + + static void show( + BuildContext context, + GridURLCellContext cellContext, + ) { + FlowyOverlay.of(context).remove(identifier()); + final editor = URLCellEditor( + cellContext: cellContext, + ); + + // + FlowyOverlay.of(context).insertWithAnchor( + widget: OverlayContainer( + child: SizedBox(width: 200, child: editor), + constraints: BoxConstraints.loose(const Size(300, 160)), + ), + identifier: URLCellEditor.identifier(), + anchorContext: context, + anchorDirection: AnchorDirection.bottomWithCenterAligned, + ); + } + + static String identifier() { + return (URLCellEditor).toString(); + } +} + +class _URLCellEditorState extends State { + late URLCellEditorBloc _cellBloc; + late TextEditingController _controller; + + @override + void initState() { + _cellBloc = URLCellEditorBloc(cellContext: widget.cellContext); + _cellBloc.add(const URLCellEditorEvent.initial()); + _controller = TextEditingController(text: _cellBloc.state.content); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: _cellBloc, + child: BlocListener( + listener: (context, state) { + if (_controller.text != state.content) { + _controller.text = state.content; + } + }, + child: TextField( + autofocus: true, + controller: _controller, + onChanged: (value) => focusChanged(), + maxLines: null, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + decoration: const InputDecoration( + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + hintText: "", + isDense: true, + ), + ), + ), + ); + } + + @override + Future dispose() async { + _cellBloc.close(); + + super.dispose(); + } + + Future focusChanged() async { + if (mounted) { + if (_cellBloc.isClosed == false && _controller.text != _cellBloc.state.content) { + _cellBloc.add(URLCellEditorEvent.updateText(_controller.text)); + } + } + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart new file mode 100644 index 0000000000..db0dcade79 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart @@ -0,0 +1,131 @@ +import 'dart:async'; +import 'package:app_flowy/workspace/application/grid/cell/url_cell_bloc.dart'; +import 'package:flowy_infra/image.dart'; +import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra_ui/style_widget/icon_button.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:app_flowy/workspace/application/grid/prelude.dart'; +import 'package:url_launcher/url_launcher.dart'; +import '../cell_builder.dart'; +import 'cell_editor.dart'; + +class GridURLCellStyle extends GridCellStyle { + String? placeholder; + + GridURLCellStyle({ + this.placeholder, + }); +} + +class GridURLCell extends StatefulWidget with GridCellWidget { + final GridCellContextBuilder cellContextBuilder; + late final GridURLCellStyle? cellStyle; + GridURLCell({ + required this.cellContextBuilder, + GridCellStyle? style, + Key? key, + }) : super(key: key) { + if (style != null) { + cellStyle = (style as GridURLCellStyle); + } else { + cellStyle = null; + } + } + + @override + State createState() => _GridURLCellState(); +} + +class _GridURLCellState extends State { + late URLCellBloc _cellBloc; + + @override + void initState() { + final cellContext = widget.cellContextBuilder.build() as GridURLCellContext; + _cellBloc = URLCellBloc(cellContext: cellContext); + _cellBloc.add(const URLCellEvent.initial()); + _listenRequestFocus(context); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return BlocProvider.value( + value: _cellBloc, + child: BlocBuilder( + builder: (context, state) { + final richText = RichText( + textAlign: TextAlign.left, + text: TextSpan( + text: state.content, + style: TextStyle( + color: theme.main2, + fontSize: 14, + decoration: TextDecoration.underline, + ), + recognizer: _tapGesture(context), + ), + ); + + return CellEnterRegion( + child: Align(alignment: Alignment.centerLeft, child: richText), + expander: _EditCellIndicator(onTap: () {}), + ); + }, + ), + ); + } + + @override + Future dispose() async { + widget.requestFocus.removeAllListener(); + _cellBloc.close(); + super.dispose(); + } + + TapGestureRecognizer _tapGesture(BuildContext context) { + final gesture = TapGestureRecognizer(); + gesture.onTap = () async { + final url = context.read().state.url; + await _openUrlOrEdit(url); + }; + return gesture; + } + + Future _openUrlOrEdit(String url) async { + final uri = Uri.parse(url); + if (url.isNotEmpty && await canLaunchUrl(uri)) { + await launchUrl(uri); + } else { + final cellContext = widget.cellContextBuilder.build() as GridURLCellContext; + URLCellEditor.show(context, cellContext); + } + } + + void _listenRequestFocus(BuildContext context) { + widget.requestFocus.addListener(() { + _openUrlOrEdit(_cellBloc.state.url); + }); + } +} + +class _EditCellIndicator extends StatelessWidget { + final VoidCallback onTap; + const _EditCellIndicator({required this.onTap, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return FlowyIconButton( + width: 26, + onPressed: onTap, + hoverColor: theme.hover, + radius: BorderRadius.circular(4), + iconPadding: const EdgeInsets.all(5), + icon: svgWidget("editor/edit", color: theme.iconColor), + ); + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart index 200a079d55..a643b58928 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart @@ -209,9 +209,10 @@ class _CellExpander extends StatelessWidget { return FittedBox( fit: BoxFit.contain, child: FlowyIconButton( + width: 26, onPressed: onExpand, - iconPadding: const EdgeInsets.fromLTRB(6, 6, 6, 6), - fillColor: theme.surface, + iconPadding: const EdgeInsets.all(5), + radius: BorderRadius.circular(4), icon: svgWidget("grid/expander", color: theme.main1), ), ); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart index 255553f3d2..0900039b1f 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart @@ -4,7 +4,7 @@ import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart'; import 'package:app_flowy/workspace/application/grid/row/row_service.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/url_cell.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart'; import 'package:flowy_infra/image.dart'; diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 59ca0e3dec..393a02ac73 100755 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -954,6 +954,7 @@ dependencies = [ "strum_macros", "tokio", "tracing", + "url", ] [[package]] diff --git a/frontend/rust-lib/flowy-grid/Cargo.toml b/frontend/rust-lib/flowy-grid/Cargo.toml index a4677d727f..43b0cbf69f 100644 --- a/frontend/rust-lib/flowy-grid/Cargo.toml +++ b/frontend/rust-lib/flowy-grid/Cargo.toml @@ -36,6 +36,7 @@ serde_json = {version = "1.0"} serde_repr = "0.1" indexmap = {version = "1.8.1", features = ["serde"]} fancy-regex = "0.10.0" +url = { version = "2"} [dev-dependencies] flowy-test = { path = "../flowy-test" } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs index 26efd34acc..81a7ff5c04 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs @@ -207,8 +207,6 @@ impl CellDataOperation for MultiSelectTypeOption { return Ok(DecodedCellData::default()); } - tracing::info!("😁{}", self.options.len()); - let encoded_data = encoded_data.into(); let select_options = select_option_ids(encoded_data) .into_iter() diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs index 6cef992fbd..7299b1babd 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs @@ -62,7 +62,19 @@ impl CellDataOperation, String> for URLTypeOption { }; if let Ok(Some(m)) = URL_REGEX.find(&changeset) { - cell_data.url = m.as_str().to_string(); + // Only support https scheme by now + match url::Url::parse(m.as_str()) { + Ok(url) => { + if url.scheme() == "https" { + cell_data.url = url.into(); + } else { + cell_data.url = format!("https://{}", m.as_str()); + } + } + Err(_) => { + cell_data.url = format!("https://{}", m.as_str()); + } + } } cell_data.to_json() @@ -132,7 +144,7 @@ mod tests { &field_type, &field_meta, "AppFlowy website - https://www.appflowy.io", - "https://www.appflowy.io", + "https://www.appflowy.io/", ); assert_changeset( @@ -141,7 +153,7 @@ mod tests { &field_type, &field_meta, "AppFlowy website appflowy.io", - "appflowy.io", + "https://appflowy.io", ); } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs index 3191fa2cc0..d93f84244a 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs @@ -167,7 +167,6 @@ pub fn decode_cell_data>( field_meta: &FieldMeta, ) -> FlowyResult { let encoded_data = encoded_data.into(); - tracing::info!("😁{:?}", field_meta.type_options); let get_cell_data = || { let data = match t_field_type { FieldType::RichText => field_meta