From 62d5d66d2018a4445025f4efd25f839a7f00fb1a Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 11 Dec 2024 16:37:28 +0800 Subject: [PATCH] feat: set table to page width (#6956) * feat: set table to page width * feat: expand the table based on the widht percentage * test: set to page width * feat: distribute columns evenly * test: distribute columns evenly * fix: border width --- .../document_with_simple_table_test.dart | 75 ++++++ .../simple_table/_shared_widget.dart | 251 ------------------ .../simple_table/simple_table.dart | 4 +- .../simple_table_cell_block_component.dart | 2 - .../simple_table/simple_table_constants.dart | 4 + .../simple_table_more_action.dart | 110 +++++--- .../simple_table_node_extension.dart | 10 + .../simple_table_style_operation.dart | 85 ++++++ .../simple_table_row_block_component.dart | 3 +- .../simple_table_align_button.dart | 81 ++++++ .../simple_table_background_menu.dart | 104 ++++++++ .../simple_table_basic_button.dart | 50 ++++ .../simple_table_border_builder.dart | 28 +- .../simple_table_divider.dart | 28 ++ .../simple_table_reorder_button.dart | 1 - .../simple_table_widget.dart | 1 - .../simple_table_widgets/widgets.dart | 11 + .../flowy_icons/16x/table_clear_content.svg | 11 +- .../16x/table_distribute_columns_evenly.svg | 9 + .../flowy_icons/16x/table_insert_above.svg | 5 +- .../flowy_icons/16x/table_insert_below.svg | 5 +- .../flowy_icons/16x/table_insert_left.svg | 7 +- .../flowy_icons/16x/table_insert_right.svg | 7 +- .../16x/table_set_to_page_width.svg | 3 + frontend/resources/translations/en.json | 6 +- 25 files changed, 577 insertions(+), 324 deletions(-) delete mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/_shared_widget.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_align_button.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_background_menu.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_basic_button.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_divider.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/widgets.dart create mode 100644 frontend/resources/flowy_icons/16x/table_distribute_columns_evenly.svg create mode 100644 frontend/resources/flowy_icons/16x/table_set_to_page_width.svg diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_simple_table_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_simple_table_test.dart index 3f1f53655c..df473cc057 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_simple_table_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_simple_table_test.dart @@ -332,6 +332,81 @@ void main() { expect(tableNode.columnLength, 2); }); }); + + testWidgets('set column width to page width (1)', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + await tester.createNewPageWithNameUnderParent( + name: 'simple_table_test', + ); + + await tester.editor.tapLineOfEditorAt(0); + await tester.insertTableInDocument(); + + final tableNode = tester.editor.getNodeAtPath([0]); + final beforeWidth = tableNode.width; + + // set the column width to page width + await tester.clickMoreActionItemInTableMenu( + type: SimpleTableMoreActionType.column, + index: 0, + action: SimpleTableMoreAction.setToPageWidth, + ); + await tester.pumpAndSettle(); + + final afterWidth = tableNode.width; + expect(afterWidth, greaterThan(beforeWidth)); + }); + + testWidgets('set column width to page width (2)', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + await tester.createNewPageWithNameUnderParent( + name: 'simple_table_test', + ); + + await tester.editor.tapLineOfEditorAt(0); + await tester.insertTableInDocument(); + + final tableNode = tester.editor.getNodeAtPath([0]); + final beforeWidth = tableNode.width; + + // set the column width to page width + await tester.clickMoreActionItemInTableMenu( + type: SimpleTableMoreActionType.row, + index: 0, + action: SimpleTableMoreAction.setToPageWidth, + ); + await tester.pumpAndSettle(); + + final afterWidth = tableNode.width; + expect(afterWidth, greaterThan(beforeWidth)); + }); + + testWidgets('distribute columns evenly (1)', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + await tester.createNewPageWithNameUnderParent( + name: 'simple_table_test', + ); + + await tester.editor.tapLineOfEditorAt(0); + await tester.insertTableInDocument(); + + final tableNode = tester.editor.getNodeAtPath([0]); + final beforeWidth = tableNode.width; + + // set the column width to page width + await tester.clickMoreActionItemInTableMenu( + type: SimpleTableMoreActionType.row, + index: 0, + action: SimpleTableMoreAction.distributeColumnsEvenly, + ); + await tester.pumpAndSettle(); + + final afterWidth = tableNode.width; + expect(afterWidth, equals(beforeWidth)); + }); } extension on WidgetTester { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/_shared_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/_shared_widget.dart deleted file mode 100644 index 2855ac6f43..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/_shared_widget.dart +++ /dev/null @@ -1,251 +0,0 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -class SimpleTableRowDivider extends StatelessWidget { - const SimpleTableRowDivider({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return VerticalDivider( - color: context.simpleTableBorderColor, - width: 1.0, - ); - } -} - -class SimpleTableColumnDivider extends StatelessWidget { - const SimpleTableColumnDivider({super.key}); - - @override - Widget build(BuildContext context) { - return Divider( - color: context.simpleTableBorderColor, - height: 1.0, - ); - } -} - -class SimpleTableAlignMenu extends StatefulWidget { - const SimpleTableAlignMenu({ - super.key, - required this.type, - required this.tableCellNode, - this.mutex, - }); - - final SimpleTableMoreActionType type; - final Node tableCellNode; - final PopoverMutex? mutex; - - @override - State createState() => _SimpleTableAlignMenuState(); -} - -class _SimpleTableAlignMenuState extends State { - @override - Widget build(BuildContext context) { - final align = switch (widget.type) { - SimpleTableMoreActionType.column => widget.tableCellNode.columnAlign, - SimpleTableMoreActionType.row => widget.tableCellNode.rowAlign, - }; - return AppFlowyPopover( - mutex: widget.mutex, - child: SimpleTableBasicButton( - leftIconSvg: align.leftIconSvg, - text: LocaleKeys.document_plugins_simpleTable_moreActions_align.tr(), - onTap: () {}, - ), - popupBuilder: (popoverContext) { - void onClose() => PopoverContainer.of(popoverContext).closeAll(); - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildAlignButton(context, TableAlign.left, onClose), - _buildAlignButton(context, TableAlign.center, onClose), - _buildAlignButton(context, TableAlign.right, onClose), - ], - ); - }, - ); - } - - Widget _buildAlignButton( - BuildContext context, - TableAlign align, - VoidCallback onClose, - ) { - return SimpleTableBasicButton( - leftIconSvg: align.leftIconSvg, - text: align.name, - onTap: () { - switch (widget.type) { - case SimpleTableMoreActionType.column: - context.read().updateColumnAlign( - tableCellNode: widget.tableCellNode, - align: align, - ); - break; - case SimpleTableMoreActionType.row: - context.read().updateRowAlign( - tableCellNode: widget.tableCellNode, - align: align, - ); - break; - } - - onClose(); - }, - ); - } -} - -class SimpleTableBasicButton extends StatelessWidget { - const SimpleTableBasicButton({ - super.key, - required this.text, - required this.onTap, - this.leftIconSvg, - this.leftIconBuilder, - this.rightIcon, - }); - - final FlowySvgData? leftIconSvg; - final String text; - final VoidCallback onTap; - final Widget Function(bool onHover)? leftIconBuilder; - final Widget? rightIcon; - - @override - Widget build(BuildContext context) { - return Container( - height: SimpleTableConstants.moreActionHeight, - padding: SimpleTableConstants.moreActionPadding, - child: FlowyIconTextButton( - margin: SimpleTableConstants.moreActionHorizontalMargin, - leftIconBuilder: _buildLeftIcon, - iconPadding: 10.0, - textBuilder: (onHover) => FlowyText.regular( - text, - fontSize: 14.0, - figmaLineHeight: 18.0, - ), - onTap: onTap, - rightIconBuilder: (onHover) => rightIcon ?? const SizedBox.shrink(), - ), - ); - } - - Widget _buildLeftIcon(bool onHover) { - if (leftIconBuilder != null) { - return leftIconBuilder!(onHover); - } - return leftIconSvg != null - ? FlowySvg(leftIconSvg!) - : const SizedBox.shrink(); - } -} - -class SimpleTableBackgroundColorMenu extends StatefulWidget { - const SimpleTableBackgroundColorMenu({ - super.key, - required this.type, - required this.tableCellNode, - this.mutex, - }); - - final SimpleTableMoreActionType type; - final Node tableCellNode; - final PopoverMutex? mutex; - - @override - State createState() => - _SimpleTableBackgroundColorMenuState(); -} - -class _SimpleTableBackgroundColorMenuState - extends State { - @override - Widget build(BuildContext context) { - final theme = AFThemeExtension.of(context); - final backgroundColor = switch (widget.type) { - SimpleTableMoreActionType.row => - widget.tableCellNode.buildRowColor(context), - SimpleTableMoreActionType.column => - widget.tableCellNode.buildColumnColor(context), - }; - return AppFlowyPopover( - mutex: widget.mutex, - popupBuilder: (popoverContext) { - return _buildColorOptionMenu( - context, - theme: theme, - onClose: () => PopoverContainer.of(popoverContext).closeAll(), - ); - }, - direction: PopoverDirection.rightWithCenterAligned, - child: SimpleTableBasicButton( - leftIconBuilder: (onHover) => ColorOptionIcon( - color: backgroundColor ?? Colors.transparent, - ), - text: LocaleKeys.document_plugins_simpleTable_moreActions_color.tr(), - onTap: () {}, - ), - ); - } - - Widget _buildColorOptionMenu( - BuildContext context, { - required AFThemeExtension theme, - required VoidCallback onClose, - }) { - final colors = [ - // reset to default background color - FlowyColorOption( - color: Colors.transparent, - i18n: LocaleKeys.document_plugins_optionAction_defaultColor.tr(), - id: optionActionColorDefaultColor, - ), - ...FlowyTint.values.map( - (e) => FlowyColorOption( - color: e.color(context, theme: theme), - i18n: e.tintName(AppFlowyEditorL10n.current), - id: e.id, - ), - ), - ]; - - return FlowyColorPicker( - colors: colors, - border: Border.all( - color: theme.onBackground, - ), - onTap: (option, index) { - switch (widget.type) { - case SimpleTableMoreActionType.column: - context.read().updateColumnBackgroundColor( - tableCellNode: widget.tableCellNode, - color: option.id, - ); - break; - case SimpleTableMoreActionType.row: - context.read().updateRowBackgroundColor( - tableCellNode: widget.tableCellNode, - color: option.id, - ); - break; - } - - onClose(); - }, - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart index 06abc4a283..4454e9efaf 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart @@ -5,6 +5,4 @@ export 'simple_table_more_action.dart'; export 'simple_table_operations/simple_table_operations.dart'; export 'simple_table_row_block_component.dart'; export 'simple_table_shortcuts/simple_table_commands.dart'; -export 'simple_table_widgets/simple_table_add_column_and_row_button.dart'; -export 'simple_table_widgets/simple_table_add_column_button.dart'; -export 'simple_table_widgets/simple_table_add_row_button.dart'; +export 'simple_table_widgets/widgets.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart index 70e618a186..b9954499c3 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart @@ -1,6 +1,4 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_border_builder.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_column_resize_handle.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_constants.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_constants.dart index 9c0952daa4..ca82866730 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_constants.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_constants.dart @@ -1,4 +1,5 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart'; +import 'package:appflowy/plugins/document/presentation/editor_style.dart'; import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; @@ -190,6 +191,9 @@ class SimpleTableConstants { right: tableRightPadding, ); + static double get tablePageOffset => + EditorStyleCustomizer.optionMenuWidth + 12; + // Add row button static const addRowButtonHeight = 16.0; static const addRowButtonPadding = 4.0; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_more_action.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_more_action.dart index 380a62791e..4c682ca3b5 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_more_action.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_more_action.dart @@ -1,10 +1,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/_shared_widget.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_constants.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_operations.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_reorder_button.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart'; import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -16,29 +12,45 @@ enum SimpleTableMoreActionType { column, row; - List get actions { + List buildActions({ + required int index, + required int columnLength, + required int rowLength, + }) { + // there're two special cases: + // 1. if the table only contains one row or one column, remove the delete action + // 2. if the index is 0, add the enable header action switch (this) { case SimpleTableMoreActionType.row: return [ SimpleTableMoreAction.insertAbove, SimpleTableMoreAction.insertBelow, + SimpleTableMoreAction.divider, + if (index == 0) SimpleTableMoreAction.enableHeaderRow, + SimpleTableMoreAction.backgroundColor, + SimpleTableMoreAction.align, + SimpleTableMoreAction.divider, + SimpleTableMoreAction.setToPageWidth, + SimpleTableMoreAction.distributeColumnsEvenly, + SimpleTableMoreAction.divider, SimpleTableMoreAction.duplicate, SimpleTableMoreAction.clearContents, - SimpleTableMoreAction.delete, - SimpleTableMoreAction.divider, - SimpleTableMoreAction.align, - SimpleTableMoreAction.backgroundColor, + if (rowLength > 1) SimpleTableMoreAction.delete, ]; case SimpleTableMoreActionType.column: return [ SimpleTableMoreAction.insertLeft, SimpleTableMoreAction.insertRight, + SimpleTableMoreAction.divider, + if (index == 0) SimpleTableMoreAction.enableHeaderColumn, + SimpleTableMoreAction.backgroundColor, + SimpleTableMoreAction.align, + SimpleTableMoreAction.divider, + SimpleTableMoreAction.setToPageWidth, + SimpleTableMoreAction.divider, SimpleTableMoreAction.duplicate, SimpleTableMoreAction.clearContents, - SimpleTableMoreAction.delete, - SimpleTableMoreAction.divider, - SimpleTableMoreAction.align, - SimpleTableMoreAction.backgroundColor, + if (columnLength > 1) SimpleTableMoreAction.delete, ]; } } @@ -73,6 +85,8 @@ enum SimpleTableMoreAction { backgroundColor, enableHeaderColumn, enableHeaderRow, + setToPageWidth, + distributeColumnsEvenly, divider; String get name { @@ -99,6 +113,11 @@ enum SimpleTableMoreAction { LocaleKeys.document_plugins_simpleTable_moreActions_delete.tr(), SimpleTableMoreAction.duplicate => LocaleKeys.document_plugins_simpleTable_moreActions_duplicate.tr(), + SimpleTableMoreAction.setToPageWidth => + LocaleKeys.document_plugins_simpleTable_moreActions_setToPageWidth.tr(), + SimpleTableMoreAction.distributeColumnsEvenly => LocaleKeys + .document_plugins_simpleTable_moreActions_distributeColumnsWidth + .tr(), SimpleTableMoreAction.divider => throw UnimplementedError(), }; } @@ -112,6 +131,10 @@ enum SimpleTableMoreAction { SimpleTableMoreAction.duplicate => FlowySvgs.duplicate_s, SimpleTableMoreAction.clearContents => FlowySvgs.table_clear_content_s, SimpleTableMoreAction.delete => FlowySvgs.trash_s, + SimpleTableMoreAction.setToPageWidth => + FlowySvgs.table_set_to_page_width_s, + SimpleTableMoreAction.distributeColumnsEvenly => + FlowySvgs.table_distribute_columns_evenly_s, SimpleTableMoreAction.enableHeaderColumn => FlowySvgs.table_header_column_s, SimpleTableMoreAction.enableHeaderRow => FlowySvgs.table_header_row_s, @@ -375,7 +398,12 @@ class _SimpleTableMoreActionListState extends State { Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, - children: _buildActions() + children: widget.type + .buildActions( + index: widget.index, + columnLength: widget.tableCellNode.columnLength, + rowLength: widget.tableCellNode.rowLength, + ) .map( (action) => SimpleTableMoreActionItem( type: widget.type, @@ -387,34 +415,6 @@ class _SimpleTableMoreActionListState extends State { .toList(), ); } - - List _buildActions() { - final actions = widget.type.actions; - - // if the index is 0, add the divider and enable header action - if (widget.index == 0) { - actions.addAll([ - SimpleTableMoreAction.divider, - if (widget.type == SimpleTableMoreActionType.column) - SimpleTableMoreAction.enableHeaderColumn, - if (widget.type == SimpleTableMoreActionType.row) - SimpleTableMoreAction.enableHeaderRow, - ]); - } - - // if the table only contains one row or one column, remove the delete action - if (widget.tableCellNode.rowLength == 1 && - widget.type == SimpleTableMoreActionType.row) { - actions.remove(SimpleTableMoreAction.delete); - } - - if (widget.tableCellNode.columnLength == 1 && - widget.type == SimpleTableMoreActionType.column) { - actions.remove(SimpleTableMoreAction.delete); - } - - return actions; - } } class SimpleTableMoreActionItem extends StatefulWidget { @@ -568,6 +568,10 @@ class _SimpleTableMoreActionItemState extends State { _duplicateRow(); break; } + case SimpleTableMoreAction.setToPageWidth: + _setToPageWidth(); + case SimpleTableMoreAction.distributeColumnsEvenly: + _distributeColumnsEvenly(); default: break; } @@ -575,6 +579,26 @@ class _SimpleTableMoreActionItemState extends State { PopoverContainer.of(context).close(); } + void _setToPageWidth() { + final value = _getTableAndTableCellAndCellPosition(); + if (value == null) { + return; + } + final (table, _, _) = value; + final editorState = context.read(); + editorState.setColumnWidthToPageWidth(tableNode: table); + } + + void _distributeColumnsEvenly() { + final value = _getTableAndTableCellAndCellPosition(); + if (value == null) { + return; + } + final (table, _, _) = value; + final editorState = context.read(); + editorState.distributeColumnWidthToPageWidth(tableNode: table); + } + void _duplicateRow() { final value = _getTableAndTableCellAndCellPosition(); if (value == null) { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_node_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_node_extension.dart index ab95bdcf54..cc7686fa72 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_node_extension.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_node_extension.dart @@ -366,6 +366,16 @@ extension TableNodeExtension on Node { } } + double get width { + double currentColumnWidth = 0; + for (var i = 0; i < columnLength; i++) { + final columnWidth = + columnWidths[i.toString()] ?? SimpleTableConstants.defaultColumnWidth; + currentColumnWidth += columnWidth; + } + return currentColumnWidth; + } + /// Get the previous cell in the same column. If the row index is 0, it will return the same cell. Node? getPreviousCellInSameColumn() { assert(type == SimpleTableCellBlockKeys.type); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_style_operation.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_style_operation.dart index edcd30e608..0277ea3fb6 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_style_operation.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_style_operation.dart @@ -233,4 +233,89 @@ extension TableOptionOperation on EditorState { transaction.updateNode(parentTableNode, attributes); await apply(transaction); } + + /// Set the column width of the table to the page width. + /// + /// Example: + /// + /// Before: + /// | 0 | 1 | + /// | 3 | 4 | + /// + /// After: + /// | 0 | 1 | <- the column's width will be expanded based on the percentage of the page width + /// | 3 | 4 | + /// + /// This function will update the table width. + Future setColumnWidthToPageWidth({ + required Node tableNode, + }) async { + // Disable in mobile + if (UniversalPlatform.isMobile) { + return; + } + + final columnLength = tableNode.columnLength; + double? pageWidth = tableNode.renderBox?.size.width; + if (pageWidth == null) { + Log.warn('table node render box is null'); + return; + } + pageWidth -= SimpleTableConstants.tablePageOffset; + + final transaction = this.transaction; + final columnWidths = tableNode.columnWidths; + final ratio = pageWidth / tableNode.width; + for (var i = 0; i < columnLength; i++) { + final columnWidth = + columnWidths[i.toString()] ?? SimpleTableConstants.defaultColumnWidth; + columnWidths[i.toString()] = (columnWidth * ratio).clamp( + SimpleTableConstants.minimumColumnWidth, + double.infinity, + ); + } + transaction.updateNode(tableNode, { + SimpleTableBlockKeys.columnWidths: columnWidths, + }); + await apply(transaction); + } + + /// Distribute the column width of the table to the page width. + /// + /// Example: + /// + /// Before: + /// Before: + /// | 0 | 1 | + /// | 3 | 4 | + /// + /// After: + /// | 0 | 1 | <- the column's width will be expanded based on the percentage of the page width + /// | 3 | 4 | + /// + /// This function will not update table width. + Future distributeColumnWidthToPageWidth({ + required Node tableNode, + }) async { + // Disable in mobile + if (UniversalPlatform.isMobile) { + return; + } + + final columnLength = tableNode.columnLength; + final tableWidth = tableNode.width; + final columnWidth = (tableWidth / columnLength).clamp( + SimpleTableConstants.minimumColumnWidth, + double.infinity, + ); + final transaction = this.transaction; + final columnWidths = tableNode.columnWidths; + for (var i = 0; i < columnLength; i++) { + columnWidths[i.toString()] = columnWidth; + } + transaction.updateNode(tableNode, { + SimpleTableBlockKeys.columnWidths: columnWidths, + }); + await apply(transaction); + } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_row_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_row_block_component.dart index 3684beb9cd..9b7d6a5d16 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_row_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_row_block_component.dart @@ -1,5 +1,4 @@ -import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/_shared_widget.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_constants.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_align_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_align_button.dart new file mode 100644 index 0000000000..a042e632ea --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_align_button.dart @@ -0,0 +1,81 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class SimpleTableAlignMenu extends StatefulWidget { + const SimpleTableAlignMenu({ + super.key, + required this.type, + required this.tableCellNode, + this.mutex, + }); + + final SimpleTableMoreActionType type; + final Node tableCellNode; + final PopoverMutex? mutex; + + @override + State createState() => _SimpleTableAlignMenuState(); +} + +class _SimpleTableAlignMenuState extends State { + @override + Widget build(BuildContext context) { + final align = switch (widget.type) { + SimpleTableMoreActionType.column => widget.tableCellNode.columnAlign, + SimpleTableMoreActionType.row => widget.tableCellNode.rowAlign, + }; + return AppFlowyPopover( + mutex: widget.mutex, + child: SimpleTableBasicButton( + leftIconSvg: align.leftIconSvg, + text: LocaleKeys.document_plugins_simpleTable_moreActions_align.tr(), + onTap: () {}, + ), + popupBuilder: (popoverContext) { + void onClose() => PopoverContainer.of(popoverContext).closeAll(); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildAlignButton(context, TableAlign.left, onClose), + _buildAlignButton(context, TableAlign.center, onClose), + _buildAlignButton(context, TableAlign.right, onClose), + ], + ); + }, + ); + } + + Widget _buildAlignButton( + BuildContext context, + TableAlign align, + VoidCallback onClose, + ) { + return SimpleTableBasicButton( + leftIconSvg: align.leftIconSvg, + text: align.name, + onTap: () { + switch (widget.type) { + case SimpleTableMoreActionType.column: + context.read().updateColumnAlign( + tableCellNode: widget.tableCellNode, + align: align, + ); + break; + case SimpleTableMoreActionType.row: + context.read().updateRowAlign( + tableCellNode: widget.tableCellNode, + align: align, + ); + break; + } + + onClose(); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_background_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_background_menu.dart new file mode 100644 index 0000000000..ef55081a14 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_background_menu.dart @@ -0,0 +1,104 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class SimpleTableBackgroundColorMenu extends StatefulWidget { + const SimpleTableBackgroundColorMenu({ + super.key, + required this.type, + required this.tableCellNode, + this.mutex, + }); + + final SimpleTableMoreActionType type; + final Node tableCellNode; + final PopoverMutex? mutex; + + @override + State createState() => + _SimpleTableBackgroundColorMenuState(); +} + +class _SimpleTableBackgroundColorMenuState + extends State { + @override + Widget build(BuildContext context) { + final theme = AFThemeExtension.of(context); + final backgroundColor = switch (widget.type) { + SimpleTableMoreActionType.row => + widget.tableCellNode.buildRowColor(context), + SimpleTableMoreActionType.column => + widget.tableCellNode.buildColumnColor(context), + }; + return AppFlowyPopover( + mutex: widget.mutex, + popupBuilder: (popoverContext) { + return _buildColorOptionMenu( + context, + theme: theme, + onClose: () => PopoverContainer.of(popoverContext).closeAll(), + ); + }, + direction: PopoverDirection.rightWithCenterAligned, + child: SimpleTableBasicButton( + leftIconBuilder: (onHover) => ColorOptionIcon( + color: backgroundColor ?? Colors.transparent, + ), + text: LocaleKeys.document_plugins_simpleTable_moreActions_color.tr(), + onTap: () {}, + ), + ); + } + + Widget _buildColorOptionMenu( + BuildContext context, { + required AFThemeExtension theme, + required VoidCallback onClose, + }) { + final colors = [ + // reset to default background color + FlowyColorOption( + color: Colors.transparent, + i18n: LocaleKeys.document_plugins_optionAction_defaultColor.tr(), + id: optionActionColorDefaultColor, + ), + ...FlowyTint.values.map( + (e) => FlowyColorOption( + color: e.color(context, theme: theme), + i18n: e.tintName(AppFlowyEditorL10n.current), + id: e.id, + ), + ), + ]; + + return FlowyColorPicker( + colors: colors, + border: Border.all( + color: theme.onBackground, + ), + onTap: (option, index) { + switch (widget.type) { + case SimpleTableMoreActionType.column: + context.read().updateColumnBackgroundColor( + tableCellNode: widget.tableCellNode, + color: option.id, + ); + break; + case SimpleTableMoreActionType.row: + context.read().updateRowBackgroundColor( + tableCellNode: widget.tableCellNode, + color: option.id, + ); + break; + } + + onClose(); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_basic_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_basic_button.dart new file mode 100644 index 0000000000..f9df88ccf0 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_basic_button.dart @@ -0,0 +1,50 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +class SimpleTableBasicButton extends StatelessWidget { + const SimpleTableBasicButton({ + super.key, + required this.text, + required this.onTap, + this.leftIconSvg, + this.leftIconBuilder, + this.rightIcon, + }); + + final FlowySvgData? leftIconSvg; + final String text; + final VoidCallback onTap; + final Widget Function(bool onHover)? leftIconBuilder; + final Widget? rightIcon; + + @override + Widget build(BuildContext context) { + return Container( + height: SimpleTableConstants.moreActionHeight, + padding: SimpleTableConstants.moreActionPadding, + child: FlowyIconTextButton( + margin: SimpleTableConstants.moreActionHorizontalMargin, + leftIconBuilder: _buildLeftIcon, + iconPadding: 10.0, + textBuilder: (onHover) => FlowyText.regular( + text, + fontSize: 14.0, + figmaLineHeight: 18.0, + ), + onTap: onTap, + rightIconBuilder: (onHover) => rightIcon ?? const SizedBox.shrink(), + ), + ); + } + + Widget _buildLeftIcon(bool onHover) { + if (leftIconBuilder != null) { + return leftIconBuilder!(onHover); + } + return leftIconSvg != null + ? FlowySvg(leftIconSvg!) + : const SizedBox.shrink(); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_border_builder.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_border_builder.dart index b3f7a4629c..f44c394694 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_border_builder.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_border_builder.dart @@ -185,12 +185,15 @@ class SimpleTableBorderBuilder { simpleTableContext.isReorderingColumn.value.$2 < node.columnIndex; return Border( - top: _buildDefaultBorderSide(), - bottom: _buildDefaultBorderSide(), - left: - isLeftSide ? _buildHighlightBorderSide() : _buildDefaultBorderSide(), + top: node.rowIndex == 0 + ? _buildDefaultBorderSide() + : _buildLightBorderSide(), + bottom: node.rowIndex + 1 == node.parentTableNode?.rowLength + ? _buildDefaultBorderSide() + : _buildLightBorderSide(), + left: isLeftSide ? _buildHighlightBorderSide() : _buildLightBorderSide(), right: - isRightSide ? _buildHighlightBorderSide() : _buildDefaultBorderSide(), + isRightSide ? _buildHighlightBorderSide() : _buildLightBorderSide(), ); } @@ -216,12 +219,15 @@ class SimpleTableBorderBuilder { simpleTableContext.isReorderingRow.value.$2 < node.rowIndex; return Border( - top: isTopSide ? _buildHighlightBorderSide() : _buildDefaultBorderSide(), - bottom: isBottomSide - ? _buildHighlightBorderSide() - : _buildDefaultBorderSide(), - left: _buildDefaultBorderSide(), - right: _buildDefaultBorderSide(), + top: isTopSide ? _buildHighlightBorderSide() : _buildLightBorderSide(), + bottom: + isBottomSide ? _buildHighlightBorderSide() : _buildLightBorderSide(), + left: node.columnIndex == 0 + ? _buildDefaultBorderSide() + : _buildLightBorderSide(), + right: node.columnIndex + 1 == node.parentTableNode?.columnLength + ? _buildDefaultBorderSide() + : _buildLightBorderSide(), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_divider.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_divider.dart new file mode 100644 index 0000000000..0de08d6e75 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_divider.dart @@ -0,0 +1,28 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:flutter/material.dart'; + +class SimpleTableRowDivider extends StatelessWidget { + const SimpleTableRowDivider({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return VerticalDivider( + color: context.simpleTableBorderColor, + width: 1.0, + ); + } +} + +class SimpleTableColumnDivider extends StatelessWidget { + const SimpleTableColumnDivider({super.key}); + + @override + Widget build(BuildContext context) { + return Divider( + color: context.simpleTableBorderColor, + height: 1.0, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_reorder_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_reorder_button.dart index 5a51103822..740c822ff5 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_reorder_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_reorder_button.dart @@ -1,6 +1,5 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_widget.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_widget.dart index 0767ec4a47..563570bf27 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_widget.dart @@ -1,4 +1,3 @@ -import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/_shared_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/widgets.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/widgets.dart new file mode 100644 index 0000000000..8b9607e2ba --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/widgets.dart @@ -0,0 +1,11 @@ +export 'simple_table_add_column_and_row_button.dart'; +export 'simple_table_add_column_button.dart'; +export 'simple_table_add_row_button.dart'; +export 'simple_table_align_button.dart'; +export 'simple_table_background_menu.dart'; +export 'simple_table_basic_button.dart'; +export 'simple_table_border_builder.dart'; +export 'simple_table_column_resize_handle.dart'; +export 'simple_table_divider.dart'; +export 'simple_table_reorder_button.dart'; +export 'simple_table_widget.dart'; diff --git a/frontend/resources/flowy_icons/16x/table_clear_content.svg b/frontend/resources/flowy_icons/16x/table_clear_content.svg index 8afeb2dd65..de1d42cabe 100644 --- a/frontend/resources/flowy_icons/16x/table_clear_content.svg +++ b/frontend/resources/flowy_icons/16x/table_clear_content.svg @@ -1,4 +1,11 @@ - - + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/table_distribute_columns_evenly.svg b/frontend/resources/flowy_icons/16x/table_distribute_columns_evenly.svg new file mode 100644 index 0000000000..fe7acf8c98 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/table_distribute_columns_evenly.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/table_insert_above.svg b/frontend/resources/flowy_icons/16x/table_insert_above.svg index 16ffb6a33c..e3bcfe4ca6 100644 --- a/frontend/resources/flowy_icons/16x/table_insert_above.svg +++ b/frontend/resources/flowy_icons/16x/table_insert_above.svg @@ -1,3 +1,6 @@ - + + + + diff --git a/frontend/resources/flowy_icons/16x/table_insert_below.svg b/frontend/resources/flowy_icons/16x/table_insert_below.svg index acfa64a346..de1719edeb 100644 --- a/frontend/resources/flowy_icons/16x/table_insert_below.svg +++ b/frontend/resources/flowy_icons/16x/table_insert_below.svg @@ -1,3 +1,6 @@ - + + + + diff --git a/frontend/resources/flowy_icons/16x/table_insert_left.svg b/frontend/resources/flowy_icons/16x/table_insert_left.svg index 3da61207b4..47bbb0820c 100644 --- a/frontend/resources/flowy_icons/16x/table_insert_left.svg +++ b/frontend/resources/flowy_icons/16x/table_insert_left.svg @@ -1,3 +1,6 @@ - - + + + + + diff --git a/frontend/resources/flowy_icons/16x/table_insert_right.svg b/frontend/resources/flowy_icons/16x/table_insert_right.svg index 2852bd389d..f754f516d0 100644 --- a/frontend/resources/flowy_icons/16x/table_insert_right.svg +++ b/frontend/resources/flowy_icons/16x/table_insert_right.svg @@ -1,3 +1,6 @@ - - + + + + + diff --git a/frontend/resources/flowy_icons/16x/table_set_to_page_width.svg b/frontend/resources/flowy_icons/16x/table_set_to_page_width.svg new file mode 100644 index 0000000000..0787942325 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/table_set_to_page_width.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 828114bf13..4a27f0f783 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -1725,7 +1725,9 @@ "insertBelow": "Insert below", "headerColumn": "Header column", "headerRow": "Header row", - "clearContents": "Clear contents" + "clearContents": "Clear contents", + "setToPageWidth": "Set to page width", + "distributeColumnsWidth": "Distribute columns evenly" }, "clickToAddNewRow": "Click to add a new row", "clickToAddNewColumn": "Click to add a new column", @@ -2906,4 +2908,4 @@ "permissionDenied": "No permission to open this file", "unknownError": "File open failed" } -} \ No newline at end of file +}