Skip to content

Commit

Permalink
Update the behavior of the cached undo/redo stack
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowriad committed Jun 19, 2023
1 parent ac6580d commit 2aec3e2
Show file tree
Hide file tree
Showing 5 changed files with 23 additions and 28 deletions.
12 changes: 9 additions & 3 deletions docs/explanations/architecture/entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,14 @@ For example, let's say a user edits the title of a post, followed by a modificat

The store also keep tracks of a "pointer" to the current "undo/redo" step. By default, the pointer always points to the last item in the stack. This pointer is updated when the user performs an undo or redo operation.

### Transient changes
### Cached changes

The undo/redo core behavior also supports what we call "transient modifications". These are modifications that are not stored in the undo/redo stack right away. For instance, when a user starts typing in a text field, the value of the field is modified in the store, but this modification is not stored in the undo/redo stack until after the user moves to the next word or after a few milliseconds. This is done to avoid creating a new undo/redo step for each character typed by the user.
The undo/redo core behavior also supports what we call "cached modifications". These are modifications that are not stored in the undo/redo stack right away. For instance, when a user starts typing in a text field, the value of the field is modified in the store, but this modification is not stored in the undo/redo stack until after the user moves to the next word or after a few milliseconds. This is done to avoid creating a new undo/redo step for each character typed by the user.

So by default, `core-data` store considers all modifications to properties that are marked as "transient" (like the `blocks` property in the post entity) as transient modifications. It keeps these modifications outside the undo/redo stack in what is called a "cache" of modifications and these modifications are only stored in the undo/redo stack when we explicitely call `__unstableCreateUndoLevel` or when the next non-transient modification is performed.
Cached changes are kept outside the undo/redo stack in what is called a "cache" of modifications and these modifications are only stored in the undo/redo stack when we explicitely call `__unstableCreateUndoLevel` or when the next modification is not a cached one.

By default all calls to `editEntityRecord` are considered "non-cached" unless the `isCached` option is passed as true. Example:

```js
wp.data.dispatch( 'core' ).editEntityRecord( 'postType', 'post', 1, { title: 'Hello World' }, { isCached: true } );
```
4 changes: 2 additions & 2 deletions packages/core-data/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ export const editEntityRecord =
`The entity being edited (${ kind }, ${ name }) does not have a loaded config.`
);
}
const { transientEdits = {}, mergedEdits = {} } = entityConfig;
const { mergedEdits = {} } = entityConfig;
const record = select.getRawEntityRecord( kind, name, recordId );
const editedRecord = select.getEditedEntityRecord(
kind,
Expand All @@ -382,7 +382,6 @@ export const editEntityRecord =
: value;
return acc;
}, {} ),
transientEdits,
};
dispatch( {
type: 'EDIT_ENTITY_RECORD',
Expand All @@ -395,6 +394,7 @@ export const editEntityRecord =
acc[ key ] = editedRecord[ key ];
return acc;
}, {} ),
isCached: options.isCached,
},
},
} );
Expand Down
4 changes: 2 additions & 2 deletions packages/core-data/src/entity-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) {
edits.content = ( { blocks: blocksForSerialization = [] } ) =>
__unstableSerializeAndClean( blocksForSerialization );

editEntityRecord( kind, name, id, edits );
editEntityRecord( kind, name, id, edits, { isCached: false } );
},
[ kind, name, id, blocks ]
);
Expand All @@ -209,7 +209,7 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) {
( newBlocks, options ) => {
const { selection } = options;
const edits = { blocks: newBlocks, selection };
editEntityRecord( kind, name, id, edits );
editEntityRecord( kind, name, id, edits, { isCached: true } );
},
[ kind, name, id ]
);
Expand Down
8 changes: 2 additions & 6 deletions packages/core-data/src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ export const entities = ( state = {}, action ) => {
*
* @property {number} list The undo stack.
* @property {number} offset Where in the undo stack we are.
* @property {Object} cache Cache of unpersisted transient edits.
* @property {Object} cache Cache of unpersisted edits.
*/

/** @typedef {Array<Object> & UndoStateMeta} UndoState */
Expand Down Expand Up @@ -543,10 +543,6 @@ export function undo( state = UNDO_INITIAL_STATE, action ) {
return state;
}

const isCachedChange = Object.keys( action.edits ).every(
( key ) => action.transientEdits[ key ]
);

const edits = Object.keys( action.edits ).map( ( key ) => {
return {
kind: action.kind,
Expand All @@ -558,7 +554,7 @@ export function undo( state = UNDO_INITIAL_STATE, action ) {
};
} );

if ( isCachedChange ) {
if ( action.meta.undo.isCached ) {
return {
...state,
cache: edits.reduce( appendEditToStack, state.cache ),
Expand Down
23 changes: 8 additions & 15 deletions packages/core-data/src/test/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,19 +155,21 @@ describe( 'undo', () => {
from,
to,
} );
const createNextEditAction = ( edits, transientEdits = {} ) => {
const createNextEditAction = ( edits, isCached ) => {
let action = {
kind: 'someKind',
name: 'someName',
recordId: 'someRecordId',
edits,
transientEdits,
};
action = {
type: 'EDIT_ENTITY_RECORD',
...action,
meta: {
undo: { edits: lastValues },
undo: {
isCached,
edits: lastValues,
},
},
};
lastValues = { ...lastValues, ...edits };
Expand Down Expand Up @@ -303,10 +305,7 @@ describe( 'undo', () => {
it( 'handles flattened undos/redos', () => {
undoState = createNextUndoState();
undoState = createNextUndoState( { value: 1 } );
undoState = createNextUndoState(
{ transientValue: 2 },
{ transientValue: true }
);
undoState = createNextUndoState( { transientValue: 2 }, true );
undoState = createNextUndoState( { value: 3 } );
expectedUndoState.list.push(
[
Expand Down Expand Up @@ -335,10 +334,7 @@ describe( 'undo', () => {

// Check that transient edits are merged into the last
// edits.
undoState = createNextUndoState(
{ transientValue: 2 },
{ transientValue: true }
);
undoState = createNextUndoState( { transientValue: 2 }, true );
undoState = createNextUndoState( 'isCreate' );
expectedUndoState.list[ expectedUndoState.list.length - 1 ].push(
createExpectedDiff( 'transientValue', { from: undefined, to: 2 } )
Expand All @@ -359,10 +355,7 @@ describe( 'undo', () => {
it( 'explicitly creates an undo level when undoing while there are pending transient edits', () => {
undoState = createNextUndoState();
undoState = createNextUndoState( { value: 1 } );
undoState = createNextUndoState(
{ transientValue: 2 },
{ transientValue: true }
);
undoState = createNextUndoState( { transientValue: 2 }, true );
undoState = createNextUndoState( 'isUndo' );
expectedUndoState.list.push( [
createExpectedDiff( 'value', { from: undefined, to: 1 } ),
Expand Down

0 comments on commit 2aec3e2

Please sign in to comment.