Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Undo/Redo (with snapshots + one-way commands) #1900

Merged
merged 24 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8c89c7d
WIP: Undo/redo using snapshots with one-way commands on top.
matthew-carroll Mar 19, 2024
d5f7dda
Added ability to start/end Editor transactions across multiple calls …
matthew-carroll Mar 20, 2024
eba2f32
WIP: Editor transactions
matthew-carroll Mar 22, 2024
0be8d8d
Merge branch 'main' into undo-redo_snapshot-with-commands
matthew-carroll Apr 15, 2024
b690e56
WIP: Debugging reactions
matthew-carroll Apr 15, 2024
1fed142
WIP: Trying to get HR conversion to work again after altering when tr…
matthew-carroll Apr 22, 2024
858399d
Fixed dash_conversion_test by loosening the guard conditions in the d…
matthew-carroll May 14, 2024
6217fe8
WIP: Getting tests to pass again - mostly dealing with how adding com…
matthew-carroll May 16, 2024
f4b2245
Got link reaction tests passing again
matthew-carroll May 26, 2024
87f8c1b
Got paragraph_conversions_test.dart to pass again
matthew-carroll May 26, 2024
9c14a8d
All tests pass - next we need reactions to be able to put their chang…
matthew-carroll May 26, 2024
50deab3
Changed EditReactions to be two-phase: modifyContent and react. Also …
matthew-carroll May 26, 2024
7686e7e
Merge branch 'main' into undo-redo_snapshot-with-commands
matthew-carroll May 26, 2024
a1f70f5
Fix merge test failure
matthew-carroll May 26, 2024
765ef51
Added a number of new test cases for undo/redo. Removed a bunch of pr…
matthew-carroll May 27, 2024
1eddf53
Added a basic concept of transaction merging so that undo doesn't app…
matthew-carroll May 27, 2024
7043540
WIP: Updating Super Editor demo to show transaction history visualiza…
matthew-carroll May 28, 2024
cdb7f52
Merge branch 'main' into undo-redo_snapshot-with-commands
matthew-carroll May 30, 2024
094cdb2
Fixed merge issue
matthew-carroll May 30, 2024
cb5d714
Merge transactions that only contain selection and composing region c…
matthew-carroll May 31, 2024
734242f
Merge branch 'main' into undo-redo_snapshot-with-commands
matthew-carroll Jul 8, 2024
9e448bf
Removed all print statements, fixed a couple failing tests in Markdow…
matthew-carroll Jul 8, 2024
e6c5177
PR cleanup
matthew-carroll Jul 8, 2024
17a4154
Merge branch 'main' into undo-redo_snapshot-with-commands
matthew-carroll Jul 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions super_editor/clones/quill/lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ This is a code block.
/// current block. This is especially important for a code block, in which pressing
/// Enter inserts a newline inside the code block - it doesn't insert a new paragraph
/// below the code block.
class _AlwaysTrailingParagraphReaction implements EditReaction {
class _AlwaysTrailingParagraphReaction extends EditReaction {
@override
void react(EditContext editorContext, RequestDispatcher requestDispatcher, List<EditEvent> changeList) {
void modifyContent(EditContext editorContext, RequestDispatcher requestDispatcher, List<EditEvent> changeList) {
final document = editorContext.find<MutableDocument>(Editor.documentKey);
final lastNode = document.nodes.lastOrNull;

Expand Down
6 changes: 3 additions & 3 deletions super_editor/clones/quill/lib/editor/editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class ClearSelectedStylesRequest implements EditRequest {
const ClearSelectedStylesRequest();
}

class ClearSelectedStylesCommand implements EditCommand {
class ClearSelectedStylesCommand extends EditCommand {
const ClearSelectedStylesCommand();

@override
Expand Down Expand Up @@ -181,7 +181,7 @@ class ClearTextAttributionsRequest implements EditRequest {
int get hashCode => documentRange.hashCode;
}

class ClearTextAttributionsCommand implements EditCommand {
class ClearTextAttributionsCommand extends EditCommand {
const ClearTextAttributionsCommand(this.documentRange);

final DocumentRange documentRange;
Expand Down Expand Up @@ -288,7 +288,7 @@ class ToggleInlineFormatRequest implements EditRequest {
int get hashCode => inlineFormat.hashCode;
}

class ToggleInlineFormatCommand implements EditCommand {
class ToggleInlineFormatCommand extends EditCommand {
const ToggleInlineFormatCommand(this.inlineFormat);

final Attribution inlineFormat;
Expand Down
8 changes: 4 additions & 4 deletions super_editor/clones/quill/lib/editor/toolbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ class _FormattingToolbarState extends State<FormattingToolbar> {
]);

// Clear the field and hide the URL bar
_urlController!.clear();
_urlController!.clearTextAndSelection();
_urlFocusNode.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
_linkPopoverController.close();
setState(() {});
Expand Down Expand Up @@ -220,7 +220,7 @@ class _FormattingToolbarState extends State<FormattingToolbar> {
}

// Clear the field and hide the URL bar
_imageController!.clear();
_imageController!.clearTextAndSelection();
_imageFocusNode.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
_imagePopoverController.close();
setState(() {});
Expand Down Expand Up @@ -535,7 +535,7 @@ class _FormattingToolbarState extends State<FormattingToolbar> {
onPressed: () {
setState(() {
_urlFocusNode.unfocus();
_urlController!.clear();
_urlController!.clearTextAndSelection();
});
},
),
Expand Down Expand Up @@ -605,7 +605,7 @@ class _FormattingToolbarState extends State<FormattingToolbar> {
onPressed: () {
setState(() {
_imageFocusNode.unfocus();
_imageController!.clear();
_imageController!.clearTextAndSelection();
});
},
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ class ConvertSelectedTextNodeRequest implements EditRequest {
int get hashCode => newType.hashCode;
}

class ConvertSelectedTextNodeCommand implements EditCommand {
class ConvertSelectedTextNodeCommand extends EditCommand {
ConvertSelectedTextNodeCommand(this.newType);

final TextNodeType newType;
Expand Down
184 changes: 170 additions & 14 deletions super_editor/example/lib/main_super_editor.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:example/demos/example_editor/_example_document.dart';
import 'package:example/demos/example_editor/example_editor.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
Expand Down Expand Up @@ -31,7 +30,7 @@ void main() {
runApp(
MaterialApp(
home: Scaffold(
body: ExampleEditor(),
body: _Demo(),
),
supportedLocales: const [
Locale('en', ''),
Expand All @@ -48,8 +47,164 @@ void main() {
);
}

class _Demo extends StatefulWidget {
const _Demo();

@override
State<_Demo> createState() => _DemoState();
}

class _DemoState extends State<_Demo> {
late MutableDocument _document;
late MutableDocumentComposer _composer;
late Editor _docEditor;

@override
void initState() {
super.initState();
_document = createInitialDocument();
_composer = MutableDocumentComposer();
_docEditor = createDefaultDocumentEditor(document: _document, composer: _composer);
}

@override
void dispose() {
_composer.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: _StandardEditor(
document: _document,
composer: _composer,
editor: _docEditor,
),
),
_buildToolbar(),
],
);
}

Widget _buildToolbar() {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
_EditorHistoryPanel(editor: _docEditor),
Container(
width: 24,
height: double.infinity,
color: const Color(0xFF2F2F2F),
child: Column(),
),
],
);
}
}

class _EditorHistoryPanel extends StatefulWidget {
const _EditorHistoryPanel({
required this.editor,
});

final Editor editor;

@override
State<_EditorHistoryPanel> createState() => _EditorHistoryPanelState();
}

class _EditorHistoryPanelState extends State<_EditorHistoryPanel> {
final _scrollController = ScrollController();
late EditListener _editListener;

@override
void initState() {
super.initState();

_editListener = FunctionalEditListener(_onEditorChange);
widget.editor.addListener(_editListener);
}

@override
void didUpdateWidget(_EditorHistoryPanel oldWidget) {
super.didUpdateWidget(oldWidget);

if (widget.editor != oldWidget.editor) {
oldWidget.editor.removeListener(_editListener);
widget.editor.addListener(_editListener);
}
}

@override
void dispose() {
_scrollController.dispose();
widget.editor.removeListener(_editListener);
super.dispose();
}

void _onEditorChange(changes) {
setState(() {
// Build the latest list of changes.
});

// Always scroll to bottom of transaction list.
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollController.position.jumpTo(_scrollController.position.maxScrollExtent);
});
}

@override
Widget build(BuildContext context) {
return Theme(
data: ThemeData(
brightness: Brightness.dark,
),
child: Container(
width: 300,
height: double.infinity,
color: const Color(0xFF333333),
child: SingleChildScrollView(
controller: _scrollController,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: Column(
children: [
for (final history in widget.editor.history)
ListTile(
title: Text("${history.changes.length} changes"),
titleTextStyle: TextStyle(
fontSize: 16,
),
subtitle: Text("${history.changes.map((event) => event.describe()).join("\n")}"),
subtitleTextStyle: TextStyle(
color: Colors.white.withOpacity(0.5),
fontSize: 10,
height: 1.4,
),
visualDensity: VisualDensity.compact,
),
],
),
),
),
),
);
}
}

class _StandardEditor extends StatefulWidget {
const _StandardEditor();
const _StandardEditor({
required this.document,
required this.composer,
required this.editor,
});

final MutableDocument document;
final MutableDocumentComposer composer;
final Editor editor;

@override
State<_StandardEditor> createState() => _StandardEditorState();
Expand All @@ -58,20 +213,13 @@ class _StandardEditor extends StatefulWidget {
class _StandardEditorState extends State<_StandardEditor> {
final GlobalKey _docLayoutKey = GlobalKey();

late MutableDocument _doc;
late MutableDocumentComposer _composer;
late Editor _docEditor;

late FocusNode _editorFocusNode;

late ScrollController _scrollController;

@override
void initState() {
super.initState();
_doc = createInitialDocument();
_composer = MutableDocumentComposer();
_docEditor = createDefaultDocumentEditor(document: _doc, composer: _composer);
_editorFocusNode = FocusNode();
_scrollController = ScrollController();
}
Expand All @@ -80,19 +228,27 @@ class _StandardEditorState extends State<_StandardEditor> {
void dispose() {
_scrollController.dispose();
_editorFocusNode.dispose();
_composer.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return SuperEditor(
editor: _docEditor,
document: _doc,
composer: _composer,
editor: widget.editor,
document: widget.document,
composer: widget.composer,
focusNode: _editorFocusNode,
scrollController: _scrollController,
documentLayoutKey: _docLayoutKey,
stylesheet: defaultStylesheet.copyWith(
addRulesAfter: [
taskStyles,
],
),
componentBuilders: [
TaskComponentBuilder(widget.editor),
...defaultComponentBuilders,
],
);
}
}
1 change: 0 additions & 1 deletion super_editor/example_docs/lib/toolbar.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'dart:math';

import 'package:example_docs/editor.dart';
import 'package:example_docs/infrastructure/icon_selector.dart';
import 'package:example_docs/infrastructure/color_selector.dart';
import 'package:example_docs/infrastructure/text_item_selector.dart';
Expand Down
Loading
Loading