Selaa lähdekoodia

feat: enable delete field in edit row detail page

appflowy 2 vuotta sitten
vanhempi
commit
34275664b2

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

@@ -191,7 +191,8 @@
       "optionTitle": "Options",
       "addOption": "Add option",
       "editProperty": "Edit property",
-      "newColumn": "New column"
+      "newColumn": "New column",
+      "deleteFieldPromptMessage": "Are you sure? This property will be deleted"
     },
     "row": {
       "duplicate": "Duplicate",

+ 7 - 2
frontend/app_flowy/lib/plugins/board/presentation/board_page.dart

@@ -287,8 +287,13 @@ class _BoardContentState extends State<BoardContent> {
     );
   }
 
-  void _openCard(String gridId, GridFieldController fieldController,
-      RowPB rowPB, GridRowCache rowCache, BuildContext context) {
+  void _openCard(
+    String gridId,
+    GridFieldController fieldController,
+    RowPB rowPB,
+    GridRowCache rowCache,
+    BuildContext context,
+  ) {
     final rowInfo = RowInfo(
       gridId: gridId,
       fields: UnmodifiableListView(fieldController.fieldContexts),

+ 26 - 2
frontend/app_flowy/lib/plugins/grid/application/field/field_editor_bloc.dart

@@ -2,6 +2,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'dart:async';
 import 'package:dartz/dartz.dart';
+import 'field_service.dart';
 import 'type_option/type_option_context.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 
@@ -15,10 +16,11 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
   FieldEditorBloc({
     required String gridId,
     required String fieldName,
+    required bool isGroupField,
     required IFieldTypeOptionLoader loader,
   })  : dataController =
             TypeOptionDataController(gridId: gridId, loader: loader),
-        super(FieldEditorState.initial(gridId, fieldName)) {
+        super(FieldEditorState.initial(gridId, fieldName, isGroupField)) {
     on<FieldEditorEvent>(
       (event, emit) async {
         await event.when(
@@ -35,7 +37,23 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
             emit(state.copyWith(name: name));
           },
           didReceiveFieldChanged: (FieldPB field) {
-            emit(state.copyWith(field: Some(field), name: field.name));
+            emit(state.copyWith(
+              field: Some(field),
+              name: field.name,
+              canDelete: field.isPrimary,
+            ));
+          },
+          deleteField: () {
+            state.field.fold(
+              () => null,
+              (field) {
+                final fieldService = FieldService(
+                  gridId: gridId,
+                  fieldId: field.id,
+                );
+                fieldService.deleteField();
+              },
+            );
           },
         );
       },
@@ -52,6 +70,7 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
 class FieldEditorEvent with _$FieldEditorEvent {
   const factory FieldEditorEvent.initial() = _InitialField;
   const factory FieldEditorEvent.updateName(String name) = _UpdateName;
+  const factory FieldEditorEvent.deleteField() = _DeleteField;
   const factory FieldEditorEvent.didReceiveFieldChanged(FieldPB field) =
       _DidReceiveFieldChanged;
 }
@@ -63,16 +82,21 @@ class FieldEditorState with _$FieldEditorState {
     required String errorText,
     required String name,
     required Option<FieldPB> field,
+    required bool canDelete,
+    required bool isGroupField,
   }) = _FieldEditorState;
 
   factory FieldEditorState.initial(
     String gridId,
     String fieldName,
+    bool isGroupField,
   ) =>
       FieldEditorState(
         gridId: gridId,
         errorText: '',
         field: none(),
+        canDelete: false,
         name: fieldName,
+        isGroupField: isGroupField,
       );
 }

+ 10 - 3
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell_action_sheet.dart

@@ -2,6 +2,7 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/type_option
 import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_editor.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
+import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -215,9 +216,15 @@ extension _FieldActionExtension on FieldAction {
             .add(const FieldActionSheetEvent.duplicateField());
         break;
       case FieldAction.delete:
-        context
-            .read<FieldActionSheetBloc>()
-            .add(const FieldActionSheetEvent.deleteField());
+        FlowyAlertDialog(
+          title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
+          confirm: () {
+            context
+                .read<FieldActionSheetBloc>()
+                .add(const FieldActionSheetEvent.deleteField());
+          },
+        ).show(context);
+
         break;
     }
   }

+ 59 - 3
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart

@@ -2,6 +2,13 @@ import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart'
 import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
 import 'package:appflowy_popover/popover.dart';
 import 'package:easy_localization/easy_localization.dart';
+import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
+import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flutter/material.dart';
@@ -13,6 +20,7 @@ import 'field_type_option_editor.dart';
 class FieldEditor extends StatefulWidget {
   final String gridId;
   final String fieldName;
+  final bool isGroupField;
   final VoidCallback? onRemoved;
 
   final IFieldTypeOptionLoader typeOptionLoader;
@@ -20,6 +28,7 @@ class FieldEditor extends StatefulWidget {
     required this.gridId,
     this.fieldName = "",
     required this.typeOptionLoader,
+    this.isGroupField = false,
     this.onRemoved,
     Key? key,
   }) : super(key: key);
@@ -41,9 +50,10 @@ class _FieldEditorState extends State<FieldEditor> {
   Widget build(BuildContext context) {
     return BlocProvider(
       create: (context) => FieldEditorBloc(
-        gridId: widget.gridId,
-        fieldName: widget.fieldName,
-        loader: widget.typeOptionLoader,
+        gridId: gridId,
+        fieldName: fieldName,
+        isGroupField: isGroupField,
+        loader: typeOptionLoader,
       )..add(const FieldEditorEvent.initial()),
       child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
         buildWhen: (p, c) => false,
@@ -56,6 +66,8 @@ class _FieldEditorState extends State<FieldEditor> {
               const VSpace(10),
               const _FieldNameCell(),
               const VSpace(10),
+              const _DeleteFieldButton(),
+              const VSpace(10),
               _FieldTypeOptionCell(popoverMutex: popoverMutex),
             ],
           );
@@ -114,3 +126,47 @@ class _FieldNameCell extends StatelessWidget {
     );
   }
 }
+
+class _DeleteFieldButton extends StatelessWidget {
+  const _DeleteFieldButton({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return BlocBuilder<FieldEditorBloc, FieldEditorState>(
+      builder: (context, state) {
+        final enable = !state.canDelete && !state.isGroupField;
+        return SizedBox(
+          height: GridSize.typeOptionItemHeight,
+          child: FlowyButton(
+            text: FlowyText.medium(
+              LocaleKeys.grid_field_delete.tr(),
+              fontSize: 12,
+              color: enable ? null : theme.shader4,
+            ),
+            hoverColor: theme.hover,
+            onTap: () {
+              if (enable) {
+                FlowyAlertDialog(
+                  title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
+                  cancel: () {
+                    FlowyOverlay.of(context).remove(FieldEditor.identifier());
+                  },
+                  confirm: () {
+                    context
+                        .read<FieldEditorBloc>()
+                        .add(const FieldEditorEvent.deleteField());
+                    FlowyOverlay.of(context).remove(FieldEditor.identifier());
+                  },
+                ).show(context);
+              }
+            },
+            leftIcon: svgWidget('grid/delete', color: theme.iconColor),
+          ),
+        );
+      },
+    );
+  }
+
+  void so() {}
+}

+ 10 - 3
frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart

@@ -1,4 +1,5 @@
 import 'package:app_flowy/plugins/grid/application/row/row_action_sheet_bloc.dart';
+import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:flowy_infra/image.dart';
@@ -150,9 +151,15 @@ extension _RowActionExtension on _RowAction {
             .add(const RowActionSheetEvent.duplicateRow());
         break;
       case _RowAction.delete:
-        context
-            .read<RowActionSheetBloc>()
-            .add(const RowActionSheetEvent.deleteRow());
+        FlowyAlertDialog(
+          title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
+          confirm: () {
+            context
+                .read<RowActionSheetBloc>()
+                .add(const RowActionSheetEvent.deleteRow());
+          },
+        ).show(context);
+
         break;
     }
   }

+ 13 - 1
frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart

@@ -133,6 +133,7 @@ class _PropertyList extends StatelessWidget {
                 ),
               ),
             ),
+            const VSpace(10),
             _CreateFieldButton(
               viewId: viewId,
               onClosed: () {
@@ -180,8 +181,9 @@ class _CreateFieldButton extends StatelessWidget {
       triggerActions: PopoverTriggerActionFlags.click,
       direction: PopoverDirection.bottomWithLeftAligned,
       onClose: onClosed,
-      child: SizedBox(
+      child: Container(
         height: 40,
+        decoration: _makeBoxDecoration(context),
         child: FlowyButton(
           text: FlowyText.medium(
             LocaleKeys.grid_field_newColumn.tr(),
@@ -195,6 +197,15 @@ class _CreateFieldButton extends StatelessWidget {
       popupBuilder: (BuildContext context) => onOpened(),
     );
   }
+
+  BoxDecoration _makeBoxDecoration(BuildContext context) {
+    final theme = context.read<AppTheme>();
+    final borderSide = BorderSide(color: theme.shader6, width: 1.0);
+    return BoxDecoration(
+      color: theme.surface,
+      border: Border(top: borderSide),
+    );
+  }
 }
 
 class _RowDetailCell extends StatefulWidget {
@@ -247,6 +258,7 @@ class _RowDetailCellState extends State<_RowDetailCell> {
                     child: FieldEditor(
                       gridId: widget.cellId.gridId,
                       fieldName: widget.cellId.fieldContext.field.name,
+                      isGroupField: widget.cellId.fieldContext.isGroupField,
                       typeOptionLoader: FieldTypeOptionLoader(
                         gridId: widget.cellId.gridId,
                         field: widget.cellId.fieldContext.field,

+ 12 - 6
frontend/app_flowy/lib/workspace/presentation/widgets/dialogs.dart

@@ -56,7 +56,8 @@ class _CreateTextFieldDialog extends State<TextFieldDialog> {
           FlowyFormTextInput(
             hintText: LocaleKeys.dialogCreatePageNameHint.tr(),
             initialValue: widget.value,
-            textStyle: const TextStyle(fontSize: 24, fontWeight: FontWeight.w400),
+            textStyle:
+                const TextStyle(fontSize: 24, fontWeight: FontWeight.w400),
             autoFocus: true,
             onChanged: (text) {
               newValue = text;
@@ -120,7 +121,7 @@ class _CreateFlowyAlertDialog extends State<FlowyAlertDialog> {
             const VSpace(20),
             OkCancelButton(
               onOkPressed: widget.confirm!,
-              onCancelPressed: widget.confirm,
+              onCancelPressed: widget.cancel,
             )
           ]
         ],
@@ -158,7 +159,7 @@ class OkCancelDialog extends StatelessWidget {
         crossAxisAlignment: CrossAxisAlignment.start,
         children: <Widget>[
           if (title != null) ...[
-            Text(title!.toUpperCase(), style: TextStyles.T1.textColor(theme.shader1)),
+            FlowyText.medium(title!.toUpperCase(), color: theme.shader1),
             VSpace(Insets.sm * 1.5),
             Container(color: theme.bg1, height: 1),
             VSpace(Insets.m * 1.5),
@@ -185,7 +186,12 @@ class OkCancelButton extends StatelessWidget {
   final double? minHeight;
 
   const OkCancelButton(
-      {Key? key, this.onOkPressed, this.onCancelPressed, this.okTitle, this.cancelTitle, this.minHeight})
+      {Key? key,
+      this.onOkPressed,
+      this.onCancelPressed,
+      this.okTitle,
+      this.cancelTitle,
+      this.minHeight})
       : super(key: key);
 
   @override
@@ -200,7 +206,7 @@ class OkCancelButton extends StatelessWidget {
               cancelTitle ?? LocaleKeys.button_Cancel.tr(),
               onPressed: () {
                 onCancelPressed!();
-                AppGlobals.nav.pop();
+                Navigator.of(context).pop();
               },
               bigMode: true,
             ),
@@ -210,7 +216,7 @@ class OkCancelButton extends StatelessWidget {
               okTitle ?? LocaleKeys.button_OK.tr(),
               onPressed: () {
                 onOkPressed!();
-                AppGlobals.nav.pop();
+                Navigator.of(context).pop();
               },
               bigMode: true,
             ),

+ 18 - 8
frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/primary_button.dart

@@ -1,22 +1,30 @@
+import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 import 'package:flowy_infra/size.dart';
-import 'package:flowy_infra/text_style.dart';
 import 'package:flowy_infra/theme.dart';
 import 'base_styled_button.dart';
-import 'package:textstyle_extensions/textstyle_extensions.dart';
 
 class PrimaryTextButton extends StatelessWidget {
   final String label;
   final VoidCallback? onPressed;
   final bool bigMode;
 
-  const PrimaryTextButton(this.label, {Key? key, this.onPressed, this.bigMode = false}) : super(key: key);
+  const PrimaryTextButton(this.label,
+      {Key? key, this.onPressed, this.bigMode = false})
+      : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    TextStyle txtStyle = TextStyles.Btn.textColor(Colors.white);
-    return PrimaryButton(bigMode: bigMode, onPressed: onPressed, child: Text(label, style: txtStyle));
+    final theme = context.watch<AppTheme>();
+    return PrimaryButton(
+      bigMode: bigMode,
+      onPressed: onPressed,
+      child: FlowyText.regular(
+        label,
+        color: theme.surface,
+      ),
+    );
   }
 }
 
@@ -25,14 +33,16 @@ class PrimaryButton extends StatelessWidget {
   final VoidCallback? onPressed;
   final bool bigMode;
 
-  const PrimaryButton({Key? key, required this.child, this.onPressed, this.bigMode = false}) : super(key: key);
+  const PrimaryButton(
+      {Key? key, required this.child, this.onPressed, this.bigMode = false})
+      : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     return BaseStyledButton(
-      minWidth: bigMode ? 170 : 78,
-      minHeight: bigMode ? 48 : 28,
+      minWidth: bigMode ? 100 : 80,
+      minHeight: bigMode ? 40 : 38,
       contentPadding: EdgeInsets.zero,
       bgColor: theme.main1,
       hoverColor: theme.main1,

+ 17 - 8
frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart

@@ -1,9 +1,8 @@
+import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 // ignore: import_of_legacy_library_into_null_safe
-import 'package:textstyle_extensions/textstyle_extensions.dart';
 import 'package:flowy_infra/size.dart';
-import 'package:flowy_infra/text_style.dart';
 import 'package:flowy_infra/theme.dart';
 import 'base_styled_button.dart';
 
@@ -12,13 +11,21 @@ class SecondaryTextButton extends StatelessWidget {
   final VoidCallback? onPressed;
   final bool bigMode;
 
-  const SecondaryTextButton(this.label, {Key? key, this.onPressed, this.bigMode = false}) : super(key: key);
+  const SecondaryTextButton(this.label,
+      {Key? key, this.onPressed, this.bigMode = false})
+      : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
-    TextStyle txtStyle = TextStyles.Btn.textColor(theme.main1);
-    return SecondaryButton(bigMode: bigMode, onPressed: onPressed, child: Text(label, style: txtStyle));
+    return SecondaryButton(
+      bigMode: bigMode,
+      onPressed: onPressed,
+      child: FlowyText.regular(
+        label,
+        color: theme.main1,
+      ),
+    );
   }
 }
 
@@ -27,14 +34,16 @@ class SecondaryButton extends StatelessWidget {
   final VoidCallback? onPressed;
   final bool bigMode;
 
-  const SecondaryButton({Key? key, required this.child, this.onPressed, this.bigMode = false}) : super(key: key);
+  const SecondaryButton(
+      {Key? key, required this.child, this.onPressed, this.bigMode = false})
+      : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     return BaseStyledButton(
-      minWidth: bigMode ? 170 : 78,
-      minHeight: bigMode ? 48 : 28,
+      minWidth: bigMode ? 100 : 80,
+      minHeight: bigMode ? 40 : 38,
       contentPadding: EdgeInsets.zero,
       bgColor: theme.shader7,
       hoverColor: theme.hover,

+ 1 - 1
frontend/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/dialog_size.dart

@@ -1,3 +1,3 @@
 class DialogSize {
-  static double get minDialogWidth => 480;
+  static double get minDialogWidth => 400;
 }

+ 3 - 1
frontend/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart

@@ -133,7 +133,9 @@ class StyledDialogRoute<T> extends PopupRoute<T> {
         super(settings: settings, filter: barrier.filter);
 
   @override
-  bool get barrierDismissible => barrier.dismissible;
+  bool get barrierDismissible {
+    return barrier.dismissible;
+  }
 
   @override
   String get barrierLabel => barrier.label;