Skip to content

State history

Igor Zinken edited this page Dec 1, 2021 · 5 revisions

Recording state changes

Mutations can be registered in state history (Vuex history-module.js) in order to provide undo and redo of operations. This is done by providing an Object to the saveState() action. The signature of this object is:

{
    undo() {
        // ... code that restores a state to the original
    },
    redo() {
        // ... code that returns a restored state to the last version
    }
}

It is good to understand that the undo/redo for an action should be considered separate from the Vue component that is triggering the transaction, the reason being that the component can be unmounted at the moment the history state is changed (and the component is no longer active).

That's why undo/redo handlers should either work on variables in a local scope, or on the Vuex store when mutating store properties. When relying on store state and getters, be sure to cache their values in the local scope to avoid conflicts (for instance in below example we cache instrumentIndex and oscillatorIndex as they are used by the undo/redo methods to update a specific oscillator for a specific instrument. As these indices can change during the application lifetime before the undo/redo handler fires, this could lead to the wrong instrument being updated.

update( propertyName, newValue ) {
    // cache the existing values of the property value we are about to mutate...
    const originalValue = this.getterForExistingValue;
    // ...and the component properties used to identify the instrument and oscillator
    const { instrumentIndex, oscillatorIndex } = this;
    const store = this.$store;
    // define the method that will mutate the existing value to given newValue
    const commit = () => store.commit( "updateOscillator", { instrumentIndex, oscillatorIndex, prop, value: newValue });
    // and perform the mutation directly
    commit();
    // now define and enqueue undo/redo handlers to reverse and redo the commit mutation
    enqueueState( propertyName, {
        undo() {
            store.commit( "updateOscillator", { instrumentIndex, oscillatorIndex, prop, value: originalValue });
        },
        redo() {
            commit();
        },
    });
}

Debouncing changes

In order to prevent storing a lot of changes of the same property (for instance when dragging a slider), the storage of a new state can be deferred through a queue. This is done using the enqueueState() of the history-state-factory.js. This is why the history state in the example above was enqueued by propertyName:

When enqueuing a new state while there is an existing one enqueued for the same property name, the first state is updated so its redo will match that of the newest state, the undo remaining unchanged. The second state will not be added to the queue.

By default the queue will be processed every second. So in the scenario of a slider being dragged over the course of a second : only the value at the beginning (on drag start) is stored for the undo state and the last value (after 1 second has passed) is stored for the redo state.

Clone this wiki locally