Skip to content

Commit

Permalink
feat: table plugin (AppFlowy-IO#62)
Browse files Browse the repository at this point in the history
* feat: table plugin requisites

* feat: moved table plugin to appflowy-editor

* fix: flutter analyze

* fix: dont use nested maps in node attributes

rust backend (flowy-document) doesn't support it.

* feat: add table to appflowy editor default selection menu

* feat table markdown encoder

* refactor: apply @Xazin review suggestions

* refactor: merge main and fix color picker conflicts

* feat: table markdown decoder

* fix: use specific backgroun generator for table

* feat: table plugin

* style: adapt new structure

* fix: table columns border height

* fix: adapt new style key commands

* fix: remove table context menu

* test: adapt to main branch changes

* fix: backspace handling regarding table

* fix: flutter analyze

* fix: enter when table cell is bullet list

* fix: selection bug on large table

* feat: row/col action menu

* fix: row action handler positioning

* refactor: merge row and col handler widget

* refactor: add size and color to handler icon

* style(merge): merge main

* feat: added padding and action wrapper

* feat: enable customizing icons and etc

* feat: table menu builder

* fix: table getBlockRect

* refactor: table action button design

---------

Co-authored-by: Lucas.Xu <[email protected]>
  • Loading branch information
zoli and LucasXu0 authored Aug 29, 2023
1 parent cda17c7 commit dd52ce6
Show file tree
Hide file tree
Showing 39 changed files with 3,913 additions and 267 deletions.
6 changes: 6 additions & 0 deletions lib/src/editor/block_component/block_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ export 'divider_block_component/divider_block_component.dart';
export 'divider_block_component/divider_character_shortcut.dart';
export 'divider_block_component/divider_menu_item.dart';

// table
export 'table_block_component/table_block_component.dart';
export 'table_block_component/table_cell_block_component.dart';
export 'table_block_component/table_commands.dart';
export 'table_block_component/table_action.dart';

// base
export 'base_component/convert_to_paragraph_command.dart';
export 'base_component/insert_newline_in_type_command.dart';
Expand Down
3 changes: 3 additions & 0 deletions lib/src/editor/block_component/standard_block_components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ final Map<String, BlockComponentBuilder> standardBlockComponentBuilderMap = {
padding: (node) => const EdgeInsets.symmetric(vertical: 8.0),
),
),
TableBlockKeys.type: TableBlockComponentBuilder(),
TableCellBlockKeys.type: TableCellBlockComponentBuilder(),
};

final List<CharacterShortcutEvent> standardCharacterShortcutEvents = [
Expand Down Expand Up @@ -92,6 +94,7 @@ final List<CommandShortcutEvent> standardCommandShortcutEvents = [

// backspace
convertToParagraphCommand,
...tableCommands,
backspaceCommand,
deleteLeftWordCommand,
deleteLeftSentenceCommand,
Expand Down
350 changes: 350 additions & 0 deletions lib/src/editor/block_component/table_block_component/table_action.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,350 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/editor/block_component/table_block_component/util.dart';

class TableActions {
const TableActions._();

static void add(
Node node,
int position,
Transaction transaction,
TableDirection dir,
) {
if (dir == TableDirection.col) {
_addCol(node, position, transaction);
} else {
_addRow(node, position, transaction);
}
}

static void delete(
Node node,
int position,
Transaction transaction,
TableDirection dir,
) {
if (dir == TableDirection.col) {
_deleteCol(node, position, transaction);
} else {
_deleteRow(node, position, transaction);
}
}

static void duplicate(
Node node,
int position,
Transaction transaction,
TableDirection dir,
) {
if (dir == TableDirection.col) {
_duplicateCol(node, position, transaction);
} else {
_duplicateRow(node, position, transaction);
}
}

static void clear(
Node node,
int position,
Transaction transaction,
TableDirection dir,
) {
if (dir == TableDirection.col) {
_clearCol(node, position, transaction);
} else {
_clearRow(node, position, transaction);
}
}

static void setBgColor(
Node node,
int position,
Transaction transaction,
String? color,
TableDirection dir,
) {
if (dir == TableDirection.col) {
_setColBgColor(node, position, transaction, color);
} else {
_setRowBgColor(node, position, transaction, color);
}
}
}

void _addCol(Node tableNode, int position, Transaction transaction) {
assert(position >= 0);

List<Node> cellNodes = [];
final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen],
colsLen = tableNode.attributes[TableBlockKeys.colsLen];

if (position != colsLen) {
for (var i = position; i < colsLen; i++) {
for (var j = 0; j < rowsLen; j++) {
final node = getCellNode(tableNode, i, j)!;
transaction.updateNode(node, {TableBlockKeys.colPosition: i + 1});
}
}
}

for (var i = 0; i < rowsLen; i++) {
final node = Node(
type: TableCellBlockKeys.type,
attributes: {
TableBlockKeys.colPosition: position,
TableBlockKeys.rowPosition: i,
},
);
node.insert(paragraphNode());

cellNodes.add(newCellNode(tableNode, node));
}

late Path insertPath;
if (position == 0) {
insertPath = getCellNode(tableNode, 0, 0)!.path;
} else {
insertPath = getCellNode(tableNode, position - 1, rowsLen - 1)!.path.next;
}
// TODO(zoli): this calls notifyListener rowsLen+1 times. isn't there a better
// way?
transaction.insertNodes(insertPath, cellNodes);
transaction.updateNode(tableNode, {TableBlockKeys.colsLen: colsLen + 1});
}

void _addRow(Node tableNode, int position, Transaction transaction) {
assert(position >= 0);

final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen],
colsLen = tableNode.attributes[TableBlockKeys.colsLen];

if (position != rowsLen) {
for (var i = position; i < rowsLen; i++) {
for (var j = 0; j < colsLen; j++) {
final node = getCellNode(tableNode, j, i)!;
transaction.updateNode(node, {TableBlockKeys.rowPosition: i + 1});
}
}
}

for (var i = 0; i < colsLen; i++) {
final node = Node(
type: TableCellBlockKeys.type,
attributes: {
TableBlockKeys.colPosition: i,
TableBlockKeys.rowPosition: position,
},
);
node.insert(paragraphNode());

late Path insertPath;
if (position == 0) {
insertPath = getCellNode(tableNode, i, 0)!.path;
} else {
insertPath = getCellNode(tableNode, i, position - 1)!.path.next;
}
transaction.insertNode(
insertPath,
newCellNode(tableNode, node),
);
}
transaction.updateNode(tableNode, {TableBlockKeys.rowsLen: rowsLen + 1});
}

void _deleteCol(Node tableNode, int col, Transaction transaction) {
final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen],
colsLen = tableNode.attributes[TableBlockKeys.colsLen];
List<Node> nodes = [];
for (var i = 0; i < rowsLen; i++) {
nodes.add(getCellNode(tableNode, col, i)!);
}
transaction.deleteNodes(nodes);

_updateCellPositions(tableNode, transaction, col + 1, 0, -1, 0);

transaction.updateNode(tableNode, {TableBlockKeys.colsLen: colsLen - 1});
}

void _deleteRow(Node tableNode, int row, Transaction transaction) {
final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen],
colsLen = tableNode.attributes[TableBlockKeys.colsLen];
List<Node> nodes = [];
for (var i = 0; i < colsLen; i++) {
nodes.add(getCellNode(tableNode, i, row)!);
}
transaction.deleteNodes(nodes);

_updateCellPositions(tableNode, transaction, 0, row + 1, 0, -1);

transaction.updateNode(tableNode, {TableBlockKeys.rowsLen: rowsLen - 1});
}

void _duplicateCol(Node tableNode, int col, Transaction transaction) {
final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen],
colsLen = tableNode.attributes[TableBlockKeys.colsLen];
List<Node> nodes = [];
for (var i = 0; i < rowsLen; i++) {
final node = getCellNode(tableNode, col, i)!;
nodes.add(
node.copyWith(
attributes: {
...node.attributes,
TableBlockKeys.colPosition: col + 1,
TableBlockKeys.rowPosition: i,
},
),
);
}
transaction.insertNodes(
getCellNode(tableNode, col, rowsLen - 1)!.path.next,
nodes,
);

_updateCellPositions(tableNode, transaction, col + 1, 0, 1, 0);

transaction.updateNode(tableNode, {TableBlockKeys.colsLen: colsLen + 1});
}

void _duplicateRow(Node tableNode, int row, Transaction transaction) {
final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen],
colsLen = tableNode.attributes[TableBlockKeys.colsLen];
for (var i = 0; i < colsLen; i++) {
final node = getCellNode(tableNode, i, row)!;
transaction.insertNode(
node.path.next,
node.copyWith(
attributes: {
...node.attributes,
TableBlockKeys.rowPosition: row + 1,
TableBlockKeys.colPosition: i,
},
),
);
}

_updateCellPositions(tableNode, transaction, 0, row + 1, 0, 1);

transaction.updateNode(tableNode, {TableBlockKeys.rowsLen: rowsLen + 1});
}

void _setColBgColor(
Node tableNode,
int col,
Transaction transaction,
String? color,
) {
final rowslen = tableNode.attributes[TableBlockKeys.rowsLen];
for (var i = 0; i < rowslen; i++) {
final node = getCellNode(tableNode, col, i)!;
transaction.updateNode(
node,
{TableBlockKeys.backgroundColor: color},
);
}
}

void _setRowBgColor(
Node tableNode,
int row,
Transaction transaction,
String? color,
) {
final colsLen = tableNode.attributes[TableBlockKeys.colsLen];
for (var i = 0; i < colsLen; i++) {
final node = getCellNode(tableNode, i, row)!;
transaction.updateNode(
node,
{TableBlockKeys.backgroundColor: color},
);
}
}

void _clearCol(
Node tableNode,
int col,
Transaction transaction,
) {
final rowsLen = tableNode.attributes[TableBlockKeys.rowsLen];
for (var i = 0; i < rowsLen; i++) {
final node = getCellNode(tableNode, col, i)!;
transaction.insertNode(
node.children.first.path,
paragraphNode(text: ''),
);
}
}

void _clearRow(
Node tableNode,
int row,
Transaction transaction,
) {
final colsLen = tableNode.attributes[TableBlockKeys.colsLen];
for (var i = 0; i < colsLen; i++) {
final node = getCellNode(tableNode, i, row)!;
transaction.insertNode(
node.children.first.path,
paragraphNode(text: ''),
);
}
}

dynamic newCellNode(Node tableNode, n) {
final row = n.attributes[TableBlockKeys.rowPosition] as int;
final col = n.attributes[TableBlockKeys.colPosition] as int;
final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen];
final int colsLen = tableNode.attributes[TableBlockKeys.colsLen];

if (!n.attributes.containsKey(TableBlockKeys.height)) {
double nodeHeight = double.tryParse(
tableNode.attributes[TableBlockKeys.rowDefaultHeight].toString(),
)!;
if (row < rowsLen) {
nodeHeight = double.tryParse(
getCellNode(tableNode, 0, row)!
.attributes[TableBlockKeys.height]
.toString(),
) ??
nodeHeight;
}
n.updateAttributes({TableBlockKeys.height: nodeHeight});
}

if (!n.attributes.containsKey(TableBlockKeys.width)) {
double nodeWidth = double.tryParse(
tableNode.attributes[TableBlockKeys.colDefaultWidth].toString(),
)!;
if (col < colsLen) {
nodeWidth = double.tryParse(
getCellNode(tableNode, col, 0)!
.attributes[TableBlockKeys.width]
.toString(),
) ??
nodeWidth;
}
n.updateAttributes({TableBlockKeys.width: nodeWidth});
}

return n;
}

void _updateCellPositions(
Node tableNode,
Transaction transaction,
int fromCol,
int fromRow,
int addToCol,
int addToRow,
) {
final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen],
colsLen = tableNode.attributes[TableBlockKeys.colsLen];

for (var i = fromCol; i < colsLen; i++) {
for (var j = fromRow; j < rowsLen; j++) {
transaction.updateNode(getCellNode(tableNode, i, j)!, {
TableBlockKeys.colPosition: i + addToCol,
TableBlockKeys.rowPosition: j + addToRow,
});
}
}
}
Loading

0 comments on commit dd52ce6

Please sign in to comment.