Basic implementation of undo/redo

It's not great, as the granularity is too low. But it's a good first
step. It only works in the RawEditor so far.
This commit is contained in:
Vishesh Handa
2021-02-03 16:03:01 +01:00
parent e80fff575c
commit ebff34b753
7 changed files with 118 additions and 13 deletions

View File

@ -24,6 +24,9 @@ class EditorBottomBar extends StatelessWidget {
final Func0<void> onZenModeChanged;
final bool metaDataEditable;
final bool undoAllowed;
final bool redoAllowed;
final Func0<void> onUndoSelected;
final Func0<void> onRedoSelected;
@ -37,6 +40,8 @@ class EditorBottomBar extends StatelessWidget {
@required this.metaDataEditable,
@required this.onUndoSelected,
@required this.onRedoSelected,
@required this.undoAllowed,
@required this.redoAllowed,
});
@override
@ -84,17 +89,24 @@ class EditorBottomBar extends StatelessWidget {
maintainState: true,
maintainInteractivity: false,
),
// TODO: Add the undo and redo icons
Expanded(
child: FlatButton.icon(
icon: const Icon(Icons.folder),
label: Text(parentFolder.publicName),
onPressed: () {
var note = editorState.getNote();
editor.moveNoteToFolderSelected(note);
},
),
const Spacer(),
IconButton(
icon: const Icon(Icons.undo),
onPressed: undoAllowed ? onUndoSelected : null,
),
FlatButton.icon(
icon: const Icon(Icons.folder),
label: Text(parentFolder.publicName),
onPressed: () {
var note = editorState.getNote();
editor.moveNoteToFolderSelected(note);
},
),
IconButton(
icon: const Icon(Icons.redo),
onPressed: redoAllowed ? onRedoSelected : null,
),
const Spacer(),
menuIcon,
],
mainAxisAlignment: MainAxisAlignment.center,

View File

@ -185,6 +185,8 @@ class ChecklistEditorState extends State<ChecklistEditor>
),
onUndoSelected: _undo,
onRedoSelected: _redo,
undoAllowed: false,
redoAllowed: false,
);
}

View File

@ -108,6 +108,8 @@ class JournalEditorState extends State<JournalEditor>
body: editor,
onUndoSelected: _undo,
onRedoSelected: _redo,
undoAllowed: false,
redoAllowed: false,
);
}

View File

@ -142,6 +142,8 @@ class MarkdownEditorState extends State<MarkdownEditor>
body: editor,
onUndoSelected: _undo,
onRedoSelected: _redo,
undoAllowed: false,
redoAllowed: false,
);
}

View File

@ -8,6 +8,7 @@ import 'package:gitjournal/core/md_yaml_doc_codec.dart';
import 'package:gitjournal/core/note.dart';
import 'package:gitjournal/editors/common.dart';
import 'package:gitjournal/editors/disposable_change_notifier.dart';
import 'package:gitjournal/editors/undo_redo.dart';
import 'package:gitjournal/widgets/editor_scroll_view.dart';
class RawEditor extends StatefulWidget implements Editor {
@ -56,12 +57,14 @@ class RawEditorState extends State<RawEditor>
implements EditorState {
Note note;
bool _noteModified;
TextEditingController _textController = TextEditingController();
TextEditingController _textController;
UndoRedoStack _undoRedoStack;
final serializer = MarkdownYAMLCodec();
RawEditorState(this.note) {
_textController = TextEditingController(text: serializer.encode(note.data));
_undoRedoStack = UndoRedoStack();
}
@override
@ -106,6 +109,8 @@ class RawEditorState extends State<RawEditor>
body: editor,
onUndoSelected: _undo,
onRedoSelected: _redo,
undoAllowed: _undoRedoStack.undoPossible,
redoAllowed: _undoRedoStack.redoPossible,
);
}
@ -118,6 +123,12 @@ class RawEditorState extends State<RawEditor>
void _noteTextChanged() {
notifyListeners();
var editState = TextEditorState.fromValue(_textController.value);
var redraw = _undoRedoStack.textChanged(editState);
if (redraw) {
setState(() {});
}
if (_noteModified) return;
setState(() {
_noteModified = true;
@ -136,9 +147,21 @@ class RawEditorState extends State<RawEditor>
@override
bool get noteModified => _noteModified;
Future<void> _undo() async {}
Future<void> _undo() async {
var es = _undoRedoStack.undo();
_textController.value = es.toValue();
setState(() {
// To Redraw the undo/redo button state
});
}
Future<void> _redo() async {}
Future<void> _redo() async {
var es = _undoRedoStack.redo();
_textController.value = es.toValue();
setState(() {
// To Redraw the undo/redo button state
});
}
}
class _NoteEditor extends StatelessWidget {

View File

@ -24,6 +24,9 @@ class EditorScaffold extends StatefulWidget {
final Func0<void> onUndoSelected;
final Func0<void> onRedoSelected;
final bool undoAllowed;
final bool redoAllowed;
EditorScaffold({
@required this.editor,
@required this.editorState,
@ -33,6 +36,8 @@ class EditorScaffold extends StatefulWidget {
@required this.parentFolder,
@required this.onUndoSelected,
@required this.onRedoSelected,
@required this.undoAllowed,
@required this.redoAllowed,
this.extraButton,
});
@ -169,6 +174,8 @@ class _EditorScaffoldState extends State<EditorScaffold> {
metaDataEditable: note != null ? note.canHaveMetadata : false,
onUndoSelected: widget.onUndoSelected,
onRedoSelected: widget.onRedoSelected,
undoAllowed: widget.undoAllowed,
redoAllowed: widget.redoAllowed,
),
)
],

View File

@ -0,0 +1,57 @@
import 'dart:math';
import 'package:gitjournal/editors/common.dart';
// FIXME: Possibly conserve memory by only storing the difference between the
// texts? Or by saving it to disk?
// FIXME: This should probably also have what field was changed?
// How does this work for the title and the checklist editor?
// FIXME: Instead of storing if each note has been modified that should be
// be just taken from this class
//
class UndoRedoStack {
var _versions = <TextEditorState>[];
int _index = -1;
/// Returns if UI should be redrawn
bool textChanged(TextEditorState es) {
var _redo = redoPossible;
var _undo = undoPossible;
if (_redo) {
var i = max(0, _index);
_versions.removeRange(i, _versions.length - 1);
}
if (_versions.isEmpty) {
_versions.add(es);
_index = _versions.length - 1;
return true;
}
var last = _versions.last;
if (last.text == es.text) {
return false;
}
_versions.add(es);
_index = _versions.length - 1;
return redoPossible != _redo || undoPossible != _undo;
}
TextEditorState undo() {
var i = _index;
_index--;
return _versions[i];
}
TextEditorState redo() {
_index++;
return _versions[_index];
}
bool get undoPossible => _index >= 0;
bool get redoPossible => _index < _versions.length - 1;
bool get modified => _versions.isNotEmpty;
}
// FIXME: Only save it every x seconds?