Working on a new Undo / Redo manager that runs on mobx and JSON patches. Here's how it works! 🧵
First off, here's a CodeSandbox. Looks pretty simple but there's a lot going on. I've included my tests! codesandbox.io/s/mobx-undo-re…
In our system, we're tracking changes to a "document". Each time the document changes, we generate a "snapshot" and compare it with our previous snapshot in order to create a "patch" that describes how to get from the current snapshot back to the previous.
These patches are "JSON patches" (jsonpatch.com). Each patch is made up of a series of operations that will turn one object into another. In our case, our patches describe all of the operations needed to get from our current snapshot back to our previous snapshot.
We store these patches in a "stack". This is a list that has a "pointer" that refers to the current patch. We also keep around a snapshot from our last change (or the initial document, if we haven't changed anything yet).
1️⃣ Let's say we make a change to our document.
When our document changes, we first take a snapshot of the current document...
...and we compare it against our previous snapshot in order to create a new patch. Again, this patch describes all the steps needed to "undo" the new change.
Next, we push the new patch onto the stack and move the pointer up so that it points to our new patch.
Finally, we save the snapshot we made of our current document as our new previous snapshot. (We can discard the old previous snapshot.)
Now we're ready for the next change!
2️⃣ To undo a change, we apply the current patch (e.g. the one that the pointer is pointing to) to the current document. Applying a patch performs the patch's operations and will bring the state back to where it was before the most recent change.
Next, we take a snapshot of the new document...
And compare it with the previous snapshot in order to produce a new patch. This patch is our "redo", it lists the operations needed to go from our current document (after undoing) back to what it was before we pressed "undo".
We *replace* the current patch—the one we just applied—with this new "redo" patch, and move the pointer back to the previous patch.
And finally we set the current snapshot as our previous snapshot.
And again, we're ready for the next change!
3️⃣ If we pressed redo, then we would start by moving out pointer up.
We would then apply the current patch to the current document...
And then create a snapshot of the current document, compare it with the previous document to create a patch...
And then *replace* the current patch with the new one.
Finally, we'd set the current snapshot as the previous snapshot...
And job done!
But we're not done. The story continues! (in the next thread)
• • •
Missing some Tweet in this thread? You can try to
force a refresh
Implemented click detection all the way up to quadruple clicks. 🤯
It sort of works like this: each time the user clicks, we fire three events—we fire onPointerDown when they start clicking, then onPointerUp _and_ onClick when the they stop clicking.
We also keep track of a clicking state.
This starts in "idle" and can be either "idle", "pendingDoubleClick", "pendingTripleClick", "pendingQuadrupleClick", or "overflow".
What do I mean by smarter? Let's say each of our blocks has a position in a grid, [x, y]. (It's actually a 3D grid, so [x, y, z], but we can ignore the z for now)
The positions map to the block's index in z/y/x arrays. This means that a block's position can only be an integer like 1 or 2, but never a float like 1.25 or 2.81.
So a position like [0, 0] is fine but [0.5, 0] is not.
I'm currently experiencing a "where do I put whitespace in my code" crisis.
I've usually placed empty lines between pretty much everything except maybe variable definitions, but this makes different methods hard to spot and sometimes makes methods themselves harder to read?
But saving empty lines for just separating methods / class fields can lead to some very dense code.