mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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
This commit is contained in:
parent
71022ed934
commit
2e91dfb4be
@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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<DocumentPage> {
|
||||
kCodeBlockType: CodeBlockNodeWidgetBuilder(),
|
||||
// Board
|
||||
kBoardType: BoardNodeWidgetBuilder(),
|
||||
// Grid
|
||||
kGridType: GridNodeWidgetBuilder(),
|
||||
// Card
|
||||
kCalloutType: CalloutNodeWidgetBuilder(),
|
||||
},
|
||||
@ -133,6 +137,8 @@ class _DocumentPageState extends State<DocumentPage> {
|
||||
emojiMenuItem,
|
||||
// Board
|
||||
boardMenuItem,
|
||||
// Grid
|
||||
gridMenuItem,
|
||||
],
|
||||
themeData: theme.copyWith(extensions: [
|
||||
...theme.extensions.values,
|
||||
|
@ -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,
|
||||
|
@ -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<BuiltInPageWidget> createState() => _BuiltInPageWidgetState();
|
||||
}
|
||||
|
||||
class _BuiltInPageWidgetState extends State<BuiltInPageWidget> {
|
||||
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<dartz.Either<ViewPB, FlowyError>>(
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final board = snapshot.data?.getLeftOrNull<ViewPB>();
|
||||
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<MenuSharedState>().latestOpenView = viewPB;
|
||||
getIt<HomeStackManager>().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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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<TextNode>();
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
@ -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<LinkToPageMenu> createState() => _LinkToPageMenuState();
|
||||
}
|
||||
|
||||
class _LinkToPageMenuState extends State<LinkToPageMenu> {
|
||||
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<List<dartz.Tuple2<AppPB, List<ViewPB>>>>(
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData &&
|
||||
snapshot.connectionState == ConnectionState.done) {
|
||||
final apps = snapshot.data;
|
||||
final children = <Widget>[
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
@ -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<LinkToPageMenu> createState() => _LinkToPageMenuState();
|
||||
}
|
||||
|
||||
class _LinkToPageMenuState extends State<LinkToPageMenu> {
|
||||
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<List<dartz.Tuple2<AppPB, List<ViewPB>>>> fetchBoards() async {
|
||||
return AppService().fetchViews(ViewLayoutTypePB.Board);
|
||||
}
|
||||
|
||||
Widget _buildBoardListWidget(BuildContext context) {
|
||||
return FutureBuilder<List<dartz.Tuple2<AppPB, List<ViewPB>>>>(
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData &&
|
||||
snapshot.connectionState == ConnectionState.done) {
|
||||
final apps = snapshot.data;
|
||||
final children = <Widget>[
|
||||
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<TextNode>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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<Node> {
|
||||
@override
|
||||
@ -27,7 +18,7 @@ class BoardNodeWidgetBuilder extends NodeWidgetBuilder<Node> {
|
||||
|
||||
@override
|
||||
NodeValidator<Node> 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<dartz.Either<ViewPB, FlowyError>> board;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
board = _fetchBoard();
|
||||
}
|
||||
|
||||
class _BoardWidgetState extends State<_BoardWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<dartz.Either<ViewPB, FlowyError>>(
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final board = snapshot.data?.getLeftOrNull<ViewPB>();
|
||||
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<dartz.Either<ViewPB, FlowyError>> _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<MenuSharedState>().latestOpenView = viewPB;
|
||||
getIt<HomeStackManager>().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<Rect> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
@ -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<Node> {
|
||||
@override
|
||||
Widget build(NodeWidgetContext<Node> context) {
|
||||
return _GridWidget(
|
||||
key: context.node.key,
|
||||
node: context.node,
|
||||
editorState: context.editorState,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
NodeValidator<Node> 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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<GridPage> createState() => _GridPageState();
|
||||
}
|
||||
|
@ -174,9 +174,13 @@ class HomeStackManager {
|
||||
index: getIt<PluginSandbox>().indexOf(notifier.plugin.ty),
|
||||
children: getIt<PluginSandbox>().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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user