From 2e91dfb4beb9eebb8bce012a4b6bc4495d88ab18 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 1 Feb 2023 14:37:45 +0700 Subject: [PATCH] Integrate Grid into Document (#1759) * fix: cursor doesn't blink when opening selection menu * feat: add board plugin * feat: integrate board plugin into document * feat: add i10n and fix known bugs * feat: support jump to board page on document * feat: disable editor scroll only when the board plugin is selected * chore: dart fix * chore: remove unused files * fix: dart lint * feat: integrate grid plugin into document * feat: add more menu to grid plugins * feat: refactor built-in page plugins, including board and grid * feat: remove padding set up when plugin type equals to editor --- .../app_flowy/assets/translations/en.json | 5 +- .../lib/plugins/document/document_page.dart | 6 + .../lib/plugins/document/editor_styles.dart | 2 +- .../plugins/base/built_in_page_widget.dart | 160 +++++++++++++++ .../plugins/base/insert_page_command.dart | 42 ++++ .../plugins/base/link_to_page_widget.dart | 186 ++++++++++++++++++ .../plugins/board/board_menu_item.dart | 186 +----------------- .../plugins/board/board_node_widget.dart | 143 ++------------ .../plugins/grid/grid_menu_item.dart | 29 +++ .../plugins/grid/grid_node_widget.dart | 54 +++++ .../presentation/share/share_button.dart | 13 +- .../plugins/grid/presentation/grid_page.dart | 8 +- .../presentation/home/home_stack.dart | 10 +- 13 files changed, 524 insertions(+), 320 deletions(-) create mode 100644 frontend/app_flowy/lib/plugins/document/presentation/plugins/base/built_in_page_widget.dart create mode 100644 frontend/app_flowy/lib/plugins/document/presentation/plugins/base/insert_page_command.dart create mode 100644 frontend/app_flowy/lib/plugins/document/presentation/plugins/base/link_to_page_widget.dart create mode 100644 frontend/app_flowy/lib/plugins/document/presentation/plugins/grid/grid_menu_item.dart create mode 100644 frontend/app_flowy/lib/plugins/document/presentation/plugins/grid/grid_node_widget.dart diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index 4034ff517c..caafceee93 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -317,7 +317,10 @@ }, "slashMenu": { "board": { - "selectABoardToLinkTo": "Select a board to link to" + "selectABoardToLinkTo": "Select a Board to link to" + }, + "grid": { + "selectAGridToLinkTo": "Select a Grid to link to" } } }, diff --git a/frontend/app_flowy/lib/plugins/document/document_page.dart b/frontend/app_flowy/lib/plugins/document/document_page.dart index 76b6e9c1d3..9e9bd432ad 100644 --- a/frontend/app_flowy/lib/plugins/document/document_page.dart +++ b/frontend/app_flowy/lib/plugins/document/document_page.dart @@ -1,5 +1,7 @@ import 'package:app_flowy/plugins/document/presentation/plugins/board/board_menu_item.dart'; import 'package:app_flowy/plugins/document/presentation/plugins/board/board_node_widget.dart'; +import 'package:app_flowy/plugins/document/presentation/plugins/grid/grid_menu_item.dart'; +import 'package:app_flowy/plugins/document/presentation/plugins/grid/grid_node_widget.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'; @@ -111,6 +113,8 @@ class _DocumentPageState extends State { kCodeBlockType: CodeBlockNodeWidgetBuilder(), // Board kBoardType: BoardNodeWidgetBuilder(), + // Grid + kGridType: GridNodeWidgetBuilder(), // Card kCalloutType: CalloutNodeWidgetBuilder(), }, @@ -133,6 +137,8 @@ class _DocumentPageState extends State { emojiMenuItem, // Board boardMenuItem, + // Grid + gridMenuItem, ], themeData: theme.copyWith(extensions: [ ...theme.extensions.values, diff --git a/frontend/app_flowy/lib/plugins/document/editor_styles.dart b/frontend/app_flowy/lib/plugins/document/editor_styles.dart index 0220621f34..b8306a8f74 100644 --- a/frontend/app_flowy/lib/plugins/document/editor_styles.dart +++ b/frontend/app_flowy/lib/plugins/document/editor_styles.dart @@ -9,7 +9,7 @@ EditorStyle customEditorTheme(BuildContext context) { ? EditorStyle.dark : EditorStyle.light; editorStyle = editorStyle.copyWith( - padding: const EdgeInsets.symmetric(horizontal: 40), + padding: const EdgeInsets.symmetric(horizontal: 100), textStyle: editorStyle.textStyle?.copyWith( fontFamily: 'poppins', fontSize: documentStyle.fontSize, diff --git a/frontend/app_flowy/lib/plugins/document/presentation/plugins/base/built_in_page_widget.dart b/frontend/app_flowy/lib/plugins/document/presentation/plugins/base/built_in_page_widget.dart new file mode 100644 index 0000000000..88b42c48d4 --- /dev/null +++ b/frontend/app_flowy/lib/plugins/document/presentation/plugins/base/built_in_page_widget.dart @@ -0,0 +1,160 @@ +import 'package:app_flowy/plugins/document/presentation/plugins/base/insert_page_command.dart'; +import 'package:app_flowy/startup/startup.dart'; +import 'package:app_flowy/workspace/application/app/app_service.dart'; +import 'package:app_flowy/workspace/application/view/view_ext.dart'; +import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; +import 'package:app_flowy/workspace/presentation/home/menu/menu.dart'; +import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:dartz/dartz.dart' as dartz; +import 'package:flowy_infra_ui/style_widget/icon_button.dart'; +import 'package:flutter/material.dart'; +import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/image.dart'; + +class BuiltInPageWidget extends StatefulWidget { + const BuiltInPageWidget({ + Key? key, + required this.node, + required this.editorState, + required this.builder, + }) : super(key: key); + + final Node node; + final EditorState editorState; + final Widget Function(ViewPB viewPB) builder; + + @override + State createState() => _BuiltInPageWidgetState(); +} + +class _BuiltInPageWidgetState extends State { + final focusNode = FocusNode(); + + String get gridID { + return widget.node.attributes[kViewID]; + } + + String get appID { + return widget.node.attributes[kAppID]; + } + + @override + Widget build(BuildContext context) { + return FutureBuilder>( + builder: (context, snapshot) { + if (snapshot.hasData) { + final board = snapshot.data?.getLeftOrNull(); + if (board != null) { + return _build(context, board); + } + } + return const Center( + child: CircularProgressIndicator(), + ); + }, + future: AppService().getView(appID, gridID), + ); + } + + @override + void dispose() { + focusNode.dispose(); + super.dispose(); + } + + Widget _build(BuildContext context, ViewPB viewPB) { + return MouseRegion( + onEnter: (event) { + widget.editorState.service.scrollService?.disable(); + }, + onExit: (event) { + widget.editorState.service.scrollService?.enable(); + }, + child: SizedBox( + height: 400, + child: Stack( + children: [ + _buildMenu(context, viewPB), + _buildGrid(context, viewPB), + ], + ), + ), + ); + } + + Widget _buildGrid(BuildContext context, ViewPB viewPB) { + return Focus( + focusNode: focusNode, + onFocusChange: (value) { + if (value) { + widget.editorState.service.selectionService.clearSelection(); + } + }, + child: widget.builder(viewPB), + ); + } + + Widget _buildMenu(BuildContext context, ViewPB viewPB) { + return Positioned( + top: 5, + left: 5, + child: PopoverActionList<_ActionWrapper>( + direction: PopoverDirection.bottomWithCenterAligned, + actions: + _ActionType.values.map((action) => _ActionWrapper(action)).toList(), + buildChild: (controller) { + return FlowyIconButton( + tooltipText: LocaleKeys.tooltip_openMenu.tr(), + width: 25, + height: 30, + iconPadding: const EdgeInsets.all(3), + icon: svgWidget('editor/details'), + onPressed: () => controller.show(), + ); + }, + onSelected: (action, controller) async { + switch (action.inner) { + case _ActionType.openAsPage: + getIt().latestOpenView = viewPB; + getIt().setPlugin(viewPB.plugin()); + break; + case _ActionType.delete: + final transaction = widget.editorState.transaction; + transaction.deleteNode(widget.node); + widget.editorState.apply(transaction); + break; + } + controller.close(); + }, + ), + ); + } +} + +enum _ActionType { + openAsPage, + delete, +} + +class _ActionWrapper extends ActionCell { + final _ActionType inner; + + _ActionWrapper(this.inner); + + Widget? icon(Color iconColor) => null; + + @override + String get name { + switch (inner) { + case _ActionType.openAsPage: + return LocaleKeys.tooltip_openAsPage.tr(); + case _ActionType.delete: + return LocaleKeys.disclosureAction_delete.tr(); + } + } +} diff --git a/frontend/app_flowy/lib/plugins/document/presentation/plugins/base/insert_page_command.dart b/frontend/app_flowy/lib/plugins/document/presentation/plugins/base/insert_page_command.dart new file mode 100644 index 0000000000..e07122da66 --- /dev/null +++ b/frontend/app_flowy/lib/plugins/document/presentation/plugins/base/insert_page_command.dart @@ -0,0 +1,42 @@ +import 'package:app_flowy/plugins/document/presentation/plugins/board/board_node_widget.dart'; +import 'package:app_flowy/plugins/document/presentation/plugins/grid/grid_node_widget.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/app.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; + +const String kAppID = 'app_id'; +const String kViewID = 'view_id'; + +extension InsertPage on EditorState { + void insertPage(AppPB appPB, ViewPB viewPB) { + final selection = service.selectionService.currentSelection.value; + final textNodes = + service.selectionService.currentSelectedNodes.whereType(); + if (selection == null || textNodes.isEmpty) { + return; + } + final transaction = this.transaction; + transaction.insertNode( + selection.end.path, + Node( + type: _convertPageType(viewPB), + attributes: { + kAppID: appPB.id, + kViewID: viewPB.id, + }, + ), + ); + apply(transaction); + } + + String _convertPageType(ViewPB viewPB) { + switch (viewPB.layout) { + case ViewLayoutTypePB.Grid: + return kGridType; + case ViewLayoutTypePB.Board: + return kBoardType; + default: + throw Exception('Unknown layout type'); + } + } +} diff --git a/frontend/app_flowy/lib/plugins/document/presentation/plugins/base/link_to_page_widget.dart b/frontend/app_flowy/lib/plugins/document/presentation/plugins/base/link_to_page_widget.dart new file mode 100644 index 0000000000..96f0dd45eb --- /dev/null +++ b/frontend/app_flowy/lib/plugins/document/presentation/plugins/base/link_to_page_widget.dart @@ -0,0 +1,186 @@ +import 'package:app_flowy/workspace/application/app/app_service.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/app.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:dartz/dartz.dart' as dartz; +import 'package:flowy_infra/image.dart'; +import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flutter/material.dart'; +import 'insert_page_command.dart'; +import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; + +EditorState? _editorState; +OverlayEntry? _linkToPageMenu; + +void showLinkToPageMenu( + EditorState editorState, + SelectionMenuService menuService, + BuildContext context, + ViewLayoutTypePB pageType, +) { + final aligment = menuService.alignment; + final offset = menuService.offset; + menuService.dismiss(); + + _editorState = editorState; + + String hintText = ''; + switch (pageType) { + case ViewLayoutTypePB.Grid: + hintText = LocaleKeys.document_slashMenu_grid_selectAGridToLinkTo.tr(); + break; + case ViewLayoutTypePB.Board: + hintText = LocaleKeys.document_slashMenu_board_selectABoardToLinkTo.tr(); + break; + default: + throw Exception('Unknown layout type'); + } + + _linkToPageMenu?.remove(); + _linkToPageMenu = OverlayEntry(builder: (context) { + return Positioned( + top: aligment == Alignment.bottomLeft ? offset.dy : null, + bottom: aligment == Alignment.topLeft ? offset.dy : null, + left: offset.dx, + child: Material( + color: Colors.transparent, + child: LinkToPageMenu( + editorState: editorState, + layoutType: pageType, + hintText: hintText, + onSelected: (appPB, viewPB) { + editorState.insertPage(appPB, viewPB); + }, + ), + ), + ); + }); + + Overlay.of(context)?.insert(_linkToPageMenu!); + + editorState.service.selectionService.currentSelection + .addListener(dismissLinkToPageMenu); +} + +void dismissLinkToPageMenu() { + _linkToPageMenu?.remove(); + _linkToPageMenu = null; + + _editorState?.service.selectionService.currentSelection + .removeListener(dismissLinkToPageMenu); + _editorState = null; +} + +class LinkToPageMenu extends StatefulWidget { + const LinkToPageMenu({ + super.key, + required this.editorState, + required this.layoutType, + required this.hintText, + required this.onSelected, + }); + + final EditorState editorState; + final ViewLayoutTypePB layoutType; + final String hintText; + final void Function(AppPB appPB, ViewPB viewPB) onSelected; + + @override + State createState() => _LinkToPageMenuState(); +} + +class _LinkToPageMenuState extends State { + EditorStyle get style => widget.editorState.editorStyle; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.transparent, + width: 300, + child: Container( + padding: const EdgeInsets.fromLTRB(10, 6, 10, 6), + decoration: BoxDecoration( + color: style.selectionMenuBackgroundColor, + boxShadow: [ + BoxShadow( + blurRadius: 5, + spreadRadius: 1, + color: Colors.black.withOpacity(0.1), + ), + ], + borderRadius: BorderRadius.circular(6.0), + ), + child: _buildListWidget(context), + ), + ); + } + + Widget _buildListWidget(BuildContext context) { + return FutureBuilder>>>( + builder: (context, snapshot) { + if (snapshot.hasData && + snapshot.connectionState == ConnectionState.done) { + final apps = snapshot.data; + final children = [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: FlowyText.regular( + widget.hintText, + fontSize: 10, + color: Colors.grey, + ), + ), + ]; + if (apps != null && apps.isNotEmpty) { + for (final app in apps) { + if (app.value2.isNotEmpty) { + children.add( + Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: FlowyText.regular( + app.value1.name, + ), + ), + ); + for (final value in app.value2) { + children.add( + FlowyButton( + leftIcon: svgWidget( + _iconName(value), + color: Theme.of(context).colorScheme.onSurface, + ), + text: FlowyText.regular(value.name), + onTap: () => widget.onSelected(app.value1, value), + ), + ); + } + } + } + } + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: children, + ); + } else { + return const Center( + child: CircularProgressIndicator(), + ); + } + }, + future: AppService().fetchViews(widget.layoutType), + ); + } + + String _iconName(ViewPB viewPB) { + switch (viewPB.layout) { + case ViewLayoutTypePB.Grid: + return 'editor/grid'; + case ViewLayoutTypePB.Board: + return 'editor/board'; + default: + throw Exception('Unknown layout type'); + } + } +} diff --git a/frontend/app_flowy/lib/plugins/document/presentation/plugins/board/board_menu_item.dart b/frontend/app_flowy/lib/plugins/document/presentation/plugins/board/board_menu_item.dart index f6eda00778..6a757051b8 100644 --- a/frontend/app_flowy/lib/plugins/document/presentation/plugins/board/board_menu_item.dart +++ b/frontend/app_flowy/lib/plugins/document/presentation/plugins/board/board_menu_item.dart @@ -1,14 +1,9 @@ import 'package:app_flowy/generated/locale_keys.g.dart'; -import 'package:app_flowy/plugins/document/presentation/plugins/board/board_node_widget.dart'; -import 'package:app_flowy/workspace/application/app/app_service.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/app.pb.dart'; +import 'package:app_flowy/plugins/document/presentation/plugins/base/link_to_page_widget.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:dartz/dartz.dart' as dartz; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; SelectionMenuItem boardMenuItem = SelectionMenuItem( @@ -22,174 +17,13 @@ SelectionMenuItem boardMenuItem = SelectionMenuItem( : editorState.editorStyle.selectionMenuItemIconColor, ); }, - keywords: ['board'], - handler: _showLinkToPageMenu, + keywords: ['board', 'kanban'], + handler: (editorState, menuService, context) { + showLinkToPageMenu( + editorState, + menuService, + context, + ViewLayoutTypePB.Board, + ); + }, ); - -EditorState? _editorState; -OverlayEntry? _linkToPageMenu; -void _dismissLinkToPageMenu() { - _linkToPageMenu?.remove(); - _linkToPageMenu = null; - - _editorState?.service.selectionService.currentSelection - .removeListener(_dismissLinkToPageMenu); - _editorState = null; -} - -void _showLinkToPageMenu( - EditorState editorState, - SelectionMenuService menuService, - BuildContext context, -) { - final aligment = menuService.alignment; - final offset = menuService.offset; - menuService.dismiss(); - - _editorState = editorState; - - _linkToPageMenu?.remove(); - _linkToPageMenu = OverlayEntry(builder: (context) { - return Positioned( - top: aligment == Alignment.bottomLeft ? offset.dy : null, - bottom: aligment == Alignment.topLeft ? offset.dy : null, - left: offset.dx, - child: Material( - color: Colors.transparent, - child: LinkToPageMenu( - editorState: editorState, - ), - ), - ); - }); - - Overlay.of(context)?.insert(_linkToPageMenu!); - - editorState.service.selectionService.currentSelection - .addListener(_dismissLinkToPageMenu); -} - -class LinkToPageMenu extends StatefulWidget { - final EditorState editorState; - - const LinkToPageMenu({ - super.key, - required this.editorState, - }); - - @override - State createState() => _LinkToPageMenuState(); -} - -class _LinkToPageMenuState extends State { - EditorStyle get style => widget.editorState.editorStyle; - - @override - Widget build(BuildContext context) { - return Container( - color: Colors.transparent, - width: 300, - child: Container( - padding: const EdgeInsets.fromLTRB(10, 6, 10, 6), - decoration: BoxDecoration( - color: style.selectionMenuBackgroundColor, - boxShadow: [ - BoxShadow( - blurRadius: 5, - spreadRadius: 1, - color: Colors.black.withOpacity(0.1), - ), - ], - borderRadius: BorderRadius.circular(6.0), - ), - child: _buildBoardListWidget(context), - ), - ); - } - - Future>>> fetchBoards() async { - return AppService().fetchViews(ViewLayoutTypePB.Board); - } - - Widget _buildBoardListWidget(BuildContext context) { - return FutureBuilder>>>( - builder: (context, snapshot) { - if (snapshot.hasData && - snapshot.connectionState == ConnectionState.done) { - final apps = snapshot.data; - final children = [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: FlowyText.regular( - LocaleKeys.document_slashMenu_board_selectABoardToLinkTo.tr(), - fontSize: 10, - color: Colors.grey, - ), - ), - ]; - if (apps != null && apps.isNotEmpty) { - for (final app in apps) { - if (app.value2.isNotEmpty) { - children.add( - Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: FlowyText.regular( - app.value1.name, - ), - ), - ); - for (final board in app.value2) { - children.add( - FlowyButton( - leftIcon: svgWidget( - 'editor/board', - color: Theme.of(context).colorScheme.onSurface, - ), - text: FlowyText.regular(board.name), - onTap: () => widget.editorState.insertBoard( - app.value1, - board, - ), - ), - ); - } - } - } - } - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: children, - ); - } else { - return const Center( - child: CircularProgressIndicator(), - ); - } - }, - future: fetchBoards(), - ); - } -} - -extension on EditorState { - void insertBoard(AppPB appPB, ViewPB viewPB) { - final selection = service.selectionService.currentSelection.value; - final textNodes = - service.selectionService.currentSelectedNodes.whereType(); - if (selection == null || textNodes.isEmpty) { - return; - } - final transaction = this.transaction; - transaction.insertNode( - selection.end.path, - Node( - type: kBoardType, - attributes: { - kAppID: appPB.id, - kBoardID: viewPB.id, - }, - ), - ); - apply(transaction); - } -} diff --git a/frontend/app_flowy/lib/plugins/document/presentation/plugins/board/board_node_widget.dart b/frontend/app_flowy/lib/plugins/document/presentation/plugins/board/board_node_widget.dart index 9a8b1884b6..d21fe6cb43 100644 --- a/frontend/app_flowy/lib/plugins/document/presentation/plugins/board/board_node_widget.dart +++ b/frontend/app_flowy/lib/plugins/document/presentation/plugins/board/board_node_widget.dart @@ -1,19 +1,10 @@ import 'package:app_flowy/plugins/board/presentation/board_page.dart'; -import 'package:app_flowy/startup/startup.dart'; -import 'package:app_flowy/workspace/application/app/app_service.dart'; -import 'package:app_flowy/workspace/application/view/view_ext.dart'; -import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; -import 'package:app_flowy/workspace/presentation/home/menu/menu.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:app_flowy/plugins/document/presentation/plugins/base/built_in_page_widget.dart'; +import 'package:app_flowy/plugins/document/presentation/plugins/base/insert_page_command.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:dartz/dartz.dart' as dartz; -import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flutter/material.dart'; const String kBoardType = 'board'; -const String kAppID = 'app_id'; -const String kBoardID = 'board_id'; class BoardNodeWidgetBuilder extends NodeWidgetBuilder { @override @@ -27,7 +18,7 @@ class BoardNodeWidgetBuilder extends NodeWidgetBuilder { @override NodeValidator get nodeValidator => (node) { - return node.attributes[kBoardID] is String && + return node.attributes[kViewID] is String && node.attributes[kAppID] is String; }; } @@ -46,130 +37,18 @@ class _BoardWidget extends StatefulWidget { State<_BoardWidget> createState() => _BoardWidgetState(); } -class _BoardWidgetState extends State<_BoardWidget> with SelectableMixin { - RenderBox get _renderBox => context.findRenderObject() as RenderBox; - - String get boardID { - return widget.node.attributes[kBoardID]; - } - - String get appID { - return widget.node.attributes[kAppID]; - } - - late Future> board; - - @override - void initState() { - super.initState(); - - board = _fetchBoard(); - } - +class _BoardWidgetState extends State<_BoardWidget> { @override Widget build(BuildContext context) { - return FutureBuilder>( - builder: (context, snapshot) { - if (snapshot.hasData) { - final board = snapshot.data?.getLeftOrNull(); - if (board != null) { - return _buildBoard(context, board); - } - } - return const Center( - child: CircularProgressIndicator(), + return BuiltInPageWidget( + node: widget.node, + editorState: widget.editorState, + builder: (viewPB) { + return BoardPage( + key: ValueKey(viewPB.id), + view: viewPB, ); }, - future: board, ); } - - Future> _fetchBoard() async { - return AppService().getView(appID, boardID); - } - - Widget _buildBoard(BuildContext context, ViewPB viewPB) { - return MouseRegion( - onHover: (event) { - if (widget.node.isSelected(widget.editorState)) { - widget.editorState.service.scrollService?.disable(); - } - }, - onExit: (event) { - widget.editorState.service.scrollService?.enable(); - }, - child: SizedBox( - height: 400, - child: Stack( - children: [ - Positioned( - top: 0, - left: 20, - child: FlowyTextButton( - viewPB.name, - onPressed: () { - getIt().latestOpenView = viewPB; - getIt().setPlugin(viewPB.plugin()); - }, - ), - ), - BoardPage( - key: ValueKey(viewPB.id), - view: viewPB, - onEditStateChanged: () { - /// Clear selection when the edit state changes, otherwise the editor will prevent the keyboard event when the board is in edit mode. - widget.editorState.service.selectionService.clearSelection(); - }, - ), - ], - ), - ), - ); - } - - @override - bool get shouldCursorBlink => false; - - @override - CursorStyle get cursorStyle => CursorStyle.borderLine; - - @override - Position start() { - return Position(path: widget.node.path, offset: 0); - } - - @override - Position end() { - return Position(path: widget.node.path, offset: 0); - } - - @override - Position getPositionInOffset(Offset start) { - return end(); - } - - @override - List getRectsInSelection(Selection selection) { - return [Offset.zero & _renderBox.size]; - } - - @override - Rect? getCursorRectInPosition(Position position) { - final size = _renderBox.size; - return Rect.fromLTWH(-size.width / 2.0, 0, size.width, size.height); - } - - @override - Selection getSelectionInRange(Offset start, Offset end) { - return Selection.single( - path: widget.node.path, - startOffset: 0, - endOffset: 0, - ); - } - - @override - Offset localToGlobal(Offset offset) { - return _renderBox.localToGlobal(offset); - } } diff --git a/frontend/app_flowy/lib/plugins/document/presentation/plugins/grid/grid_menu_item.dart b/frontend/app_flowy/lib/plugins/document/presentation/plugins/grid/grid_menu_item.dart new file mode 100644 index 0000000000..d7ab95cc05 --- /dev/null +++ b/frontend/app_flowy/lib/plugins/document/presentation/plugins/grid/grid_menu_item.dart @@ -0,0 +1,29 @@ +import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'package:app_flowy/plugins/document/presentation/plugins/base/link_to_page_widget.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/image.dart'; +import 'package:flutter/material.dart'; + +SelectionMenuItem gridMenuItem = SelectionMenuItem( + name: () => LocaleKeys.grid_menuName.tr(), + icon: (editorState, onSelected) { + return svgWidget( + 'editor/grid', + size: const Size.square(18.0), + color: onSelected + ? editorState.editorStyle.selectionMenuItemSelectedIconColor + : editorState.editorStyle.selectionMenuItemIconColor, + ); + }, + keywords: ['grid'], + handler: (editorState, menuService, context) { + showLinkToPageMenu( + editorState, + menuService, + context, + ViewLayoutTypePB.Grid, + ); + }, +); diff --git a/frontend/app_flowy/lib/plugins/document/presentation/plugins/grid/grid_node_widget.dart b/frontend/app_flowy/lib/plugins/document/presentation/plugins/grid/grid_node_widget.dart new file mode 100644 index 0000000000..04288bced4 --- /dev/null +++ b/frontend/app_flowy/lib/plugins/document/presentation/plugins/grid/grid_node_widget.dart @@ -0,0 +1,54 @@ +import 'package:app_flowy/plugins/document/presentation/plugins/base/built_in_page_widget.dart'; +import 'package:app_flowy/plugins/document/presentation/plugins/base/insert_page_command.dart'; +import 'package:app_flowy/plugins/grid/presentation/grid_page.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +const String kGridType = 'grid'; + +class GridNodeWidgetBuilder extends NodeWidgetBuilder { + @override + Widget build(NodeWidgetContext context) { + return _GridWidget( + key: context.node.key, + node: context.node, + editorState: context.editorState, + ); + } + + @override + NodeValidator get nodeValidator => (node) { + return node.attributes[kAppID] is String && + node.attributes[kViewID] is String; + }; +} + +class _GridWidget extends StatefulWidget { + const _GridWidget({ + Key? key, + required this.node, + required this.editorState, + }) : super(key: key); + + final Node node; + final EditorState editorState; + + @override + State<_GridWidget> createState() => _GridWidgetState(); +} + +class _GridWidgetState extends State<_GridWidget> { + @override + Widget build(BuildContext context) { + return BuiltInPageWidget( + node: widget.node, + editorState: widget.editorState, + builder: (viewPB) { + return GridPage( + key: ValueKey(viewPB.id), + view: viewPB, + ); + }, + ); + } +} diff --git a/frontend/app_flowy/lib/plugins/document/presentation/share/share_button.dart b/frontend/app_flowy/lib/plugins/document/presentation/share/share_button.dart index 7564141e07..d89e2dfc47 100644 --- a/frontend/app_flowy/lib/plugins/document/presentation/share/share_button.dart +++ b/frontend/app_flowy/lib/plugins/document/presentation/share/share_button.dart @@ -105,8 +105,8 @@ class ShareActionList extends StatelessWidget { break; case ShareAction.copyLink: NavigatorAlertDialog( - title: LocaleKeys.shareAction_workInProgress.tr()) - .show(context); + title: LocaleKeys.shareAction_workInProgress.tr(), + ).show(context); break; } controller.close(); @@ -128,5 +128,12 @@ class ShareActionWrapper extends ActionCell { Widget? icon(Color iconColor) => null; @override - String get name => inner.name; + String get name { + switch (inner) { + case ShareAction.markdown: + return LocaleKeys.shareAction_markdown.tr(); + case ShareAction.copyLink: + return LocaleKeys.shareAction_copyLink.tr(); + } + } } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart b/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart index d7237dac8d..b1d6660de1 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart @@ -31,10 +31,6 @@ import 'widgets/shortcuts.dart'; import 'widgets/toolbar/grid_toolbar.dart'; class GridPage extends StatefulWidget { - final ViewPB view; - final GridController gridController; - final VoidCallback? onDeleted; - GridPage({ required this.view, this.onDeleted, @@ -42,6 +38,10 @@ class GridPage extends StatefulWidget { }) : gridController = GridController(view: view), super(key: key); + final ViewPB view; + final GridController gridController; + final VoidCallback? onDeleted; + @override State createState() => _GridPageState(); } diff --git a/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart b/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart index 2c7983fb25..7d1cd4e249 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart @@ -174,9 +174,13 @@ class HomeStackManager { index: getIt().indexOf(notifier.plugin.ty), children: getIt().supportPluginTypes.map((pluginType) { if (pluginType == notifier.plugin.ty) { - return notifier.plugin.display - .buildWidget(PluginContext(onDeleted: onDeleted)) - .padding(horizontal: 40, vertical: 28); + final pluginWidget = notifier.plugin.display + .buildWidget(PluginContext(onDeleted: onDeleted)); + if (pluginType == PluginType.editor) { + return pluginWidget; + } else { + return pluginWidget.padding(horizontal: 40, vertical: 28); + } } else { return const BlankPage(); }