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

Single-update undo/redo #1374

Merged
merged 25 commits into from
Dec 20, 2022
Merged

Single-update undo/redo #1374

merged 25 commits into from
Dec 20, 2022

Conversation

apedroferreira
Copy link
Member

@apedroferreira apedroferreira commented Nov 22, 2022

Closes #845
Closes #1192
Preview URL: https://toolpad-pr-1374.onrender.com/

Update the DOM only a single time for each drag & drop operation/resizing/delete, so that undo/redo works without throttling.

@apedroferreira apedroferreira self-assigned this Nov 22, 2022
@apedroferreira apedroferreira added core Infrastructure work going on behind the scenes enhancement This is not a bug, nor a new feature labels Nov 22, 2022
@oliviertassinari oliviertassinari temporarily deployed to atomic-undo-redo - toolpad PR #1374 November 24, 2022 15:42 — with Render Destroyed
@apedroferreira apedroferreira marked this pull request as ready for review November 24, 2022 15:47
@oliviertassinari oliviertassinari temporarily deployed to atomic-undo-redo - toolpad PR #1374 November 24, 2022 19:41 — with Render Destroyed
@Janpot Janpot mentioned this pull request Nov 28, 2022
10 tasks
@oliviertassinari oliviertassinari requested a deployment to atomic-undo-redo - toolpad-db PR #1374 November 28, 2022 18:46 — with Render Abandoned
@apedroferreira
Copy link
Member Author

apedroferreira commented Nov 29, 2022

@Janpot I've removed some methods from domApi in the last commit, the ones that seemed most likely to be used in updates with more than one change. Let me know if you think I should remove more or less methods.

Copy link
Contributor

@bytasv bytasv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If dom changes are done in atomic update why do we still need throttle? Wouldn't the goal of this PR be to get rid of throttle entirely?
You mentioned in other PR that throttle might still be needed when undoing changes in a text input, though our undo/redo implementation should not be even triggered inside text inputs, so I'm wondering if it's related to that?

@Janpot
Copy link
Member

Janpot commented Nov 30, 2022

The undo would happen natively in the text input, but the toolpad undo stack won't reflect this, it will just add another stack entry on top instead of popping one off. a subsequent redo will behave wrong.

example (this is already current behavior):

  1. add a button
  2. focus the content prop and add text at the end " foo", it should now read "Button Text foo"
  3. Cmd+Z, it should now read "Button Text" again
  4. remove focus from the input
  5. Cmd+Z, it should now remove the button, but instead it adds back " foo", you'll have to Cmd+Z twice more to get the button removed.

We'll need to fix this and ☝️ can be an integration test

The problem will compound the more text you write and undo/redo in the text input.

As soon as we remove the throttling, every update to the textinput will get its own stack entry. So one entry for every letter typed, and then one entry for every time undo or redo is called within the textinput.

@bytasv
Copy link
Contributor

bytasv commented Nov 30, 2022

Thanks for explanation Jan (I missed your reply in another PR), now its clear to me. I agree we need to fix that and cover with test

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Nov 30, 2022
@apedroferreira
Copy link
Member Author

apedroferreira commented Dec 14, 2022

Can I get a final review here in order to merge, please? All suggestions should have been addressed already.
Changing views is ready in #1417, but I can't get a preview live until I merge this part.

Edit: Ah nevermind this still needs the text input changes or it's a bit broken - feel free to pre-approve this PR anyway so I can do that in a separate PR

@@ -42,42 +41,3 @@ test('test basic undo and redo', async ({ page, browserName, api }) => {
// Redo should bring back text field
await expect(canvasInputLocator).toHaveCount(3);
});

test('test batching quick actions into single undo entry', async ({ page, browserName, api }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This spec should still be relevant, specifically for text field changes

Copy link
Member Author

@apedroferreira apedroferreira Dec 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can have something more specific once we have specific logic for text inputs?
Anyway I won't merge this until I add a test that does the same that this one did.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test should work fine after all, I had the impression that it was testing more general changes but I was wrong!

Copy link
Contributor

@bytasv bytasv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from from spec comment all seems to be working great 👍

@oliviertassinari oliviertassinari temporarily deployed to atomic-undo-redo - toolpad PR #1374 December 15, 2022 17:24 — with Render Destroyed
@apedroferreira
Copy link
Member Author

apedroferreira commented Dec 15, 2022

I've merged text input undo/redo into this PR so that it works as a whole and can be merged (#1459).
In that PR I'm just using debouncing for text input changes + disabling the native browser undo/redo in text inputs (in the page view only - native undo/redo won't be blocked in other views).

Also re-added the batching test as looks like it's perfect for testing the text inputs after all!

Preview URL: https://toolpad-pr-1374.onrender.com/

@apedroferreira
Copy link
Member Author

I'm gonna merge this PR later today if it's ok!
Feel free to check these changes (just 4 files) #1459, and the preview at https://toolpad-pr-1374.onrender.com/ (includes atomic updates + text input undo/redo) in case there's any more comments.
I shouldn't have requested for reviews yesterday until having added these extra changes, so sorry for that.

},
[connectionNode, domApi],
[connectionNode, dom, domApi],
Copy link
Member

@Janpot Janpot Dec 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙂 I think this is why I suggested to use a function in domApi.update. This callback invalidates on every dom change, and it's also not 100% sure it will get the last dom. e.g. imagine a function:

const addConnection = React.useCallback(() => domApi.update(appDom.addConnection(dom)), [dom]);

// somewhere else, this function called twice
addConnection();
addConnection();

Those would get the same initial version of dom, meaning only one connection is added. While I would expect to have two connections. It would be different if

const addConnection = React.useCallback(
  () => domApi.update((draft) => appDom.addConnection(draft)),
  [],
);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it work to restore and use methods such as domApi.setNodeNamespacedProp (which I had deleted) in cases like this?
And use domApi.update as I'm using it but only when a temporary DOM is needed? domApi.update replaces the whole DOM anyway so in those cases maybe it's fine?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still thinking of better alternatives, your proposal does seem more "atomic", it's just more difficult to use in RenderOverlay.

Copy link
Member Author

@apedroferreira apedroferreira Dec 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try supporting domApi.update((draft) => ... and use it as much as possible where I can, except in the few cases where it's hard to use, I think that should be possible.

Copy link
Member

@Janpot Janpot Dec 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still thinking of better alternatives, your proposal does seem more "atomic", it's just more difficult to use in RenderOverlay.

We can support both methods, like

update: (draft: React.SetStateAction<AppDom>) => void

Also, wouldn't necessarily recommend it, but nothing prevents you from doing:

const addConnection = React.useCallback(
  () => domApi.update(() => appDom.addConnection(dom)),
  [dom],
);

Copy link
Member Author

@apedroferreira apedroferreira Dec 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I was able to use an updater function everywhere after all!
Commit: 1f7779e
After the refactoring I had made in RenderOverlay I guess it wasn't so difficult there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this PR is using the updater function now I will merge this part already!

@oliviertassinari oliviertassinari temporarily deployed to atomic-undo-redo - toolpad PR #1374 December 16, 2022 12:48 — with Render Destroyed
@oliviertassinari oliviertassinari temporarily deployed to atomic-undo-redo - toolpad PR #1374 December 16, 2022 12:53 — with Render Destroyed
@oliviertassinari oliviertassinari temporarily deployed to atomic-undo-redo - toolpad PR #1374 December 16, 2022 12:54 — with Render Destroyed
@apedroferreira apedroferreira merged commit 1bab118 into master Dec 20, 2022
@apedroferreira apedroferreira deleted the atomic-undo-redo branch December 20, 2022 09:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Infrastructure work going on behind the scenes enhancement This is not a bug, nor a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Reduce every DOM operation to the minimum possible DOM updates
5 participants