mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #627 from vincentdchan/feat/transaction-builder
Feat: transaction builder
This commit is contained in:
commit
32e5947bed
@ -33,10 +33,12 @@ class _ImageNodeWidget extends StatelessWidget {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
child: _build(context),
|
child: _build(context),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
editorState.update(node, {
|
TransactionBuilder(editorState)
|
||||||
'image_src':
|
..updateNode(node, {
|
||||||
"https://images.pexels.com/photos/9995076/pexels-photo-9995076.png?cs=srgb&dl=pexels-temmuz-uzun-9995076.jpg&fm=jpg&w=640&h=400"
|
'image_src':
|
||||||
});
|
"https://images.pexels.com/photos/9995076/pexels-photo-9995076.png?cs=srgb&dl=pexels-temmuz-uzun-9995076.jpg&fm=jpg&w=640&h=400"
|
||||||
|
})
|
||||||
|
..commit();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -187,7 +187,6 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
|
|||||||
@override
|
@override
|
||||||
void updateEditingValue(TextEditingValue value) {
|
void updateEditingValue(TextEditingValue value) {
|
||||||
debugPrint(value.text);
|
debugPrint(value.text);
|
||||||
editorState.update(node, {'content': value.text});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -159,12 +159,21 @@ class Node extends ChangeNotifier with LinkedListEntry<Node> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TextNode extends Node {
|
class TextNode extends Node {
|
||||||
final Delta delta;
|
Delta _delta;
|
||||||
|
|
||||||
TextNode({
|
TextNode({
|
||||||
required super.type,
|
required super.type,
|
||||||
required super.children,
|
required super.children,
|
||||||
required super.attributes,
|
required super.attributes,
|
||||||
required this.delta,
|
required Delta delta,
|
||||||
});
|
}) : _delta = delta;
|
||||||
|
|
||||||
|
Delta get delta {
|
||||||
|
return _delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
set delta(Delta v) {
|
||||||
|
_delta = v;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flowy_editor/document/node.dart';
|
import 'package:flowy_editor/document/node.dart';
|
||||||
import 'package:flowy_editor/document/path.dart';
|
import 'package:flowy_editor/document/path.dart';
|
||||||
|
import 'package:flowy_editor/document/text_delta.dart';
|
||||||
import './attributes.dart';
|
import './attributes.dart';
|
||||||
|
|
||||||
class StateTree {
|
class StateTree {
|
||||||
@ -35,6 +36,18 @@ class StateTree {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool textEdit(Path path, Delta delta) {
|
||||||
|
if (path.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var node = root.childAtPath(path);
|
||||||
|
if (node == null || node is! TextNode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
node.delta = node.delta.compose(delta);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Node? delete(Path path) {
|
Node? delete(Path path) {
|
||||||
if (path.isEmpty) {
|
if (path.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -36,25 +36,6 @@ class EditorState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to a better place.
|
|
||||||
void update(Node node, Attributes attributes) {
|
|
||||||
_applyOperation(UpdateOperation(
|
|
||||||
path: node.path,
|
|
||||||
attributes: Attributes.from(node.attributes)..addAll(attributes),
|
|
||||||
oldAttributes: node.attributes,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: move to a better place.
|
|
||||||
void delete(Node node) {
|
|
||||||
_applyOperation(
|
|
||||||
DeleteOperation(
|
|
||||||
path: node.path,
|
|
||||||
removedValue: node,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _applyOperation(Operation op) {
|
void _applyOperation(Operation op) {
|
||||||
if (op is InsertOperation) {
|
if (op is InsertOperation) {
|
||||||
document.insert(op.path, op.value);
|
document.insert(op.path, op.value);
|
||||||
@ -62,6 +43,8 @@ class EditorState {
|
|||||||
document.update(op.path, op.attributes);
|
document.update(op.path, op.attributes);
|
||||||
} else if (op is DeleteOperation) {
|
} else if (op is DeleteOperation) {
|
||||||
document.delete(op.path);
|
document.delete(op.path);
|
||||||
|
} else if (op is TextEditOperation) {
|
||||||
|
document.textEdit(op.path, op.delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,6 @@ export 'package:flowy_editor/document/path.dart';
|
|||||||
export 'package:flowy_editor/render/render_plugins.dart';
|
export 'package:flowy_editor/render/render_plugins.dart';
|
||||||
export 'package:flowy_editor/render/node_widget_builder.dart';
|
export 'package:flowy_editor/render/node_widget_builder.dart';
|
||||||
export 'package:flowy_editor/operation/transaction.dart';
|
export 'package:flowy_editor/operation/transaction.dart';
|
||||||
|
export 'package:flowy_editor/operation/transaction_builder.dart';
|
||||||
export 'package:flowy_editor/operation/operation.dart';
|
export 'package:flowy_editor/operation/operation.dart';
|
||||||
export 'package:flowy_editor/editor_state.dart';
|
export 'package:flowy_editor/editor_state.dart';
|
||||||
|
@ -67,14 +67,16 @@ class DeleteOperation extends Operation {
|
|||||||
class TextEditOperation extends Operation {
|
class TextEditOperation extends Operation {
|
||||||
final Path path;
|
final Path path;
|
||||||
final Delta delta;
|
final Delta delta;
|
||||||
|
final Delta inverted;
|
||||||
|
|
||||||
TextEditOperation({
|
TextEditOperation({
|
||||||
required this.path,
|
required this.path,
|
||||||
required this.delta,
|
required this.delta,
|
||||||
|
required this.inverted,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Operation invert() {
|
Operation invert() {
|
||||||
return TextEditOperation(path: path, delta: delta);
|
return TextEditOperation(path: path, delta: inverted, inverted: delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,28 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flowy_editor/document/selection.dart';
|
||||||
import './operation.dart';
|
import './operation.dart';
|
||||||
|
|
||||||
|
/// This class to use to store the **changes**
|
||||||
|
/// will be applied to the editor.
|
||||||
|
///
|
||||||
|
/// This class is immutable version the the class
|
||||||
|
/// [[Transaction]]. Is used to stored and
|
||||||
|
/// transmit. If you want to build the transaction,
|
||||||
|
/// use [[Transaction]] directly.
|
||||||
|
///
|
||||||
|
/// There will be several ways to consume the transaction:
|
||||||
|
/// 1. Apply to the state to update the UI.
|
||||||
|
/// 2. Send to the backend to store and do operation transforming.
|
||||||
|
/// 3. Stored by the UndoManager to implement redo/undo.
|
||||||
|
///
|
||||||
|
@immutable
|
||||||
class Transaction {
|
class Transaction {
|
||||||
final List<Operation> operations;
|
final UnmodifiableListView<Operation> operations;
|
||||||
Transaction([this.operations = const []]);
|
final Selection? cursorSelection;
|
||||||
|
|
||||||
|
const Transaction({
|
||||||
|
required this.operations,
|
||||||
|
this.cursorSelection,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
import 'package:flowy_editor/editor_state.dart';
|
||||||
|
import 'package:flowy_editor/document/node.dart';
|
||||||
|
import 'package:flowy_editor/document/path.dart';
|
||||||
|
import 'package:flowy_editor/document/text_delta.dart';
|
||||||
|
import 'package:flowy_editor/document/attributes.dart';
|
||||||
|
import 'package:flowy_editor/document/selection.dart';
|
||||||
|
|
||||||
|
import './operation.dart';
|
||||||
|
import './transaction.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
/// This class is used to
|
||||||
|
/// build the transaction from the state.
|
||||||
|
///
|
||||||
|
/// This class automatically save the
|
||||||
|
/// cursor from the state.
|
||||||
|
///
|
||||||
|
/// When the transaction is undo, the
|
||||||
|
/// cursor can be restored.
|
||||||
|
///
|
||||||
|
class TransactionBuilder {
|
||||||
|
final List<Operation> operations = [];
|
||||||
|
EditorState state;
|
||||||
|
Selection? cursorSelection;
|
||||||
|
|
||||||
|
TransactionBuilder(this.state);
|
||||||
|
|
||||||
|
commit() {
|
||||||
|
final transaction = _finish();
|
||||||
|
state.apply(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
void insertNode(Path path, Node node) {
|
||||||
|
cursorSelection = state.cursorSelection;
|
||||||
|
operations.add(InsertOperation(path: path, value: node));
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateNode(Node node, Attributes attributes) {
|
||||||
|
cursorSelection = state.cursorSelection;
|
||||||
|
operations.add(UpdateOperation(
|
||||||
|
path: node.path,
|
||||||
|
attributes: Attributes.from(node.attributes)..addAll(attributes),
|
||||||
|
oldAttributes: node.attributes,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteNode(Node node) {
|
||||||
|
cursorSelection = state.cursorSelection;
|
||||||
|
operations.add(DeleteOperation(path: node.path, removedValue: node));
|
||||||
|
}
|
||||||
|
|
||||||
|
void textEdit(TextNode node, Delta Function() f) {
|
||||||
|
cursorSelection = state.cursorSelection;
|
||||||
|
final path = node.path;
|
||||||
|
|
||||||
|
final delta = f();
|
||||||
|
|
||||||
|
final inverted = delta.invert(node.delta);
|
||||||
|
operations
|
||||||
|
.add(TextEditOperation(path: path, delta: delta, inverted: inverted));
|
||||||
|
}
|
||||||
|
|
||||||
|
Transaction _finish() {
|
||||||
|
return Transaction(
|
||||||
|
operations: UnmodifiableListView(operations),
|
||||||
|
cursorSelection: cursorSelection,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user