mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-08-05 07:16:58 +00:00
feat: make the columns block same width width the editor (#7493)
* feat: make the columns block same width width the editor * chore: turn off column debug mode * feat: add block selection container in outline block * feat: use ratio instead of width in simple columns * fix: document rules * fix: turn off debug mode * fix: update the existing columns block data
This commit is contained in:
parent
7b32a92290
commit
c81f87dcdc
@ -104,6 +104,28 @@ class DocumentRules {
|
|||||||
} else {
|
} else {
|
||||||
// otherwise, delete the column
|
// otherwise, delete the column
|
||||||
deleteColumnsTransaction.deleteNode(column);
|
deleteColumnsTransaction.deleteNode(column);
|
||||||
|
|
||||||
|
final deletedColumnRatio =
|
||||||
|
column.attributes[SimpleColumnBlockKeys.ratio];
|
||||||
|
if (deletedColumnRatio != null) {
|
||||||
|
// update the ratio of the columns
|
||||||
|
final columnsNode = column.columnsParent;
|
||||||
|
if (columnsNode != null) {
|
||||||
|
final length = columnsNode.children.length;
|
||||||
|
for (final columnNode in columnsNode.children) {
|
||||||
|
final ratio =
|
||||||
|
columnNode.attributes[SimpleColumnBlockKeys.ratio] ??
|
||||||
|
1.0 / length;
|
||||||
|
if (ratio != null) {
|
||||||
|
deleteColumnsTransaction.updateNode(columnNode, {
|
||||||
|
...columnNode.attributes,
|
||||||
|
SimpleColumnBlockKeys.ratio:
|
||||||
|
ratio + deletedColumnRatio / (length - 1),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ class _DraggableOptionButtonState extends State<DraggableOptionButton> {
|
|||||||
interceptor: (context, targetNode) {
|
interceptor: (context, targetNode) {
|
||||||
// if the cursor node is in a columns block or a column block,
|
// if the cursor node is in a columns block or a column block,
|
||||||
// we will return the node's parent instead to support dragging a node to the inside of a columns block or a column block.
|
// we will return the node's parent instead to support dragging a node to the inside of a columns block or a column block.
|
||||||
final parentColumnNode = targetNode.parentColumn;
|
final parentColumnNode = targetNode.columnParent;
|
||||||
if (parentColumnNode != null) {
|
if (parentColumnNode != null) {
|
||||||
final position = getDragAreaPosition(
|
final position = getDragAreaPosition(
|
||||||
context,
|
context,
|
||||||
@ -147,7 +147,7 @@ class _DraggableOptionButtonState extends State<DraggableOptionButton> {
|
|||||||
interceptor: (context, targetNode) {
|
interceptor: (context, targetNode) {
|
||||||
// if the cursor node is in a columns block or a column block,
|
// if the cursor node is in a columns block or a column block,
|
||||||
// we will return the node's parent instead to support dragging a node to the inside of a columns block or a column block.
|
// we will return the node's parent instead to support dragging a node to the inside of a columns block or a column block.
|
||||||
final parentColumnNode = targetNode.parentColumn;
|
final parentColumnNode = targetNode.columnParent;
|
||||||
if (parentColumnNode != null) {
|
if (parentColumnNode != null) {
|
||||||
final position = getDragAreaPosition(
|
final position = getDragAreaPosition(
|
||||||
context,
|
context,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/columns/simple_columns_block_constant.dart';
|
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart'
|
import 'package:appflowy_editor/appflowy_editor.dart'
|
||||||
@ -60,38 +59,37 @@ Future<void> dragToMoveNode(
|
|||||||
// 1. if the targetNode is a column block, it means we should create a column block to contain the node and insert the column node to the target node's parent
|
// 1. if the targetNode is a column block, it means we should create a column block to contain the node and insert the column node to the target node's parent
|
||||||
// 2. if the targetNode is not a column block, it means we should create a columns block to contain the target node and the drag node
|
// 2. if the targetNode is not a column block, it means we should create a columns block to contain the target node and the drag node
|
||||||
final transaction = editorState.transaction;
|
final transaction = editorState.transaction;
|
||||||
final targetNodeParent = targetNode.parentColumnsBlock;
|
final targetNodeParent = targetNode.columnsParent;
|
||||||
|
|
||||||
if (targetNodeParent != null) {
|
if (targetNodeParent != null) {
|
||||||
final length = targetNodeParent.children.length;
|
final length = targetNodeParent.children.length;
|
||||||
|
final ratios = targetNodeParent.children
|
||||||
|
.map(
|
||||||
|
(e) =>
|
||||||
|
e.attributes[SimpleColumnBlockKeys.ratio]?.toDouble() ??
|
||||||
|
1.0 / length,
|
||||||
|
)
|
||||||
|
.map((e) => e * length / (length + 1))
|
||||||
|
.toList();
|
||||||
|
|
||||||
final columnNode = simpleColumnNode(
|
final columnNode = simpleColumnNode(
|
||||||
children: [node.deepCopy()],
|
children: [node.deepCopy()],
|
||||||
width: (node.rect.width * 1 / (length + 1)).clamp(
|
ratio: 1.0 / (length + 1),
|
||||||
SimpleColumnsBlockConstants.minimumColumnWidth,
|
|
||||||
double.infinity,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
for (final (index, column) in targetNodeParent.children.indexed) {
|
||||||
for (final column in targetNodeParent.children) {
|
|
||||||
final width =
|
|
||||||
column.attributes[SimpleColumnBlockKeys.width]?.toDouble() ??
|
|
||||||
SimpleColumnsBlockConstants.minimumColumnWidth;
|
|
||||||
transaction.updateNode(column, {
|
transaction.updateNode(column, {
|
||||||
...column.attributes,
|
...column.attributes,
|
||||||
SimpleColumnBlockKeys.width: (width * length / (length + 1)).clamp(
|
SimpleColumnBlockKeys.ratio: ratios[index],
|
||||||
SimpleColumnsBlockConstants.minimumColumnWidth,
|
|
||||||
double.infinity,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.insertNode(targetNode.path.next, columnNode);
|
transaction.insertNode(targetNode.path.next, columnNode);
|
||||||
transaction.deleteNode(node);
|
transaction.deleteNode(node);
|
||||||
} else {
|
} else {
|
||||||
final width = targetNode.rect.width / 2 - 16;
|
|
||||||
final columnsNode = simpleColumnsNode(
|
final columnsNode = simpleColumnsNode(
|
||||||
children: [
|
children: [
|
||||||
simpleColumnNode(children: [targetNode.deepCopy()], width: width),
|
simpleColumnNode(children: [targetNode.deepCopy()], ratio: 0.5),
|
||||||
simpleColumnNode(children: [node.deepCopy()], width: width),
|
simpleColumnNode(children: [node.deepCopy()], ratio: 0.5),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -109,39 +107,37 @@ Future<void> dragToMoveNode(
|
|||||||
// 1. if the target node is a column block, we should create a column block to contain the node and insert the column node to the target node's parent
|
// 1. if the target node is a column block, we should create a column block to contain the node and insert the column node to the target node's parent
|
||||||
// 2. if the target node is not a column block, we should create a columns block to contain the target node and the drag node
|
// 2. if the target node is not a column block, we should create a columns block to contain the target node and the drag node
|
||||||
final transaction = editorState.transaction;
|
final transaction = editorState.transaction;
|
||||||
final targetNodeParent = targetNode.parentColumnsBlock;
|
final targetNodeParent = targetNode.columnsParent;
|
||||||
if (targetNodeParent != null) {
|
if (targetNodeParent != null) {
|
||||||
// find the previous sibling node of the target node
|
// find the previous sibling node of the target node
|
||||||
final length = targetNodeParent.children.length;
|
final length = targetNodeParent.children.length;
|
||||||
|
final ratios = targetNodeParent.children
|
||||||
|
.map(
|
||||||
|
(e) =>
|
||||||
|
e.attributes[SimpleColumnBlockKeys.ratio]?.toDouble() ??
|
||||||
|
1.0 / length,
|
||||||
|
)
|
||||||
|
.map((e) => e * length / (length + 1))
|
||||||
|
.toList();
|
||||||
final columnNode = simpleColumnNode(
|
final columnNode = simpleColumnNode(
|
||||||
children: [node.deepCopy()],
|
children: [node.deepCopy()],
|
||||||
width: (node.rect.width * 1 / (length + 1)).clamp(
|
ratio: 1.0 / (length + 1),
|
||||||
SimpleColumnsBlockConstants.minimumColumnWidth,
|
|
||||||
double.infinity,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
for (final column in targetNodeParent.children) {
|
for (final (index, column) in targetNodeParent.children.indexed) {
|
||||||
final width =
|
|
||||||
column.attributes[SimpleColumnBlockKeys.width]?.toDouble() ??
|
|
||||||
SimpleColumnsBlockConstants.minimumColumnWidth;
|
|
||||||
transaction.updateNode(column, {
|
transaction.updateNode(column, {
|
||||||
...column.attributes,
|
...column.attributes,
|
||||||
SimpleColumnBlockKeys.width: (width * length / (length + 1)).clamp(
|
SimpleColumnBlockKeys.ratio: ratios[index],
|
||||||
SimpleColumnsBlockConstants.minimumColumnWidth,
|
|
||||||
double.infinity,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.insertNode(targetNode.path.previous, columnNode);
|
transaction.insertNode(targetNode.path.previous, columnNode);
|
||||||
transaction.deleteNode(node);
|
transaction.deleteNode(node);
|
||||||
} else {
|
} else {
|
||||||
final width = targetNode.rect.width / 2 - 16;
|
|
||||||
final columnsNode = simpleColumnsNode(
|
final columnsNode = simpleColumnsNode(
|
||||||
children: [
|
children: [
|
||||||
simpleColumnNode(children: [node.deepCopy()], width: width),
|
simpleColumnNode(children: [node.deepCopy()], ratio: 0.5),
|
||||||
simpleColumnNode(children: [targetNode.deepCopy()], width: width),
|
simpleColumnNode(children: [targetNode.deepCopy()], ratio: 0.5),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -7,27 +7,62 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
Node simpleColumnNode({
|
Node simpleColumnNode({
|
||||||
List<Node>? children,
|
List<Node>? children,
|
||||||
double? width,
|
double? ratio,
|
||||||
}) {
|
}) {
|
||||||
return Node(
|
return Node(
|
||||||
type: SimpleColumnBlockKeys.type,
|
type: SimpleColumnBlockKeys.type,
|
||||||
children: children ?? [paragraphNode()],
|
children: children ?? [paragraphNode()],
|
||||||
attributes: {
|
attributes: {
|
||||||
SimpleColumnBlockKeys.width: width,
|
SimpleColumnBlockKeys.ratio: ratio,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SimpleColumnBlockAttributes on Node {
|
||||||
|
// get the next column node of the current column node
|
||||||
|
// if the current column node is the last column node, return null
|
||||||
|
Node? get nextColumn {
|
||||||
|
final index = path.last;
|
||||||
|
final parent = this.parent;
|
||||||
|
if (parent == null || index == parent.children.length - 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parent.children[index + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the previous column node of the current column node
|
||||||
|
// if the current column node is the first column node, return null
|
||||||
|
Node? get previousColumn {
|
||||||
|
final index = path.last;
|
||||||
|
final parent = this.parent;
|
||||||
|
if (parent == null || index == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parent.children[index - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SimpleColumnBlockKeys {
|
class SimpleColumnBlockKeys {
|
||||||
const SimpleColumnBlockKeys._();
|
const SimpleColumnBlockKeys._();
|
||||||
|
|
||||||
static const String type = 'simple_column';
|
static const String type = 'simple_column';
|
||||||
|
|
||||||
|
/// @Deprecated Use [SimpleColumnBlockKeys.ratio] instead.
|
||||||
|
///
|
||||||
|
/// This field is no longer used since v0.6.9
|
||||||
|
@Deprecated('Use [SimpleColumnBlockKeys.ratio] instead.')
|
||||||
static const String width = 'width';
|
static const String width = 'width';
|
||||||
|
|
||||||
|
/// The ratio of the column width.
|
||||||
|
///
|
||||||
|
/// The value is a double number between 0 and 1.
|
||||||
|
static const String ratio = 'ratio';
|
||||||
}
|
}
|
||||||
|
|
||||||
class SimpleColumnBlockComponentBuilder extends BlockComponentBuilder {
|
class SimpleColumnBlockComponentBuilder extends BlockComponentBuilder {
|
||||||
SimpleColumnBlockComponentBuilder({super.configuration});
|
SimpleColumnBlockComponentBuilder({
|
||||||
|
super.configuration,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
|
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
|
||||||
@ -74,16 +109,6 @@ class SimpleColumnBlockComponentState extends State<SimpleColumnBlockComponent>
|
|||||||
|
|
||||||
late final EditorState editorState = context.read<EditorState>();
|
late final EditorState editorState = context.read<EditorState>();
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget child = Column(
|
Widget child = Column(
|
||||||
@ -121,7 +146,7 @@ class SimpleColumnBlockComponentState extends State<SimpleColumnBlockComponent>
|
|||||||
if (SimpleColumnsBlockConstants.enableDebugBorder) {
|
if (SimpleColumnsBlockConstants.enableDebugBorder) {
|
||||||
child = Container(
|
child = Container(
|
||||||
color: Colors.green.withValues(
|
color: Colors.green.withValues(
|
||||||
alpha: 0.2,
|
alpha: 0.3,
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/columns/simple_columns_block_constant.dart';
|
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -26,6 +25,13 @@ class _SimpleColumnBlockWidthResizerState
|
|||||||
|
|
||||||
ValueNotifier<bool> isHovering = ValueNotifier(false);
|
ValueNotifier<bool> isHovering = ValueNotifier(false);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
isHovering.dispose();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
@ -78,25 +84,47 @@ class _SimpleColumnBlockWidthResizerState
|
|||||||
|
|
||||||
// update the column width in memory
|
// update the column width in memory
|
||||||
final columnNode = widget.columnNode;
|
final columnNode = widget.columnNode;
|
||||||
|
final columnsNode = columnNode.columnsParent;
|
||||||
|
if (columnsNode == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final editorWidth = columnsNode.rect.width;
|
||||||
final rect = columnNode.rect;
|
final rect = columnNode.rect;
|
||||||
final width =
|
final width = rect.width;
|
||||||
columnNode.attributes[SimpleColumnBlockKeys.width] ?? rect.width;
|
final originalRatio = columnNode.attributes[SimpleColumnBlockKeys.ratio];
|
||||||
final newWidth = width + details.delta.dx;
|
final newWidth = width + details.delta.dx;
|
||||||
|
|
||||||
final transaction = widget.editorState.transaction;
|
final transaction = widget.editorState.transaction;
|
||||||
|
final newRatio = newWidth / editorWidth;
|
||||||
transaction.updateNode(columnNode, {
|
transaction.updateNode(columnNode, {
|
||||||
...columnNode.attributes,
|
...columnNode.attributes,
|
||||||
SimpleColumnBlockKeys.width: newWidth.clamp(
|
SimpleColumnBlockKeys.ratio: newRatio,
|
||||||
SimpleColumnsBlockConstants.minimumColumnWidth,
|
|
||||||
double.infinity,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
final columnsNode = columnNode.parent;
|
|
||||||
if (columnsNode != null) {
|
if (newRatio < 0.1 && newRatio < originalRatio) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final nextColumn = columnNode.nextColumn;
|
||||||
|
if (nextColumn != null) {
|
||||||
|
final nextColumnRect = nextColumn.rect;
|
||||||
|
final nextColumnWidth = nextColumnRect.width;
|
||||||
|
final newNextColumnWidth = nextColumnWidth - details.delta.dx;
|
||||||
|
final newNextColumnRatio = newNextColumnWidth / editorWidth;
|
||||||
|
if (newNextColumnRatio < 0.1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
transaction.updateNode(nextColumn, {
|
||||||
|
...nextColumn.attributes,
|
||||||
|
SimpleColumnBlockKeys.ratio: newNextColumnRatio,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
transaction.updateNode(columnsNode, {
|
transaction.updateNode(columnsNode, {
|
||||||
...columnsNode.attributes,
|
...columnsNode.attributes,
|
||||||
ColumnsBlockKeys.columnCount: columnsNode.children.length,
|
ColumnsBlockKeys.columnCount: columnsNode.children.length,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
widget.editorState.apply(
|
widget.editorState.apply(
|
||||||
transaction,
|
transaction,
|
||||||
options: ApplyOptions(inMemoryUpdate: true),
|
options: ApplyOptions(inMemoryUpdate: true),
|
||||||
@ -113,9 +141,15 @@ class _SimpleColumnBlockWidthResizerState
|
|||||||
|
|
||||||
// apply the transaction again to make sure the width is updated
|
// apply the transaction again to make sure the width is updated
|
||||||
final transaction = widget.editorState.transaction;
|
final transaction = widget.editorState.transaction;
|
||||||
transaction.updateNode(widget.columnNode, {
|
final columnsNode = widget.columnNode.columnsParent;
|
||||||
...widget.columnNode.attributes,
|
if (columnsNode == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (final columnNode in columnsNode.children) {
|
||||||
|
transaction.updateNode(columnNode, {
|
||||||
|
...columnNode.attributes,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
widget.editorState.apply(transaction);
|
widget.editorState.apply(transaction);
|
||||||
|
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
|
@ -3,7 +3,7 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
|||||||
|
|
||||||
extension SimpleColumnNodeExtension on Node {
|
extension SimpleColumnNodeExtension on Node {
|
||||||
/// Returns the parent [Node] of the current node if it is a [SimpleColumnsBlock].
|
/// Returns the parent [Node] of the current node if it is a [SimpleColumnsBlock].
|
||||||
Node? get parentColumnsBlock {
|
Node? get columnsParent {
|
||||||
Node? currentNode = parent;
|
Node? currentNode = parent;
|
||||||
while (currentNode != null) {
|
while (currentNode != null) {
|
||||||
if (currentNode.type == SimpleColumnsBlockKeys.type) {
|
if (currentNode.type == SimpleColumnsBlockKeys.type) {
|
||||||
@ -15,7 +15,7 @@ extension SimpleColumnNodeExtension on Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the parent [Node] of the current node if it is a [SimpleColumnBlock].
|
/// Returns the parent [Node] of the current node if it is a [SimpleColumnBlock].
|
||||||
Node? get parentColumn {
|
Node? get columnParent {
|
||||||
Node? currentNode = parent;
|
Node? currentNode = parent;
|
||||||
while (currentNode != null) {
|
while (currentNode != null) {
|
||||||
if (currentNode.type == SimpleColumnBlockKeys.type) {
|
if (currentNode.type == SimpleColumnBlockKeys.type) {
|
||||||
@ -27,8 +27,8 @@ extension SimpleColumnNodeExtension on Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the current node is in a [SimpleColumnsBlock].
|
/// Returns whether the current node is in a [SimpleColumnsBlock].
|
||||||
bool get isInColumnsBlock => parentColumnsBlock != null;
|
bool get isInColumnsBlock => columnsParent != null;
|
||||||
|
|
||||||
/// Returns whether the current node is in a [SimpleColumnBlock].
|
/// Returns whether the current node is in a [SimpleColumnBlock].
|
||||||
bool get isInColumnBlock => parentColumn != null;
|
bool get isInColumnBlock => columnParent != null;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/columns/simple_columns_block_constant.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/columns/simple_columns_block_constant.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
@ -5,20 +7,19 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:universal_platform/universal_platform.dart';
|
|
||||||
|
|
||||||
// if the children is not provided, it will create two columns by default.
|
// if the children is not provided, it will create two columns by default.
|
||||||
// if the columnCount is provided, it will create the specified number of columns.
|
// if the columnCount is provided, it will create the specified number of columns.
|
||||||
Node simpleColumnsNode({
|
Node simpleColumnsNode({
|
||||||
List<Node>? children,
|
List<Node>? children,
|
||||||
int? columnCount,
|
int? columnCount,
|
||||||
double? width,
|
double? ratio,
|
||||||
}) {
|
}) {
|
||||||
columnCount ??= 2;
|
columnCount ??= 2;
|
||||||
children ??= List.generate(
|
children ??= List.generate(
|
||||||
columnCount,
|
columnCount,
|
||||||
(index) => simpleColumnNode(
|
(index) => simpleColumnNode(
|
||||||
width: width,
|
ratio: ratio,
|
||||||
children: [paragraphNode()],
|
children: [paragraphNode()],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -91,6 +92,13 @@ class ColumnsBlockComponentState extends State<ColumnsBlockComponent>
|
|||||||
|
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_updateColumnsBlock();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
scrollController.dispose();
|
scrollController.dispose();
|
||||||
@ -100,23 +108,12 @@ class ColumnsBlockComponentState extends State<ColumnsBlockComponent>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget child = SingleChildScrollView(
|
Widget child = Row(
|
||||||
scrollDirection: Axis.horizontal,
|
mainAxisSize: MainAxisSize.min,
|
||||||
controller: scrollController,
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: _buildChildren(),
|
children: _buildChildren(),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (UniversalPlatform.isDesktop) {
|
|
||||||
// only show the scrollbar on desktop
|
|
||||||
child = Scrollbar(
|
|
||||||
controller: scrollController,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
child = Align(
|
child = Align(
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
child: IntrinsicHeight(
|
child: IntrinsicHeight(
|
||||||
@ -148,19 +145,18 @@ class ColumnsBlockComponentState extends State<ColumnsBlockComponent>
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildChildren() {
|
List<Widget> _buildChildren() {
|
||||||
|
final length = node.children.length;
|
||||||
final children = <Widget>[];
|
final children = <Widget>[];
|
||||||
for (var i = 0; i < node.children.length; i++) {
|
for (var i = 0; i < length; i++) {
|
||||||
final childNode = node.children[i];
|
final childNode = node.children[i];
|
||||||
final width =
|
final double ratio =
|
||||||
childNode.attributes[SimpleColumnBlockKeys.width]?.toDouble() ??
|
childNode.attributes[SimpleColumnBlockKeys.ratio]?.toDouble() ??
|
||||||
SimpleColumnsBlockConstants.minimumColumnWidth;
|
1.0 / length;
|
||||||
|
|
||||||
Widget child = editorState.renderer.build(context, childNode);
|
Widget child = editorState.renderer.build(context, childNode);
|
||||||
|
|
||||||
child = SizedBox(
|
child = Expanded(
|
||||||
width: width.clamp(
|
flex: (max(ratio, 0.1) * 10000).toInt(),
|
||||||
SimpleColumnsBlockConstants.minimumColumnWidth,
|
|
||||||
double.infinity,
|
|
||||||
),
|
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -176,6 +172,26 @@ class ColumnsBlockComponentState extends State<ColumnsBlockComponent>
|
|||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the existing columns block data
|
||||||
|
// if the column ratio is not existing, it will be set to 1.0 / columnCount
|
||||||
|
void _updateColumnsBlock() {
|
||||||
|
final transaction = editorState.transaction;
|
||||||
|
final length = node.children.length;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
final childNode = node.children[i];
|
||||||
|
final ratio = childNode.attributes[SimpleColumnBlockKeys.ratio];
|
||||||
|
if (ratio == null) {
|
||||||
|
transaction.updateNode(childNode, {
|
||||||
|
...childNode.attributes,
|
||||||
|
SimpleColumnBlockKeys.ratio: 1.0 / length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (transaction.operations.isNotEmpty) {
|
||||||
|
editorState.apply(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Position start() => Position(path: widget.node.path);
|
Position start() => Position(path: widget.node.path);
|
||||||
|
|
||||||
|
@ -227,6 +227,7 @@ class CustomImageBlockComponentState extends State<CustomImageBlockComponent>
|
|||||||
delegate: this,
|
delegate: this,
|
||||||
listenable: editorState.selectionNotifier,
|
listenable: editorState.selectionNotifier,
|
||||||
blockColor: editorState.editorStyle.selectionColor,
|
blockColor: editorState.editorStyle.selectionColor,
|
||||||
|
selectionAboveBlock: true,
|
||||||
supportTypes: const [BlockSelectionType.block],
|
supportTypes: const [BlockSelectionType.block],
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
@ -313,7 +314,7 @@ class CustomImageBlockComponentState extends State<CustomImageBlockComponent>
|
|||||||
}) {
|
}) {
|
||||||
final imageBox = imageKey.currentContext?.findRenderObject();
|
final imageBox = imageKey.currentContext?.findRenderObject();
|
||||||
if (imageBox is RenderBox) {
|
if (imageBox is RenderBox) {
|
||||||
return Offset.zero & imageBox.size;
|
return padding.topLeft & imageBox.size;
|
||||||
}
|
}
|
||||||
return Rect.zero;
|
return Rect.zero;
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,9 @@ class _OutlineBlockWidgetState extends State<OutlineBlockWidget>
|
|||||||
with
|
with
|
||||||
BlockComponentConfigurable,
|
BlockComponentConfigurable,
|
||||||
BlockComponentTextDirectionMixin,
|
BlockComponentTextDirectionMixin,
|
||||||
BlockComponentBackgroundColorMixin {
|
BlockComponentBackgroundColorMixin,
|
||||||
|
DefaultSelectableMixin,
|
||||||
|
SelectableMixin {
|
||||||
// Change the value if the heading block type supports heading levels greater than '3'
|
// Change the value if the heading block type supports heading levels greater than '3'
|
||||||
static const maxVisibleDepth = 6;
|
static const maxVisibleDepth = 6;
|
||||||
|
|
||||||
@ -95,6 +97,17 @@ class _OutlineBlockWidgetState extends State<OutlineBlockWidget>
|
|||||||
late EditorState editorState = context.read<EditorState>();
|
late EditorState editorState = context.read<EditorState>();
|
||||||
late Stream<EditorTransactionValue> stream = editorState.transactionStream;
|
late Stream<EditorTransactionValue> stream = editorState.transactionStream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
GlobalKey<State<StatefulWidget>> blockComponentKey = GlobalKey(
|
||||||
|
debugLabel: OutlineBlockKeys.type,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
GlobalKey<State<StatefulWidget>> get containerKey => widget.node.key;
|
||||||
|
|
||||||
|
@override
|
||||||
|
GlobalKey<State<StatefulWidget>> get forwardKey => widget.node.key;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return StreamBuilder(
|
return StreamBuilder(
|
||||||
@ -102,6 +115,19 @@ class _OutlineBlockWidgetState extends State<OutlineBlockWidget>
|
|||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
Widget child = _buildOutlineBlock();
|
Widget child = _buildOutlineBlock();
|
||||||
|
|
||||||
|
child = BlockSelectionContainer(
|
||||||
|
node: node,
|
||||||
|
delegate: this,
|
||||||
|
listenable: editorState.selectionNotifier,
|
||||||
|
remoteSelection: editorState.remoteSelections,
|
||||||
|
blockColor: editorState.editorStyle.selectionColor,
|
||||||
|
selectionAboveBlock: true,
|
||||||
|
supportTypes: const [
|
||||||
|
BlockSelectionType.block,
|
||||||
|
],
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
|
||||||
if (UniversalPlatform.isDesktopOrWeb) {
|
if (UniversalPlatform.isDesktopOrWeb) {
|
||||||
if (widget.showActions && widget.actionBuilder != null) {
|
if (widget.showActions && widget.actionBuilder != null) {
|
||||||
child = BlockComponentActionWrapper(
|
child = BlockComponentActionWrapper(
|
||||||
@ -176,6 +202,7 @@ class _OutlineBlockWidgetState extends State<OutlineBlockWidget>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
|
key: blockComponentKey,
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
minHeight: 40.0,
|
minHeight: 40.0,
|
||||||
),
|
),
|
||||||
|
@ -90,13 +90,8 @@ SelectionMenuItem fourColumnsSlashMenuItem = SelectionMenuItem.node(
|
|||||||
);
|
);
|
||||||
|
|
||||||
Node _buildColumnsNode(EditorState editorState, int columnCount) {
|
Node _buildColumnsNode(EditorState editorState, int columnCount) {
|
||||||
final selection = editorState.selection;
|
return simpleColumnsNode(
|
||||||
double? width;
|
columnCount: columnCount,
|
||||||
if (selection != null) {
|
ratio: 1.0 / columnCount,
|
||||||
final parentNode = editorState.getNodeAtPath(selection.start.path);
|
);
|
||||||
if (parentNode != null) {
|
|
||||||
width = parentNode.rect.width / columnCount - 16;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return simpleColumnsNode(columnCount: columnCount, width: width);
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user