소스 검색

fix: add new select option tag from textfield

appflowy 3 년 전
부모
커밋
ed10ebac7a

+ 9 - 5
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart

@@ -110,7 +110,7 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
   void _loadOptions() async {
     _delayOperation?.cancel();
     _delayOperation = Timer(
-      const Duration(milliseconds: 300),
+      const Duration(milliseconds: 1),
       () async {
         final result = await _selectOptionService.getOpitonContext(
           gridId: state.gridId,
@@ -119,10 +119,14 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
         );
 
         result.fold(
-          (selectOptionContext) => add(SelectOptionEditorEvent.didReceiveOptions(
-            selectOptionContext.options,
-            selectOptionContext.selectOptions,
-          )),
+          (selectOptionContext) {
+            if (!isClosed) {
+              add(SelectOptionEditorEvent.didReceiveOptions(
+                selectOptionContext.options,
+                selectOptionContext.selectOptions,
+              ));
+            }
+          },
           (err) => Log.error(err),
         );
       },

+ 1 - 44
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<Unit, FlowyError> 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<Field> 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<Field> fields) {
-    return RowData(
-      gridId: row.gridId,
-      rowId: row.rowId,
-      blockId: row.blockId,
-      fields: fields,
-      height: row.height,
-    );
-  }
-
-  @override
-  List<Object> get props => [rowId, fields];
-}

+ 31 - 1
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<Field> fields,
+    required double height,
+  }) = _RowData;
+
+  factory RowData.fromBlockRow(GridBlockRow row, List<Field> 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;
+}

+ 1 - 0
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';

+ 0 - 96
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<SelectOption> options;
-  final LinkedHashMap<String, SelectOption> 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<AppTheme>();
-
-    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;

+ 2 - 3
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<SelectOptionEditorBloc, SelectOptionEditorState>(
-      listener: (context, state) {},
+    return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
       builder: (context, state) {
         final optionMap = LinkedHashMap<String, SelectOption>.fromIterable(state.selectedOptions,
             key: (option) => option.name, value: (option) => option);

+ 101 - 0
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<SelectOption> options;
+  final LinkedHashMap<String, SelectOption> 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<AppTheme>();
+
+    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),
+      ),
+    );
+  }
+}

+ 29 - 32
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<CellMeta>) -> SelectOptionContext;
+    fn mut_options(&mut self) -> &mut Vec<SelectOption>;
 }
 
 // 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<CellMeta>) -> 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<SelectOption> {
+        &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<CellMeta>) -> 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<SelectOption> {
+        &mut self.options
+    }
 }
 
 impl CellDataOperation for MultiSelectTypeOption {