GrapesJS / grapesjs

Free and Open source Web Builder Framework. Next generation tool for building templates without coding
https://grapesjs.com
Other
22.62k stars 4.09k forks source link

[Feature]: Improve UndoManager API #3639

Open anatoli-dp opened 3 years ago

anatoli-dp commented 3 years ago

One thing i think would add great functionality is a more detailed history stack. U can get the undo stack and build a sort of history report from it of all the edits one has made but it is a little unclear as to what each edit was (at least to me so if I am missing something please let me know). Maybe like a formulaic description that is like component [component name] added/deleted/edited at [location on page or component added to], or component [component] text edited, . . . (please excuse the general description as even i have no clue how i would phrase it) just some quick description that can give a brief overview of what that particular undo/redo history was for each item on the stack. I am aware of only the id of each history item atm, so if this is currently possible I am just unaware of how i should go about extracting this information to create a history sidebar outside of just the id so please give me a clue as what to look for

Is there an alternative at the latest version?

Is this related to an issue?

artf commented 3 years ago

Hi @anatoli-dp the ability to view the undo history would be actually super cool and I was already thinking about how to extend the UndoManager module in order to allow this kind of functionality via a plugin (eg. you can render the UI with your framework of choice). The UndoManager stack is updated, with a new UndoAction object (containing infos about the action), on any change of listened models/collections (components, css rules, etc.). One thing worth noting is that one change can generate multiple UndoActions:

// This will generate one UndoAction for a new component 
// and another one for a new CSS Rule
editor.addComponents(`
  <div class="test">Hi</div>
  <style>.test{color: red}</style>
`);

Here is an example of how you could retrieve an array of grouped UndoActions (the private method I made during the last refactoring).

One thing to understand is how to identify each group of UndoActions with the proper description/label (as you have described).

anatoli-dp commented 3 years ago

yeah im aware of how to access the stack i just dont know how to intepret it to give a more meaningful description to the end user

artf commented 3 years ago

i just dont know how to intepret it to give a more meaningful description to the end user

Each UndoAction (each instance of the stack) tells you the action type (eg. add, remove, reset, change) and details about the updated model (eg. Component, CssRule, etc.). You can try to read those data but I was thinking more about how to make each operation easily recognizable.

Agobin commented 3 years ago

Just an idea.

If there was a way to add a data-label attribute to trait controls when they are defined to describe the action they perform. And when this trait element performs an action, the label can be used to describe that change.

Before the trait element applies the change, we get the location of the undomanager's pointer (that is the index of the model it is currently pointing to). After the trait element applies the change, we update the magic fusion index attribute of all the models created when that change was performed to have the same magic fusion index (the magic fusion of the last model could be used).

Now we can store a list of changes that has the description of the change and the magic fusion index of the models that resulted from the change. With this list, we can display a history of changes and easily navigate the stack to switch between states.

To switch between states, since we know the magic fusion index of the state, depending on the location of the stack pointer, we could perform a series of undo(true) or redo(true) to move the stack pointer until we reach a model whose magic fusion index is the magic fusion index of the target state.

Special cases: For editor functions like addComponents, setStyle we could add an option for the user to add a description and use the same concept as mentioned above.

For RTE edits, we could use the rte:enable and rte:disable events to track the models created during the edit, since the description is text change, we could use the type of component to complete the description.

My two cents, maybe it could be worth exploring.

artf commented 3 years ago

Thanks @Agobin I've been actually thinking the same, adding an extra option for each meaningful top APIs, eg:

editor.addComponents(`
  <div class="test">Hi</div>
  <style>.test{color: red}</style>
`, { undoLabel: 'add-components' });

then a new API method (eg. getGroupedStack) on UndoManager which returns a similar array of UndoActionGroup:

{
    [
        index: 1, // Magix index to use to jump on this state
        labels: ['add-components'], // probably we might have multiple labels
        actions: [{}, {}, ...], // all grouped UndoActions which contain more details for each change
    ],
    // ...
    [
        index: 5,
        labels: ['custom-label-1', 'custom-label-2'],
        actions: [{}, {}],
    ],
}

And probably an easier way to select the current undo index undoManager.setIndex(UndoActionGroup.index) to switch the state.

I'd really appreciate a PR if anybody wants to try.

artf commented 3 years ago

Other features to add in UndoManager:

mihir-khandekar commented 2 years ago

Hi @artf, is there any update on these changes? https://github.com/artf/grapesjs/issues/3639#issuecomment-955197769

vizardkill commented 1 year ago

up

jogibear9988 commented 7 months ago

I created this in my Designer via a API to open a UNDO group: https://github.com/node-projects/web-component-designer/blob/656ef532088731eb4fa7998b612ca3f273d27f9f/packages/web-component-designer/src/elements/services/undoService/IUndoService.ts#L6 This group you hand over a description, and all undo items are then put into this group (also nested groups are possible) until I call commit on the group.