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(
|
||||
child: _build(context),
|
||||
onTap: () {
|
||||
editorState.update(node, {
|
||||
'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"
|
||||
});
|
||||
TransactionBuilder(editorState)
|
||||
..updateNode(node, {
|
||||
'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
|
||||
void updateEditingValue(TextEditingValue value) {
|
||||
debugPrint(value.text);
|
||||
editorState.update(node, {'content': value.text});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -159,12 +159,21 @@ class Node extends ChangeNotifier with LinkedListEntry<Node> {
|
||||
}
|
||||
|
||||
class TextNode extends Node {
|
||||
final Delta delta;
|
||||
Delta _delta;
|
||||
|
||||
TextNode({
|
||||
required super.type,
|
||||
required super.children,
|
||||
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/path.dart';
|
||||
import 'package:flowy_editor/document/text_delta.dart';
|
||||
import './attributes.dart';
|
||||
|
||||
class StateTree {
|
||||
@ -35,6 +36,18 @@ class StateTree {
|
||||
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) {
|
||||
if (path.isEmpty) {
|
||||
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) {
|
||||
if (op is InsertOperation) {
|
||||
document.insert(op.path, op.value);
|
||||
@ -62,6 +43,8 @@ class EditorState {
|
||||
document.update(op.path, op.attributes);
|
||||
} else if (op is DeleteOperation) {
|
||||
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/node_widget_builder.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/editor_state.dart';
|
||||
|
@ -67,14 +67,16 @@ class DeleteOperation extends Operation {
|
||||
class TextEditOperation extends Operation {
|
||||
final Path path;
|
||||
final Delta delta;
|
||||
final Delta inverted;
|
||||
|
||||
TextEditOperation({
|
||||
required this.path,
|
||||
required this.delta,
|
||||
required this.inverted,
|
||||
});
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
/// 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 {
|
||||
final List<Operation> operations;
|
||||
Transaction([this.operations = const []]);
|
||||
final UnmodifiableListView<Operation> operations;
|
||||
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