Prechádzať zdrojové kódy

chore: show create option message on bottom

appflowy 3 rokov pred
rodič
commit
18752e7af8

+ 1 - 0
frontend/app_flowy/assets/translations/en.json

@@ -181,6 +181,7 @@
       "textPlaceholder": "Empty"
     },
     "selectOption": {
+      "create": "Create",
       "purpleColor": "Purple",
       "pinkColor": "Pink",
       "lightPinkColor": "Light Pink",

+ 54 - 12
frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart

@@ -1,9 +1,13 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'dart:async';
+
+import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-import 'dart:async';
+
+import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+
 import 'select_option_service.dart';
 
 part 'selection_editor_bloc.freezed.dart';
@@ -24,14 +28,19 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
             _startListening();
           },
           didReceiveOptions: (_DidReceiveOptions value) {
+            final result = _makeOptions(state.filter, value.options);
             emit(state.copyWith(
               allOptions: value.options,
-              options: _makeOptions(state.filter, value.options),
+              options: result.options,
+              createOption: result.createOption,
               selectedOptions: value.selectedOptions,
             ));
           },
           newOption: (_NewOption value) {
             _createOption(value.optionName);
+            emit(state.copyWith(
+              filter: none(),
+            ));
           },
           deleteOption: (_DeleteOption value) {
             _deleteOption(value.option);
@@ -91,16 +100,37 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
   }
 
   void _filterOption(String optionName, Emitter<SelectOptionEditorState> emit) {
-    emit(state.copyWith(filter: optionName, options: _makeOptions(optionName, state.allOptions)));
+    final _MakeOptionResult result = _makeOptions(Some(optionName), state.allOptions);
+    emit(state.copyWith(
+      filter: Some(optionName),
+      options: result.options,
+      createOption: result.createOption,
+    ));
   }
 
-  List<SelectOption> _makeOptions(String filter, List<SelectOption> allOptions) {
+  _MakeOptionResult _makeOptions(Option<String> filter, List<SelectOption> allOptions) {
     final List<SelectOption> options = List.from(allOptions);
-    if (filter.isNotEmpty) {
-      options.retainWhere((option) => option.name.toLowerCase().contains(filter.toLowerCase()));
-    }
-
-    return options;
+    Option<String> createOption = filter;
+
+    filter.foldRight(null, (filter, previous) {
+      if (filter.isNotEmpty) {
+        options.retainWhere((option) {
+          final name = option.name.toLowerCase();
+          final lFilter = filter.toLowerCase();
+
+          if (name == lFilter) {
+            createOption = none();
+          }
+
+          return name.contains(lFilter);
+        });
+      }
+    });
+
+    return _MakeOptionResult(
+      options: options,
+      createOption: createOption,
+    );
   }
 
   void _startListening() {
@@ -135,7 +165,8 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
     required List<SelectOption> options,
     required List<SelectOption> allOptions,
     required List<SelectOption> selectedOptions,
-    required String filter,
+    required Option<String> createOption,
+    required Option<String> filter,
   }) = _SelectOptionEditorState;
 
   factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) {
@@ -144,7 +175,18 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
       options: data?.options ?? [],
       allOptions: data?.options ?? [],
       selectedOptions: data?.selectOptions ?? [],
-      filter: "",
+      createOption: none(),
+      filter: none(),
     );
   }
 }
+
+class _MakeOptionResult {
+  List<SelectOption> options;
+  Option<String> createOption;
+
+  _MakeOptionResult({
+    required this.options,
+    required this.createOption,
+  });
+}

+ 23 - 5
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart

@@ -60,17 +60,35 @@ extension SelectOptionColorExtension on SelectOptionColor {
 }
 
 class SelectOptionTag extends StatelessWidget {
-  final SelectOption option;
+  final String name;
+  final Color color;
   final bool isSelected;
-  const SelectOptionTag({required this.option, this.isSelected = false, Key? key}) : super(key: key);
+  const SelectOptionTag({
+    required this.name,
+    required this.color,
+    this.isSelected = false,
+    Key? key,
+  }) : super(key: key);
+
+  factory SelectOptionTag.fromSelectOption({
+    required BuildContext context,
+    required SelectOption option,
+    bool isSelected = false,
+  }) {
+    return SelectOptionTag(
+      name: option.name,
+      color: option.color.make(context),
+      isSelected: isSelected,
+    );
+  }
 
   @override
   Widget build(BuildContext context) {
     return ChoiceChip(
       pressElevation: 1,
-      label: FlowyText.medium(option.name, fontSize: 12),
-      selectedColor: option.color.make(context),
-      backgroundColor: option.color.make(context),
+      label: FlowyText.medium(name, fontSize: 12),
+      selectedColor: color,
+      backgroundColor: color,
       labelPadding: const EdgeInsets.symmetric(horizontal: 6),
       selected: true,
       onSelected: (_) {},

+ 8 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart

@@ -150,7 +150,14 @@ class _SelectOptionCell extends StatelessWidget {
         child: FlowyText.medium(cellStyle!.placeholder, fontSize: 14, color: theme.shader3),
       );
     } else {
-      final tags = selectOptions.map((option) => SelectOptionTag(option: option)).toList();
+      final tags = selectOptions
+          .map(
+            (option) => SelectOptionTag.fromSelectOption(
+              context: context,
+              option: option,
+            ),
+          )
+          .toList();
       child = Align(
         alignment: Alignment.centerLeft,
         child: Wrap(children: tags, spacing: 4, runSpacing: 4),

+ 46 - 8
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart

@@ -104,9 +104,18 @@ class _OptionList extends StatelessWidget {
   Widget build(BuildContext context) {
     return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
       builder: (context, state) {
-        final cells = state.options.map((option) {
+        List<Widget> cells = [];
+        cells.addAll(state.options.map((option) {
           return _SelectOptionCell(option, state.selectedOptions.contains(option));
-        }).toList();
+        }).toList());
+
+        state.createOption.fold(
+          () => null,
+          (createOption) {
+            cells.add(_CreateOptionCell(name: createOption));
+          },
+        );
+
         final list = ListView.separated(
           shrinkWrap: true,
           controller: ScrollController(),
@@ -119,7 +128,11 @@ class _OptionList extends StatelessWidget {
             return cells[index];
           },
         );
-        return list;
+
+        return Padding(
+          padding: const EdgeInsets.all(3.0),
+          child: list,
+        );
       },
     );
   }
@@ -177,6 +190,30 @@ class _Title extends StatelessWidget {
   }
 }
 
+class _CreateOptionCell extends StatelessWidget {
+  final String name;
+  const _CreateOptionCell({required this.name, Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return Row(
+      children: [
+        FlowyText.medium(
+          LocaleKeys.grid_selectOption_create.tr(),
+          fontSize: 12,
+          color: theme.shader3,
+        ),
+        const HSpace(10),
+        SelectOptionTag(
+          name: name,
+          color: theme.shader6,
+        ),
+      ],
+    );
+  }
+}
+
 class _SelectOptionCell extends StatelessWidget {
   final SelectOption option;
   final bool isSelected;
@@ -206,7 +243,11 @@ class _SelectOptionCell extends StatelessWidget {
       style: HoverStyle(hoverColor: theme.hover),
       builder: (_, onHover) {
         List<Widget> children = [
-          SelectOptionTag(option: option, isSelected: isSelected),
+          SelectOptionTag(
+            name: option.name,
+            color: option.color.make(context),
+            isSelected: isSelected,
+          ),
           const Spacer(),
         ];
 
@@ -223,10 +264,7 @@ class _SelectOptionCell extends StatelessWidget {
           ));
         }
 
-        return Padding(
-          padding: const EdgeInsets.all(3.0),
-          child: Row(children: children),
-        );
+        return Row(children: children);
       },
     );
   }

+ 5 - 3
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/text_field.dart

@@ -76,7 +76,7 @@ class SelectOptionTextField extends StatelessWidget {
                 borderRadius: Corners.s10Border,
               ),
               isDense: true,
-              prefixIcon: _renderTags(sc),
+              prefixIcon: _renderTags(context, sc),
               hintText: LocaleKeys.grid_selectOption_searchOption.tr(),
               prefixIconConstraints: BoxConstraints(maxWidth: distanceToText),
               focusedBorder: OutlineInputBorder(
@@ -90,12 +90,14 @@ class SelectOptionTextField extends StatelessWidget {
     );
   }
 
-  Widget? _renderTags(ScrollController sc) {
+  Widget? _renderTags(BuildContext context, ScrollController sc) {
     if (selectedOptionMap.isEmpty) {
       return null;
     }
 
-    final children = selectedOptionMap.values.map((option) => SelectOptionTag(option: option)).toList();
+    final children = selectedOptionMap.values
+        .map((option) => SelectOptionTag.fromSelectOption(context: context, option: option))
+        .toList();
     return Padding(
       padding: const EdgeInsets.all(8.0),
       child: SingleChildScrollView(