Skip to content

Commit

Permalink
feat: moved table plugin to appflowy-editor
Browse files Browse the repository at this point in the history
  • Loading branch information
zoli committed Apr 12, 2023
1 parent 7dc97e4 commit 2be0c60
Show file tree
Hide file tree
Showing 23 changed files with 2,157 additions and 70 deletions.
6 changes: 6 additions & 0 deletions lib/appflowy_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,9 @@ export 'src/render/action_menu/action_menu.dart';
export 'src/render/action_menu/action_menu_item.dart';
export 'src/core/document/node_iterator.dart';
export 'src/service/context_menu/context_menu.dart';
// Table
export 'src/render/table/table_const.dart';
export 'src/render/table/table_node_widget.dart';
export 'src/render/table/table_cell_node_widget.dart';
export 'src/render/table/table_shortcut_event.dart';
export 'src/render/table/table_context_menu_item.dart';
89 changes: 41 additions & 48 deletions lib/src/render/color_menu/color_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,34 @@ class ColorOption {
final String name;
}

enum _ColorType {
font,
background,
class ColorOptionList {
const ColorOptionList({
this.selectedColorHex,
required this.header,
required this.colorOptions,
required this.onSubmittedAction,
});

final String header;
final List<ColorOption> colorOptions;
final void Function(String color) onSubmittedAction;
final String? selectedColorHex;
}

class ColorPicker extends StatefulWidget {
const ColorPicker({
super.key,
this.selectedFontColorHex,
this.selectedBackgroundColorHex,
required this.pickerBackgroundColor,
required this.fontColorOptions,
required this.backgroundColorOptions,
required this.pickerItemHoverColor,
required this.pickerItemTextColor,
required this.onSubmittedbackgroundColorHex,
required this.onSubmittedFontColorHex,
required this.colorOptionLists,
});

final String? selectedFontColorHex;
final String? selectedBackgroundColorHex;
final Color pickerBackgroundColor;
final Color pickerItemHoverColor;
final Color pickerItemTextColor;
final void Function(String color) onSubmittedbackgroundColorHex;
final void Function(String color) onSubmittedFontColorHex;

final List<ColorOption> fontColorOptions;
final List<ColorOption> backgroundColorOptions;
final List<ColorOptionList> colorOptionLists;

@override
State<ColorPicker> createState() => _ColorPickerState();
Expand Down Expand Up @@ -67,34 +66,31 @@ class _ColorPickerState extends State<ColorPicker> {
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
// font color
_buildHeader('font color'),
// padding
const SizedBox(height: 6),
_buildColorItems(
_ColorType.font,
widget.fontColorOptions,
widget.selectedFontColorHex,
),
// background color
const SizedBox(height: 6),
_buildHeader('background color'),
const SizedBox(height: 6),
_buildColorItems(
_ColorType.background,
widget.backgroundColorOptions,
widget.selectedBackgroundColorHex,
),
],
),
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildColorOptionLists(widget.colorOptionLists)),
),
),
);
}

List<Widget> _buildColorOptionLists(List<ColorOptionList> colorOptionLists) {
List<Widget> colorOptionMenu = [];
for (var i = 0; i < colorOptionLists.length; i++) {
if (i != 0) {
colorOptionMenu.add(const SizedBox(height: 6));
}

colorOptionMenu.addAll([
_buildHeader(colorOptionLists[i].header),
const SizedBox(height: 6),
_buildColorItems(colorOptionLists[i]),
]);
}

return colorOptionMenu;
}

Widget _buildHeader(String text) {
return Text(
text,
Expand All @@ -105,18 +101,19 @@ class _ColorPickerState extends State<ColorPicker> {
);
}

Widget _buildColorItems(
_ColorType type, List<ColorOption> options, String? selectedColor) {
Widget _buildColorItems(ColorOptionList colorOptionList) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: options
.map((e) => _buildColorItem(type, e, e.colorHex == selectedColor))
children: colorOptionList.colorOptions
.map((e) => _buildColorItem(colorOptionList.onSubmittedAction, e,
e.colorHex == colorOptionList.selectedColorHex))
.toList(),
);
}

Widget _buildColorItem(_ColorType type, ColorOption option, bool isChecked) {
Widget _buildColorItem(
void Function(String color) onTap, ColorOption option, bool isChecked) {
return SizedBox(
height: 36,
child: InkWell(
Expand All @@ -125,11 +122,7 @@ class _ColorPickerState extends State<ColorPicker> {
),
hoverColor: widget.pickerItemHoverColor,
onTap: () {
if (type == _ColorType.font) {
widget.onSubmittedFontColorHex(option.colorHex);
} else if (type == _ColorType.background) {
widget.onSubmittedbackgroundColorHex(option.colorHex);
}
onTap(option.colorHex);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
Expand Down
186 changes: 186 additions & 0 deletions lib/src/render/table/table_action.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/render/table/util.dart';

addCol(Node tableNode, Transaction transaction) {
List<Node> cellNodes = [];
final int rowsLen = tableNode.attributes['rowsLen'],
colsLen = tableNode.attributes['colsLen'];

var lastCellNode = getCellNode(tableNode, colsLen - 1, rowsLen - 1)!;
for (var i = 0; i < rowsLen; i++) {
final node = Node(
type: kTableCellType,
attributes: {
'position': {'col': colsLen, 'row': i}
},
);
node.insert(TextNode.empty());

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

// TODO(zoli): this calls notifyListener rowsLen+1 times. isn't there a better
// way?
transaction.insertNodes(lastCellNode.path.next, cellNodes);
transaction.updateNode(tableNode, {'colsLen': colsLen + 1});
}

addRow(Node tableNode, Transaction transaction) {
final int rowsLen = tableNode.attributes['rowsLen'],
colsLen = tableNode.attributes['colsLen'];
for (var i = 0; i < colsLen; i++) {
final node = Node(
type: kTableCellType,
attributes: {
'position': {'col': i, 'row': rowsLen}
},
);
node.insert(TextNode.empty());

transaction.insertNode(getCellNode(tableNode, i, rowsLen - 1)!.path.next,
newCellNode(tableNode, node));
}
transaction.updateNode(tableNode, {'rowsLen': rowsLen + 1});
}

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

for (var i = col + 1; i < colsLen; i++) {
for (var j = 0; j < rowsLen; j++) {
transaction.updateNode(getCellNode(tableNode, i, j)!, {
'position': {'col': i - 1, 'row': j}
});
}
}

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

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

for (var i = row + 1; i < rowsLen; i++) {
for (var j = 0; j < colsLen; j++) {
transaction.updateNode(getCellNode(tableNode, j, i)!, {
'position': {'col': j, 'row': i - 1}
});
}
}

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

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

for (var i = col + 1; i < colsLen; i++) {
for (var j = 0; j < rowsLen; j++) {
transaction.updateNode(getCellNode(tableNode, i, j)!, {
'position': {'col': i + 1, 'row': j}
});
}
}

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

duplicateRow(Node tableNode, int row, Transaction transaction) {
final int rowsLen = tableNode.attributes['rowsLen'],
colsLen = tableNode.attributes['colsLen'];
for (var i = 0; i < colsLen; i++) {
final node = getCellNode(tableNode, i, row)!;
transaction.insertNode(
node.path.next,
node.copyWith(attributes: {
'position': {'row': row + 1, 'col': i}
}),
);
}

for (var i = row + 1; i < rowsLen; i++) {
for (var j = 0; j < colsLen; j++) {
transaction.updateNode(getCellNode(tableNode, j, i)!, {
'position': {'col': j, 'row': i + 1}
});
}
}

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

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

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

newCellNode(Node tableNode, n) {
final row = n.attributes['position']['row'] as int;
final col = n.attributes['position']['col'] as int;
final int rowsLen = tableNode.attributes['rowsLen'];
final int colsLen = tableNode.attributes['colsLen'];

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

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

return n;
}
43 changes: 43 additions & 0 deletions lib/src/render/table/table_action_button.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:appflowy_editor/appflowy_editor.dart';

class TableActionButton extends StatefulWidget {
const TableActionButton({
Key? key,
required this.width,
required this.height,
required this.padding,
required this.onPressed,
}) : super(key: key);

final double width, height;
final EdgeInsetsGeometry padding;
final Function onPressed;

@override
State<TableActionButton> createState() => _TableActionButtonState();
}

class _TableActionButtonState extends State<TableActionButton> {
bool _visible = false;

@override
Widget build(BuildContext context) {
return Container(
padding: widget.padding,
width: widget.width,
height: widget.height,
child: MouseRegion(
onEnter: (_) => setState(() => _visible = true),
onExit: (_) => setState(() => _visible = false),
child: Center(
child: Visibility(
visible: _visible,
child: ActionMenuWidget(items: [
ActionMenuItem.icon(
iconData: Icons.add, onPressed: () => widget.onPressed()),
]),
),
)));
}
}

0 comments on commit 2be0c60

Please sign in to comment.