mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-09-17 00:42:29 +08:00
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:
@ -24,6 +24,9 @@ class EditorBottomBar extends StatelessWidget {
|
|||||||
final Func0<void> onZenModeChanged;
|
final Func0<void> onZenModeChanged;
|
||||||
final bool metaDataEditable;
|
final bool metaDataEditable;
|
||||||
|
|
||||||
|
final bool undoAllowed;
|
||||||
|
final bool redoAllowed;
|
||||||
|
|
||||||
final Func0<void> onUndoSelected;
|
final Func0<void> onUndoSelected;
|
||||||
final Func0<void> onRedoSelected;
|
final Func0<void> onRedoSelected;
|
||||||
|
|
||||||
@ -37,6 +40,8 @@ class EditorBottomBar extends StatelessWidget {
|
|||||||
@required this.metaDataEditable,
|
@required this.metaDataEditable,
|
||||||
@required this.onUndoSelected,
|
@required this.onUndoSelected,
|
||||||
@required this.onRedoSelected,
|
@required this.onRedoSelected,
|
||||||
|
@required this.undoAllowed,
|
||||||
|
@required this.redoAllowed,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -84,17 +89,24 @@ class EditorBottomBar extends StatelessWidget {
|
|||||||
maintainState: true,
|
maintainState: true,
|
||||||
maintainInteractivity: false,
|
maintainInteractivity: false,
|
||||||
),
|
),
|
||||||
// TODO: Add the undo and redo icons
|
const Spacer(),
|
||||||
Expanded(
|
IconButton(
|
||||||
child: FlatButton.icon(
|
icon: const Icon(Icons.undo),
|
||||||
icon: const Icon(Icons.folder),
|
onPressed: undoAllowed ? onUndoSelected : null,
|
||||||
label: Text(parentFolder.publicName),
|
|
||||||
onPressed: () {
|
|
||||||
var note = editorState.getNote();
|
|
||||||
editor.moveNoteToFolderSelected(note);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
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,
|
menuIcon,
|
||||||
],
|
],
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
@ -185,6 +185,8 @@ class ChecklistEditorState extends State<ChecklistEditor>
|
|||||||
),
|
),
|
||||||
onUndoSelected: _undo,
|
onUndoSelected: _undo,
|
||||||
onRedoSelected: _redo,
|
onRedoSelected: _redo,
|
||||||
|
undoAllowed: false,
|
||||||
|
redoAllowed: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +108,8 @@ class JournalEditorState extends State<JournalEditor>
|
|||||||
body: editor,
|
body: editor,
|
||||||
onUndoSelected: _undo,
|
onUndoSelected: _undo,
|
||||||
onRedoSelected: _redo,
|
onRedoSelected: _redo,
|
||||||
|
undoAllowed: false,
|
||||||
|
redoAllowed: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,6 +142,8 @@ class MarkdownEditorState extends State<MarkdownEditor>
|
|||||||
body: editor,
|
body: editor,
|
||||||
onUndoSelected: _undo,
|
onUndoSelected: _undo,
|
||||||
onRedoSelected: _redo,
|
onRedoSelected: _redo,
|
||||||
|
undoAllowed: false,
|
||||||
|
redoAllowed: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import 'package:gitjournal/core/md_yaml_doc_codec.dart';
|
|||||||
import 'package:gitjournal/core/note.dart';
|
import 'package:gitjournal/core/note.dart';
|
||||||
import 'package:gitjournal/editors/common.dart';
|
import 'package:gitjournal/editors/common.dart';
|
||||||
import 'package:gitjournal/editors/disposable_change_notifier.dart';
|
import 'package:gitjournal/editors/disposable_change_notifier.dart';
|
||||||
|
import 'package:gitjournal/editors/undo_redo.dart';
|
||||||
import 'package:gitjournal/widgets/editor_scroll_view.dart';
|
import 'package:gitjournal/widgets/editor_scroll_view.dart';
|
||||||
|
|
||||||
class RawEditor extends StatefulWidget implements Editor {
|
class RawEditor extends StatefulWidget implements Editor {
|
||||||
@ -56,12 +57,14 @@ class RawEditorState extends State<RawEditor>
|
|||||||
implements EditorState {
|
implements EditorState {
|
||||||
Note note;
|
Note note;
|
||||||
bool _noteModified;
|
bool _noteModified;
|
||||||
TextEditingController _textController = TextEditingController();
|
TextEditingController _textController;
|
||||||
|
UndoRedoStack _undoRedoStack;
|
||||||
|
|
||||||
final serializer = MarkdownYAMLCodec();
|
final serializer = MarkdownYAMLCodec();
|
||||||
|
|
||||||
RawEditorState(this.note) {
|
RawEditorState(this.note) {
|
||||||
_textController = TextEditingController(text: serializer.encode(note.data));
|
_textController = TextEditingController(text: serializer.encode(note.data));
|
||||||
|
_undoRedoStack = UndoRedoStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -106,6 +109,8 @@ class RawEditorState extends State<RawEditor>
|
|||||||
body: editor,
|
body: editor,
|
||||||
onUndoSelected: _undo,
|
onUndoSelected: _undo,
|
||||||
onRedoSelected: _redo,
|
onRedoSelected: _redo,
|
||||||
|
undoAllowed: _undoRedoStack.undoPossible,
|
||||||
|
redoAllowed: _undoRedoStack.redoPossible,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,6 +123,12 @@ class RawEditorState extends State<RawEditor>
|
|||||||
void _noteTextChanged() {
|
void _noteTextChanged() {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
|
var editState = TextEditorState.fromValue(_textController.value);
|
||||||
|
var redraw = _undoRedoStack.textChanged(editState);
|
||||||
|
if (redraw) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
if (_noteModified) return;
|
if (_noteModified) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_noteModified = true;
|
_noteModified = true;
|
||||||
@ -136,9 +147,21 @@ class RawEditorState extends State<RawEditor>
|
|||||||
@override
|
@override
|
||||||
bool get noteModified => _noteModified;
|
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 {
|
class _NoteEditor extends StatelessWidget {
|
||||||
|
@ -24,6 +24,9 @@ class EditorScaffold extends StatefulWidget {
|
|||||||
final Func0<void> onUndoSelected;
|
final Func0<void> onUndoSelected;
|
||||||
final Func0<void> onRedoSelected;
|
final Func0<void> onRedoSelected;
|
||||||
|
|
||||||
|
final bool undoAllowed;
|
||||||
|
final bool redoAllowed;
|
||||||
|
|
||||||
EditorScaffold({
|
EditorScaffold({
|
||||||
@required this.editor,
|
@required this.editor,
|
||||||
@required this.editorState,
|
@required this.editorState,
|
||||||
@ -33,6 +36,8 @@ class EditorScaffold extends StatefulWidget {
|
|||||||
@required this.parentFolder,
|
@required this.parentFolder,
|
||||||
@required this.onUndoSelected,
|
@required this.onUndoSelected,
|
||||||
@required this.onRedoSelected,
|
@required this.onRedoSelected,
|
||||||
|
@required this.undoAllowed,
|
||||||
|
@required this.redoAllowed,
|
||||||
this.extraButton,
|
this.extraButton,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -169,6 +174,8 @@ class _EditorScaffoldState extends State<EditorScaffold> {
|
|||||||
metaDataEditable: note != null ? note.canHaveMetadata : false,
|
metaDataEditable: note != null ? note.canHaveMetadata : false,
|
||||||
onUndoSelected: widget.onUndoSelected,
|
onUndoSelected: widget.onUndoSelected,
|
||||||
onRedoSelected: widget.onRedoSelected,
|
onRedoSelected: widget.onRedoSelected,
|
||||||
|
undoAllowed: widget.undoAllowed,
|
||||||
|
redoAllowed: widget.redoAllowed,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
57
lib/editors/undo_redo.dart
Normal file
57
lib/editors/undo_redo.dart
Normal 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?
|
Reference in New Issue
Block a user