From ed10ebac7a89e019406499c1c98bbb30acabe104 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sat, 9 Apr 2022 20:45:33 +0800 Subject: [PATCH] fix: add new select option tag from textfield --- .../grid/cell_bloc/selection_editor_bloc.dart | 14 ++- .../workspace/application/grid/grid_bloc.dart | 45 +------- .../application/grid/row/row_service.dart | 32 +++++- .../plugins/grid/src/grid_page.dart | 1 + .../cell/selection_cell/extension.dart | 96 ----------------- .../cell/selection_cell/selection_editor.dart | 5 +- .../cell/selection_cell/text_field.dart | 101 ++++++++++++++++++ .../type_options/selection_type_option.rs | 61 +++++------ 8 files changed, 174 insertions(+), 181 deletions(-) create mode 100644 frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/text_field.dart diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart index d2606b0fdb..5b6ba78bd5 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart @@ -110,7 +110,7 @@ class SelectOptionEditorBloc extends Bloc add(SelectOptionEditorEvent.didReceiveOptions( - selectOptionContext.options, - selectOptionContext.selectOptions, - )), + (selectOptionContext) { + if (!isClosed) { + add(SelectOptionEditorEvent.didReceiveOptions( + selectOptionContext.options, + selectOptionContext.selectOptions, + )); + } + }, (err) => Log.error(err), ); }, diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart index 01db1f1300..33d3cbe40c 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:app_flowy/workspace/application/grid/row/row_service.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; @@ -6,7 +7,6 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:equatable/equatable.dart'; import 'grid_block_service.dart'; import 'field/grid_listenr.dart'; import 'grid_service.dart'; @@ -154,46 +154,3 @@ class GridLoadingState with _$GridLoadingState { const factory GridLoadingState.loading() = _Loading; const factory GridLoadingState.finish(Either successOrFail) = _Finish; } - -class GridBlockRow { - final String gridId; - final String rowId; - final String blockId; - final double height; - - const GridBlockRow({ - required this.gridId, - required this.rowId, - required this.blockId, - required this.height, - }); -} - -class RowData extends Equatable { - final String gridId; - final String rowId; - final String blockId; - final List fields; - final double height; - - const RowData({ - required this.gridId, - required this.rowId, - required this.blockId, - required this.fields, - required this.height, - }); - - factory RowData.fromBlockRow(GridBlockRow row, List fields) { - return RowData( - gridId: row.gridId, - rowId: row.rowId, - blockId: row.blockId, - fields: fields, - height: row.height, - ); - } - - @override - List get props => [rowId, fields]; -} diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart index 72c25e022b..7ed5477364 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart @@ -1,5 +1,4 @@ import 'package:dartz/dartz.dart'; -import 'package:equatable/equatable.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; @@ -41,3 +40,34 @@ class CellData with _$CellData { Cell? cell, }) = _CellData; } + +@freezed +class RowData with _$RowData { + const factory RowData({ + required String gridId, + required String rowId, + required String blockId, + required List fields, + required double height, + }) = _RowData; + + factory RowData.fromBlockRow(GridBlockRow row, List fields) { + return RowData( + gridId: row.gridId, + rowId: row.rowId, + blockId: row.blockId, + fields: fields, + height: row.height, + ); + } +} + +@freezed +class GridBlockRow with _$GridBlockRow { + const factory GridBlockRow({ + required String gridId, + required String rowId, + required String blockId, + required double height, + }) = _GridBlockRow; +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart index 936ad68c44..5de9cc75a6 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart @@ -1,5 +1,6 @@ import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/grid/grid_bloc.dart'; +import 'package:app_flowy/workspace/application/grid/row/row_service.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart'; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart index d94e11dde0..bdf4d01158 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart @@ -1,6 +1,3 @@ -import 'dart:collection'; - -import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; @@ -8,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:textfield_tags/textfield_tags.dart'; extension SelectOptionColorExtension on SelectOptionColor { Color make(BuildContext context) { @@ -63,98 +59,6 @@ extension SelectOptionColorExtension on SelectOptionColor { } } -class SelectOptionTextField extends StatelessWidget { - final FocusNode _focusNode; - final TextEditingController _controller; - final TextfieldTagsController tagController; - final List options; - final LinkedHashMap selectedOptionMap; - - final double distanceToText; - - final Function(String) onNewTag; - - SelectOptionTextField({ - required this.options, - required this.selectedOptionMap, - required this.distanceToText, - required this.tagController, - required this.onNewTag, - TextEditingController? controller, - FocusNode? focusNode, - Key? key, - }) : _controller = controller ?? TextEditingController(), - _focusNode = focusNode ?? FocusNode(), - super(key: key); - - @override - Widget build(BuildContext context) { - final theme = context.watch(); - - return TextFieldTags( - textEditingController: _controller, - textfieldTagsController: tagController, - initialTags: selectedOptionMap.keys.toList(), - focusNode: _focusNode, - textSeparators: const [' ', ','], - inputfieldBuilder: (BuildContext context, editController, focusNode, error, onChanged, onSubmitted) { - return ((context, sc, tags, onTagDelegate) { - tags.retainWhere((name) { - return options.where((option) => option.name == name).isEmpty; - }); - if (tags.isNotEmpty) { - assert(tags.length == 1); - onNewTag(tags.first); - } - - return TextField( - autofocus: true, - controller: editController, - focusNode: focusNode, - onChanged: onChanged, - onSubmitted: onSubmitted, - maxLines: 1, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - decoration: InputDecoration( - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: theme.main1, width: 1.0), - borderRadius: Corners.s10Border, - ), - isDense: true, - prefixIcon: _renderTags(sc), - hintText: LocaleKeys.grid_selectOption_searchOption.tr(), - prefixIconConstraints: BoxConstraints(maxWidth: distanceToText), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: theme.main1, - width: 1.0, - ), - borderRadius: Corners.s10Border, - ), - ), - ); - }); - }, - ); - } - - Widget? _renderTags(ScrollController sc) { - if (selectedOptionMap.isEmpty) { - return null; - } - - final children = selectedOptionMap.values.map((option) => SelectOptionTag(option: option)).toList(); - return Padding( - padding: const EdgeInsets.all(8.0), - child: SingleChildScrollView( - controller: sc, - scrollDirection: Axis.horizontal, - child: Row(children: children), - ), - ); - } -} - class SelectOptionTag extends StatelessWidget { final SelectOption option; final bool isSelected; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart index 1685473a65..ebaabbfb0b 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart @@ -1,5 +1,4 @@ import 'dart:collection'; - import 'package:app_flowy/workspace/application/grid/cell_bloc/selection_editor_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'; @@ -21,6 +20,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:textfield_tags/textfield_tags.dart'; import 'extension.dart'; +import 'text_field.dart'; const double _editorPannelWidth = 300; @@ -135,8 +135,7 @@ class _TextField extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocConsumer( - listener: (context, state) {}, + return BlocBuilder( builder: (context, state) { final optionMap = LinkedHashMap.fromIterable(state.selectedOptions, key: (option) => option.name, value: (option) => option); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/text_field.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/text_field.dart new file mode 100644 index 0000000000..e7f4e98851 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/text_field.dart @@ -0,0 +1,101 @@ +import 'dart:collection'; + +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/theme.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:textfield_tags/textfield_tags.dart'; + +import 'extension.dart'; + +class SelectOptionTextField extends StatelessWidget { + final FocusNode _focusNode; + final TextEditingController _controller; + final TextfieldTagsController tagController; + final List options; + final LinkedHashMap selectedOptionMap; + + final double distanceToText; + + final Function(String) onNewTag; + + SelectOptionTextField({ + required this.options, + required this.selectedOptionMap, + required this.distanceToText, + required this.tagController, + required this.onNewTag, + TextEditingController? controller, + FocusNode? focusNode, + Key? key, + }) : _controller = controller ?? TextEditingController(), + _focusNode = focusNode ?? FocusNode(), + super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + + return TextFieldTags( + textEditingController: _controller, + textfieldTagsController: tagController, + initialTags: selectedOptionMap.keys.toList(), + focusNode: _focusNode, + textSeparators: const [' ', ','], + inputfieldBuilder: (BuildContext context, editController, focusNode, error, onChanged, onSubmitted) { + return ((context, sc, tags, onTagDelegate) { + return TextField( + autofocus: true, + controller: editController, + focusNode: focusNode, + onChanged: onChanged, + onSubmitted: (text) { + if (onSubmitted != null) { + onSubmitted(text); + } + + if (text.isNotEmpty) { + onNewTag(text); + } + }, + maxLines: 1, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + decoration: InputDecoration( + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: theme.main1, width: 1.0), + borderRadius: Corners.s10Border, + ), + isDense: true, + prefixIcon: _renderTags(sc), + hintText: LocaleKeys.grid_selectOption_searchOption.tr(), + prefixIconConstraints: BoxConstraints(maxWidth: distanceToText), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: theme.main1, width: 1.0), + borderRadius: Corners.s10Border, + ), + ), + ); + }); + }, + ); + } + + Widget? _renderTags(ScrollController sc) { + if (selectedOptionMap.isEmpty) { + return null; + } + + final children = selectedOptionMap.values.map((option) => SelectOptionTag(option: option)).toList(); + return Padding( + padding: const EdgeInsets.all(8.0), + child: SingleChildScrollView( + controller: sc, + scrollDirection: Axis.horizontal, + child: Row(children: children), + ), + ); + } +} 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 bcf21972ce..30cd36b122 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 @@ -16,9 +16,28 @@ use std::str::FromStr; pub const SELECTION_IDS_SEPARATOR: &str = ","; pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync { - fn insert_option(&mut self, new_option: SelectOption); - fn delete_option(&mut self, delete_option: SelectOption); + fn insert_option(&mut self, new_option: SelectOption) { + let options = self.mut_options(); + if let Some(index) = options + .iter() + .position(|option| option.id == new_option.id || option.name == new_option.name) + { + options.remove(index); + options.insert(index, new_option); + } else { + options.insert(0, new_option); + } + } + + fn delete_option(&mut self, delete_option: SelectOption) { + let options = self.mut_options(); + if let Some(index) = options.iter().position(|option| option.id == delete_option.id) { + options.remove(index); + } + } + fn option_context(&self, cell_meta: &Option) -> SelectOptionContext; + fn mut_options(&mut self) -> &mut Vec; } // Single select @@ -33,21 +52,6 @@ pub struct SingleSelectTypeOption { impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect); impl SelectOptionOperation for SingleSelectTypeOption { - fn insert_option(&mut self, new_option: SelectOption) { - if let Some(index) = self.options.iter().position(|option| option.id == new_option.id) { - self.options.remove(index); - self.options.insert(index, new_option); - } else { - self.options.insert(0, new_option); - } - } - - fn delete_option(&mut self, delete_option: SelectOption) { - if let Some(index) = self.options.iter().position(|option| option.id == delete_option.id) { - self.options.remove(index); - } - } - fn option_context(&self, cell_meta: &Option) -> SelectOptionContext { let select_options = make_select_context_from(cell_meta, &self.options); SelectOptionContext { @@ -55,6 +59,10 @@ impl SelectOptionOperation for SingleSelectTypeOption { select_options, } } + + fn mut_options(&mut self) -> &mut Vec { + &mut self.options + } } impl CellDataOperation for SingleSelectTypeOption { @@ -139,21 +147,6 @@ impl MultiSelectTypeOption { } impl SelectOptionOperation for MultiSelectTypeOption { - fn insert_option(&mut self, new_option: SelectOption) { - if let Some(index) = self.options.iter().position(|option| option.id == new_option.id) { - self.options.remove(index); - self.options.insert(index, new_option); - } else { - self.options.insert(0, new_option); - } - } - - fn delete_option(&mut self, delete_option: SelectOption) { - if let Some(index) = self.options.iter().position(|option| option.id == delete_option.id) { - self.options.remove(index); - } - } - fn option_context(&self, cell_meta: &Option) -> SelectOptionContext { let select_options = make_select_context_from(cell_meta, &self.options); SelectOptionContext { @@ -161,6 +154,10 @@ impl SelectOptionOperation for MultiSelectTypeOption { select_options, } } + + fn mut_options(&mut self) -> &mut Vec { + &mut self.options + } } impl CellDataOperation for MultiSelectTypeOption {