From b7867bf17746f7f86c1720d1c622b0d32afa6d18 Mon Sep 17 00:00:00 2001 From: Alex Wallen Date: Mon, 20 Mar 2023 15:29:17 -1000 Subject: [PATCH] Create a new board from the slash menu (#2018) * feat: create a new board. * feat: switch slash menu keywords * fix: remove unused imports * chore: export SelectionMenuItem from appflowy_editor for integration test * feat: add integration test for slash commands * fix: test in new file was unable to start * feat: add translations --- .../assets/translations/en.json | 5 +- .../integration_test/switch_folder_test.dart | 71 +++++++++++++++++++ .../lib/plugins/document/document_page.dart | 13 ++-- .../plugins/board/board_menu_item.dart | 3 +- .../plugins/board/board_view_menu_item.dart | 61 ++++++++++++++++ .../appflowy_editor/lib/appflowy_editor.dart | 1 + 6 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/board/board_view_menu_item.dart diff --git a/frontend/appflowy_flutter/assets/translations/en.json b/frontend/appflowy_flutter/assets/translations/en.json index f1aade4e48..d01c2453e0 100644 --- a/frontend/appflowy_flutter/assets/translations/en.json +++ b/frontend/appflowy_flutter/assets/translations/en.json @@ -334,7 +334,8 @@ }, "slashMenu": { "board": { - "selectABoardToLinkTo": "Select a Board to link to" + "selectABoardToLinkTo": "Select a Board to link to", + "createANewBoard": "Create a new Board" }, "grid": { "selectAGridToLinkTo": "Select a Grid to link to" @@ -402,4 +403,4 @@ "layoutDateField": "Layout calendar by" } } -} \ No newline at end of file +} diff --git a/frontend/appflowy_flutter/integration_test/switch_folder_test.dart b/frontend/appflowy_flutter/integration_test/switch_folder_test.dart index 6dcd41b081..92ddaf3472 100644 --- a/frontend/appflowy_flutter/integration_test/switch_folder_test.dart +++ b/frontend/appflowy_flutter/integration_test/switch_folder_test.dart @@ -1,5 +1,10 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/plugins/base/built_in_page_widget.dart'; import 'package:appflowy/user/presentation/folder/folder_widget.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/style_widget/text_field.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -157,5 +162,71 @@ void main() { await TestFolder.currentLocation(), ); }); + + testWidgets('/board shortcut creates a new board', (tester) async { + const folderName = 'appflowy'; + await TestFolder.cleanTestLocation(folderName); + await TestFolder.setTestLocation(folderName); + + await tester.initializeAppFlowy(); + + // tap open button + await mockGetDirectoryPath(folderName); + await tester.tapOpenFolderButton(); + + await tester.wait(1000); + await tester.expectToSeeWelcomePage(); + + final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + // Necessary for being able to enterText when not in debug mode + binding.testTextInput.register(); + + // Needs tab to obtain focus for the app flowy editor. + // by default the tap appears at the center of the widget. + final Finder editor = find.byType(AppFlowyEditor); + await tester.tap(editor); + await tester.pumpAndSettle(); + + // tester.sendText() cannot be used since the editor + // does not contain any EditableText widgets. + // to interact with the app during an integration test, + // simulate physical keyboard events. + await simulateKeyDownEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + await simulateKeyDownEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft); + await tester.pumpAndSettle(); + await simulateKeyDownEvent(LogicalKeyboardKey.slash); + await tester.pumpAndSettle(); + await simulateKeyDownEvent(LogicalKeyboardKey.keyB); + await tester.pumpAndSettle(); + await simulateKeyDownEvent(LogicalKeyboardKey.keyO); + await tester.pumpAndSettle(); + await simulateKeyDownEvent(LogicalKeyboardKey.keyA); + await tester.pumpAndSettle(); + await simulateKeyDownEvent(LogicalKeyboardKey.keyR); + await tester.pumpAndSettle(); + await simulateKeyDownEvent(LogicalKeyboardKey.keyD); + await tester.pumpAndSettle(); + await simulateKeyDownEvent(LogicalKeyboardKey.arrowDown); + await tester.pumpAndSettle(); + + // Checks whether the options in the selection menu + // for /board exist. + expect(find.byType(SelectionMenuItemWidget), findsAtLeastNWidgets(2)); + + // Finalizes the slash command that creates the board. + await simulateKeyDownEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + + // Checks whether new board is referenced and properly on the page. + expect(find.byType(BuiltInPageWidget), findsOneWidget); + + // Checks whether the new board is in the side bar. + final sidebarLabel = LocaleKeys.newPageText.tr(); + expect(find.text(sidebarLabel), findsOneWidget); + }); }); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/document_page.dart b/frontend/appflowy_flutter/lib/plugins/document/document_page.dart index 73bcd377bc..42e3884023 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/document_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/document_page.dart @@ -1,4 +1,7 @@ -import 'package:appflowy/plugins/document/presentation/plugins/board/board_menu_item.dart'; +import 'package:appflowy/plugins/document/presentation/plugins/board/board_view_menu_item.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy/plugins/document/presentation/plugins/board/board_node_widget.dart'; import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_node_widget.dart'; import 'package:appflowy/plugins/document/presentation/plugins/grid/grid_menu_item.dart'; @@ -7,19 +10,17 @@ import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/au import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/auto_completion_plugins.dart'; import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_node_widget.dart'; import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_toolbar_item.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:dartz/dartz.dart' as dartz; import 'package:flowy_infra_ui/widget/error_page.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../../startup/startup.dart'; import 'application/doc_bloc.dart'; import 'editor_styles.dart'; import 'presentation/banner.dart'; +import 'presentation/plugins/board/board_menu_item.dart'; class DocumentPage extends StatefulWidget { final VoidCallback onDeleted; @@ -172,6 +173,8 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> { emojiMenuItem, // Board boardMenuItem, + // Create Board + boardViewMenuItem(documentBloc), // Grid gridMenuItem, // Callout diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/board/board_menu_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/board/board_menu_item.dart index cccb671e7c..c81efbb279 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/board/board_menu_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/board/board_menu_item.dart @@ -17,7 +17,8 @@ SelectionMenuItem boardMenuItem = SelectionMenuItem( : editorState.editorStyle.selectionMenuItemIconColor, ); }, - keywords: ['board', 'kanban'], + // TODO(a-wallen): Translate keywords + keywords: ['referenced board', 'referenced kanban'], handler: (editorState, menuService, context) { showLinkToPageMenu( editorState, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/board/board_view_menu_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/board/board_view_menu_item.dart new file mode 100644 index 0000000000..b25fe52679 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/board/board_view_menu_item.dart @@ -0,0 +1,61 @@ +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/application/prelude.dart'; +import 'package:appflowy/plugins/document/presentation/plugins/base/insert_page_command.dart'; +import 'package:appflowy/workspace/application/app/app_service.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/image.dart'; +import 'package:flutter/material.dart'; + +SelectionMenuItem boardViewMenuItem(DocumentBloc documentBloc) => + SelectionMenuItem( + name: LocaleKeys.document_slashMenu_board_createANewBoard.tr(), + icon: (editorState, onSelected) { + return svgWidget( + 'editor/board', + size: const Size.square(18.0), + color: onSelected + ? editorState.editorStyle.selectionMenuItemSelectedIconColor + : editorState.editorStyle.selectionMenuItemIconColor, + ); + }, + // TODO(a-wallen): Translate keywords. + keywords: ['board', 'kanban'], + handler: (editorState, menuService, context) async { + if (!documentBloc.view.hasAppId()) { + return; + } + + final appId = documentBloc.view.appId; + final service = AppBackendService(); + + final result = (await service.createView( + appId: appId, + name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + layoutType: ViewLayoutTypePB.Board, + )) + .getLeftOrNull(); + + // If the result is null, then something went wrong here. + if (result == null) { + return; + } + + final app = + (await service.readApp(appId: result.appId)).getLeftOrNull(); + // We should show an error dialog. + if (app == null) { + return; + } + + final view = + (await service.getView(result.appId, result.id)).getLeftOrNull(); + // As this. + if (view == null) { + return; + } + + editorState.insertPage(app, view); + }, + ); diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/lib/appflowy_editor.dart b/frontend/appflowy_flutter/packages/appflowy_editor/lib/appflowy_editor.dart index bea819e232..44595fcdff 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/lib/appflowy_editor.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/lib/appflowy_editor.dart @@ -31,6 +31,7 @@ export 'src/extensions/attributes_extension.dart'; export 'src/render/rich_text/default_selectable.dart'; export 'src/render/rich_text/flowy_rich_text.dart'; export 'src/render/selection_menu/selection_menu_widget.dart'; +export 'src/render/selection_menu/selection_menu_item_widget.dart'; export 'src/l10n/l10n.dart'; export 'src/render/style/plugin_styles.dart'; export 'src/render/style/editor_style.dart';