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

Feature: undo/redo for app builder #1446

Merged
merged 29 commits into from
Dec 10, 2021
Merged

Feature: undo/redo for app builder #1446

merged 29 commits into from
Dec 10, 2021

Conversation

gondar00
Copy link
Contributor

@gondar00 gondar00 commented Nov 23, 2021

This PR adds a shortcut i.e ⌘Z for undo-ing a change made to a component
This PR adds a shortcut i.e ⌘+shift+Z for redo-ing a change made to a component

Define change made to a component?

Change can be user activities such as resizing, deleting, moving the element on the canvas, add-edit component to canvas.

Screen.Recording.2021-11-23.at.5.05.44.PM.mov

Implementing Undo-Redo Functionality in Editor.jsx using Immer

What is Immer patches?

To know more about immer please visit their website.
Immer brings in a feature called Patches. That allows us to implement many cool features – one of them being Undo-Redo.

Immer records all the changes that are performed on the draft object and generates an array of JSON objects indicating what has changed. These arrays are called Patches. Immer also produces inverse patches, which are patches that need to be applied if you want to go back to the previous state.

Implementation

appDefinition is the place where all the components including their layout changes are stored. Whenever a change is made to appDefinition: appDefinitionChanged is the callback function invoked every-time any changes to appDefinition are made.

In this callback function have added: immer function i.e

    produce(
      this.state.appDefinition,
      (draft) => {
        draft.components = newDefinition.components;
      },
      this.handleAddPatch
    );

Next, handleAddPatch function handles 5 things:

  1. Updating the currentVersion
  2. Add the redo and undo keys to the currentVersionChanges object
  3. Sets canUndo & canRedo properties
  4. When a new patch is added it deletes the redo version i.e. delete currentVersion + 1
  5. Maintains the max number of version as a lot of versions can effect the browser perf. currently set to 100. i.e if the currentVersion is 100 then it starts deleting the 0th version, 101 then it deletes the 1st version and so on, such that at any given point in time the maximum number of component versions stored inside currentVersionChanges are 100 to preserve browser performance due to the deeply nested nature of appDefinition the currentVersionChanges can be a very large object.
  handleAddPatch = (patches, inversePatches) => {
    if (isEmpty(patches) && isEmpty(inversePatches)) return;
    this.currentVersion++;
    this.currentVersionChanges[this.currentVersion] = {
      redo: patches,
      undo: inversePatches,
    };

    this.canUndo = this.currentVersionChanges.hasOwnProperty(this.currentVersion - 1);
    this.canRedo = this.currentVersionChanges.hasOwnProperty(this.currentVersion + 1);

    delete this.currentVersionChanges[this.currentVersion + 1];
    delete this.currentVersionChanges[this.currentVersion - this.noOfVersionsSupported];
  }

Things to keep in mind

  1. When we receive an undoable action – we can always undo but cannot redo anymore.
  2. Whenever you perform an undo – you can always redo and keep doing undo as long as we have a patch for it.
  3. Whenever you redo – you can always undo and keep doing redo as long as we have a patch for it.

@gondar00 gondar00 changed the title feat: add restore shortcut when component is deleted feat: add restore shortcut when appDefinition components are changed Nov 24, 2021
@Navaneeth-pk
Copy link
Collaborator

Please fix the merge conflicts.
@gondar00

@gondar00
Copy link
Contributor Author

gondar00 commented Dec 4, 2021

@Navaneeth-pk fixed merge conflict 👍

Copy link
Contributor

@sherfin94 sherfin94 left a comment

Choose a reason for hiding this comment

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

Adding an event handler, and undoing a couple of times seems to crash the app:

undo-after-event-handler

@sherfin94
Copy link
Contributor

Adding an event handler, and undoing a couple of times seems to crash the app:

undo-after-event-handler

Actually, now I think the crash happens whenever the inspector panel is open.

  1. Add any component
  2. Open its inspector panel
  3. Undo several times

@gondar00

@gondar00
Copy link
Contributor Author

gondar00 commented Dec 8, 2021

Thanks @sherfin94

I was able to reproduce this bug, its related to inspector panel

Screen.Recording.2021-12-08.at.4.53.41.PM.mov
  1. click on widget
  2. open inspector
  3. undo, undo, undo
  4. app crashes

It's related to inspector panel click event when it opens, I am looking into it, will post the fix soon

@gondar00
Copy link
Contributor Author

gondar00 commented Dec 9, 2021

@sherfin94 added the fix

@gondar00 gondar00 requested a review from sherfin94 December 9, 2021 06:47
@akshaysasidrn
Copy link
Collaborator

Apologies for late question, do we also have to add some buttons as well for undo/redo?

// 2. Whenever you perform an undo – you can always redo and keep doing undo as long as we have a patch for it.
// 3. Whenever you redo – you can always undo and keep doing redo as long as we have a patch for it.
initComponentVersioning = () => {
this.currentVersion = -1;
Copy link
Collaborator

Choose a reason for hiding this comment

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

nitpick: will this be confusing later b/w app's current version and immer patch's current version? ́😅

Copy link
Contributor Author

@gondar00 gondar00 Dec 10, 2021

Choose a reason for hiding this comment

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

Actually app version can be version 1, but the immer patch's current version can be anywhere between a range of 1-100,

Copy link
Collaborator

Choose a reason for hiding this comment

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

I meant we store currentVersionId for apps at db, was speaking in terms of that.

@sherfin94
Copy link
Contributor

@sherfin94 added the fix

Hey @gondar00, looks like the crash still happens in certain scenarios:

  1. Add a button first
  2. Then add a table
  3. Click on table handle (so that inspector opens)
  4. Undo twice

undo-issue

@gondar00
Copy link
Contributor Author

@sherfin94 added the fix

Hey @gondar00, looks like the crash still happens in certain scenarios:

  1. Add a button first
  2. Then add a table
  3. Click on table handle (so that inspector opens)
  4. Undo twice

undo-issue

This I've added a check it seems to work fine now, thanks ^^

Copy link
Contributor

@sherfin94 sherfin94 left a comment

Choose a reason for hiding this comment

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

Looks like a great PR @gondar00 👏🏼 👏🏼

One small thing, unrelated to the main purpose of this PR that we'll have to fix is, the new type of toasts doesn't show different colors when different types of alert is selected (success, failure etc).
toast

@akshaysasidrn
Copy link
Collaborator

Great work @gondar00 🙌🏽

@Navaneeth-pk Navaneeth-pk merged commit 6c90a16 into develop Dec 10, 2021
@Navaneeth-pk Navaneeth-pk deleted the restore-component branch December 10, 2021 23:11
@Navaneeth-pk Navaneeth-pk changed the title feat: add restore shortcut when appDefinition components are changed Feature: undo/redo for app builder Dec 10, 2021
shah21 pushed a commit that referenced this pull request Dec 28, 2021
* feat: add restore shortcut when component is deleted

* change toast messages to hot toast from toastify

* change toast messages to hot toast from toastify

* change toast messages to hot toast from toastify

* on key press match clear the pressed keys

* add react hotkeys hook and delete use-shortcuts custom hook

* change toast messages to hot toast from toastify

* add immer lib

* applyPatches from immer + add undo redo on appdefination changes

* remove notification on undo

* add can-undo + can-redo checks

* add missing can-redo to handlePatchAdd

* add component versioning on componentDefinitionChanged

* set default value of loading state to interpolated boolean false for table

* set canUndo on initial load to false

* fix last element not getting removed on undo

* Remove console log

* add migration to change loadingState for existing tables

* set loadingstate value based on the previous value

* fix: app crash on inspector opening

* add check for selectedComponentId inside components def

* update template definitions for loadingState

* fix alert for success, error, info for button notifications
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants