feat: notify transaction instead of operations
This commit is contained in:
parent
e91f810d73
commit
64bab90f1a
4 changed files with 8 additions and 7 deletions
|
|
@ -109,8 +109,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
..handler = (message) {
|
||||
debugPrint(message);
|
||||
};
|
||||
_editorState!.operationStream.listen((event) {
|
||||
debugPrint('Operation: ${event.toJson()}');
|
||||
_editorState!.transactionStream.listen((event) {
|
||||
debugPrint('Transaction: ${event.toJson()}');
|
||||
});
|
||||
return Container(
|
||||
color: darkMode ? Colors.black : Colors.white,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import 'package:appflowy_editor/src/core/location/selection.dart';
|
|||
import 'package:appflowy_editor/src/core/document/document.dart';
|
||||
import 'package:appflowy_editor/src/core/transform/operation.dart';
|
||||
import 'package:appflowy_editor/src/core/transform/transaction.dart';
|
||||
import 'package:appflowy_editor/src/undo_manager.dart';
|
||||
import 'package:appflowy_editor/src/history/undo_manager.dart';
|
||||
|
||||
class ApplyOptions {
|
||||
/// This flag indicates that
|
||||
|
|
@ -63,8 +63,8 @@ class EditorState {
|
|||
EditorStyle editorStyle = EditorStyle.defaultStyle();
|
||||
|
||||
/// Operation stream.
|
||||
Stream<Operation> get operationStream => _observer.stream;
|
||||
final StreamController<Operation> _observer = StreamController.broadcast();
|
||||
Stream<Transaction> get transactionStream => _observer.stream;
|
||||
final StreamController<Transaction> _observer = StreamController.broadcast();
|
||||
|
||||
final UndoManager undoManager = UndoManager();
|
||||
Selection? _cursorSelection;
|
||||
|
|
@ -140,6 +140,8 @@ class EditorState {
|
|||
_applyOperation(op);
|
||||
}
|
||||
|
||||
_observer.add(transaction);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
updateCursorSelection(transaction.afterSelection);
|
||||
});
|
||||
|
|
@ -187,6 +189,5 @@ class EditorState {
|
|||
} else if (op is UpdateTextOperation) {
|
||||
document.updateText(op.path, op.delta);
|
||||
}
|
||||
_observer.add(op);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:appflowy_editor/src/core/location/selection.dart';
|
||||
import 'package:appflowy_editor/src/infra/log.dart';
|
||||
import 'package:appflowy_editor/src/core/transform/operation.dart';
|
||||
import 'package:appflowy_editor/src/core/transform/transaction.dart';
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
|
||||
/// A [HistoryItem] contains list of operations committed by users.
|
||||
/// If a [HistoryItem] is not sealed, operations can be added sequentially.
|
||||
/// Otherwise, the operations should be added to a new [HistoryItem].
|
||||
class HistoryItem extends LinkedListEntry<HistoryItem> {
|
||||
final List<Operation> operations = [];
|
||||
Selection? beforeSelection;
|
||||
Selection? afterSelection;
|
||||
bool _sealed = false;
|
||||
|
||||
HistoryItem();
|
||||
|
||||
/// Seal the history item.
|
||||
/// When an item is sealed, no more operations can be added
|
||||
/// to the item.
|
||||
///
|
||||
/// The caller should create a new [HistoryItem].
|
||||
seal() {
|
||||
_sealed = true;
|
||||
}
|
||||
|
||||
bool get sealed => _sealed;
|
||||
|
||||
add(Operation op) {
|
||||
operations.add(op);
|
||||
}
|
||||
|
||||
addAll(Iterable<Operation> iterable) {
|
||||
operations.addAll(iterable);
|
||||
}
|
||||
|
||||
/// Create a new [Transaction] by inverting the operations.
|
||||
Transaction toTransaction(EditorState state) {
|
||||
final builder = Transaction(document: state.document);
|
||||
for (var i = operations.length - 1; i >= 0; i--) {
|
||||
final operation = operations[i];
|
||||
final inverted = operation.invert();
|
||||
builder.add(inverted, transform: false);
|
||||
}
|
||||
builder.afterSelection = beforeSelection;
|
||||
builder.beforeSelection = afterSelection;
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
class FixedSizeStack {
|
||||
final _list = LinkedList<HistoryItem>();
|
||||
final int maxSize;
|
||||
|
||||
FixedSizeStack(this.maxSize);
|
||||
|
||||
push(HistoryItem stackItem) {
|
||||
if (_list.length >= maxSize) {
|
||||
_list.remove(_list.first);
|
||||
}
|
||||
_list.add(stackItem);
|
||||
}
|
||||
|
||||
HistoryItem? pop() {
|
||||
if (_list.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final last = _list.last;
|
||||
|
||||
_list.remove(last);
|
||||
|
||||
return last;
|
||||
}
|
||||
|
||||
clear() {
|
||||
_list.clear();
|
||||
}
|
||||
|
||||
HistoryItem get last => _list.last;
|
||||
|
||||
bool get isEmpty => _list.isEmpty;
|
||||
|
||||
bool get isNonEmpty => _list.isNotEmpty;
|
||||
}
|
||||
|
||||
class UndoManager {
|
||||
final FixedSizeStack undoStack;
|
||||
final FixedSizeStack redoStack;
|
||||
EditorState? state;
|
||||
|
||||
UndoManager([int stackSize = 20])
|
||||
: undoStack = FixedSizeStack(stackSize),
|
||||
redoStack = FixedSizeStack(stackSize);
|
||||
|
||||
HistoryItem getUndoHistoryItem() {
|
||||
if (undoStack.isEmpty) {
|
||||
final item = HistoryItem();
|
||||
undoStack.push(item);
|
||||
return item;
|
||||
}
|
||||
final last = undoStack.last;
|
||||
if (last.sealed) {
|
||||
redoStack.clear();
|
||||
final item = HistoryItem();
|
||||
undoStack.push(item);
|
||||
return item;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
undo() {
|
||||
Log.editor.debug('undo');
|
||||
final s = state;
|
||||
if (s == null) {
|
||||
return;
|
||||
}
|
||||
final historyItem = undoStack.pop();
|
||||
if (historyItem == null) {
|
||||
return;
|
||||
}
|
||||
final transaction = historyItem.toTransaction(s);
|
||||
s.apply(
|
||||
transaction,
|
||||
const ApplyOptions(
|
||||
recordUndo: false,
|
||||
recordRedo: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
redo() {
|
||||
Log.editor.debug('redo');
|
||||
final s = state;
|
||||
if (s == null) {
|
||||
return;
|
||||
}
|
||||
final historyItem = redoStack.pop();
|
||||
if (historyItem == null) {
|
||||
return;
|
||||
}
|
||||
final transaction = historyItem.toTransaction(s);
|
||||
s.apply(
|
||||
transaction,
|
||||
const ApplyOptions(
|
||||
recordUndo: true,
|
||||
recordRedo: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:collection';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/undo_manager.dart';
|
||||
import 'package:appflowy_editor/src/history/undo_manager.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() async {
|
||||
|
|
|
|||
Loading…
Reference in a new issue