feat: notify transaction instead of operations

This commit is contained in:
Lucas.Xu 2022-10-13 11:55:16 +08:00
commit 64bab90f1a
4 changed files with 8 additions and 7 deletions

View file

@ -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,

View file

@ -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);
}
}

View file

@ -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,
),
);
}
}

View file

@ -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 {