Design Proposal: Undo/Redo feature #5057
Replies: 1 comment 2 replies
-
Hi Shuktika, thanks so much for the detailed write up!
I don't think there's a benefit to splitting up the slices into their own arrays, in our set / get actions we can just use the slices we need to and ignore the ones we don't, but every action will use the same slices anyways. For your first history question, yes we should be able to access the root state but we will have to do so from a thunk. Setting the state data back in place might be a little more involved though. I'm not sure if we can/should modify the state directly in the thunk, we might need to define some Let me know if any of that is confusing or if you want to discuss it further. |
Beta Was this translation helpful? Give feedback.
-
Problem statement
When editing a flow in designer, users have to manually revert their changes. This is a design proposal for undo/redo feature implementation in LAUX and Power Automate. Users will have undo/redo buttons in the command bar for reverting significant changes in their flow that we would save states for (eg. adding/removing action, adding text in parameter etc.).
Implementation details
Overview
We will store an array of past and future redux states that will track undo/redo states in the redux store. These state saves will be dispatched/triggered once certain actions are performed by user (i.e. on certain redux actions/reducers). Undo/redo buttons will be disabled based on past/future arrays being empty.
Functions available outside LAUX for host services to use:
Option 1: Use redux-undo library
Undoable is an enhancer from redux-undo library that can be applied to the root reducer or specific slices. The enhancer changes the state to the format of past, present and future to track undo/redo states. For example, if applied to the RootState, the state would change to:
We could choose to apply the enhancer to certain slices instead of the root state. They would also put the individual slice states in the format mentioned above. Unsure how this would work with undo/redo button clicks and if it could put the slices out of sync (will have to try a proof of concept for this).
Pros:
Cons:
Documentation:
Option 2: Implement undo/redo in LAUX from scratch
Storing undo/redo state history
We have two options for storing history for undo/redo:
For example, if we want to store only workflow state and operations state for undo history, state would change to something like:
Triggering state save for undo history
We want to save state for undo history when the flow has changed from a user action, i.e. when the flow can potentially become dirty if it wasn't already and has changed from previous undo history save. We can add a debounce rate to not save state too often if the actions can occur too close together. Workflow slice already has a matcher that attempts to set the flow as dirty on certain actions. We can probably use the same list of actions to trigger undo history state save on:
LogicAppsUX/libs/designer/src/lib/core/state/workflow/workflowSlice.ts
Line 482 in ea8387a
We have three options to trigger a state save for undo history (i.e. update the past and future arrays):
extraReducers: builder.addMatcher
to trigger a undo history state save on matching only certain actions (eg. add node, delete node etc.). We would usually want to save state before the action is performed, so I'm unsure if this will work since the matcher will be called after action we match on is performed so we might not have the previous/beginning state from before action is performed when the matcher is called. However, if it can work, this would be an easy way of keeping all the actions we care about for undo history in one place and easy to update in future. This will also be similar to builder.addMatcher in workflow slice for updating isDirty variable.saveUndoState
usingcreateAction
and useextraReducers: builder.addCase
to trigger state save on actions. This will be similar toresetWorkflowState
action. ThesaveUndoState
action would be dispatched at the beginning of the actions we want to trigger undo history save on (eg. add node, delete node etc.). I'm unsure how we can dispatch or trigger a call tosaveUndoState
for actions specified in slices like addNode.saveUndoState
usingcreateAction
and dispatch it when we deep compare changes in workflow in DataProviderInner. This is not as nice as the previous two options but if the open questions for those cannot be figured out, we might have to go with this option. Also, unsure if this will cover all cases for user changes.Preferred Solution
Option 2 of implementing undo/redo in LAUX from scratch is preferred since it gives us more flexibility with implementation and is less likely to break existing code. It also doesn't introduce a breaking change that would impact accessing the state in LAUX and by host services.
Which options to use within option 2 is a bit of an open question that I'm seeking help with:
Potential Optimizations
Open questions
The biggest open question is which approaches are preferred for implementation.
For option 2 (implementing from scratch), which ones are feasible? Re-listing open questions from option 2 in one place here:
Storing undo/redo state history:
Option 1. Is it possible to save part of root state in undo slice? Can we access root state in a slice under the root state?
Triggering state save for undo history:
Option 1: Is there a way we can perform the state save specified in matcher first, then the action like addNode?
Option 2: Is there a way we can trigger state save by dispatching it from within slice actions like addNode?
Option 3: Will it cover all user edits?
Beta Was this translation helpful? Give feedback.
All reactions