Skip to content

Commit

Permalink
Move undo/redo to middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
mirka committed Dec 7, 2021
1 parent 371c80a commit 7cedd34
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 80 deletions.
9 changes: 2 additions & 7 deletions src/components/block-editor-toolbar/header-toolbar/redo.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,10 @@ import { useSelect, useDispatch } from '@wordpress/data';
import { displayShortcut } from '@wordpress/keycodes';
import { redo as redoIcon } from '@wordpress/icons';
import { forwardRef } from '@wordpress/element';
import { applyFilters } from '@wordpress/hooks';

function EditorHistoryRedo( props, ref ) {
const hasRedo = useSelect( ( select ) => {
return applyFilters( 'isoEditor.blockEditor.hasEditorRedo', select( 'isolated/editor' ).hasEditorRedo() );
}, [] );
const { redo: defaultRedo } = useDispatch( 'isolated/editor' );
const redo = applyFilters( 'isoEditor.blockEditor.redo', defaultRedo );

const hasRedo = useSelect( ( select ) => select( 'isolated/editor' ).hasEditorRedo(), [] );
const { redo } = useDispatch( 'isolated/editor' );
return (
<Button
{ ...props }
Expand Down
9 changes: 2 additions & 7 deletions src/components/block-editor-toolbar/header-toolbar/undo.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,10 @@ import { useSelect, useDispatch } from '@wordpress/data';
import { displayShortcut } from '@wordpress/keycodes';
import { undo as undoIcon } from '@wordpress/icons';
import { forwardRef } from '@wordpress/element';
import { applyFilters } from '@wordpress/hooks';

function EditorHistoryUndo( props, ref ) {
const hasUndo = useSelect( ( select ) => {
return applyFilters( 'isoEditor.blockEditor.hasEditorUndo', select( 'isolated/editor' ).hasEditorUndo() );
}, [] );
const { undo: defaultUndo } = useDispatch( 'isolated/editor' );
const undo = applyFilters( 'isoEditor.blockEditor.undo', defaultUndo );

const hasUndo = useSelect( ( select ) => select( 'isolated/editor' ).hasEditorUndo(), [] );
const { undo } = useDispatch( 'isolated/editor' );
return (
<Button
{ ...props }
Expand Down
9 changes: 2 additions & 7 deletions src/components/block-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { __, _x } from '@wordpress/i18n';
import { useSelect } from '@wordpress/data';
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
import { useEffect } from '@wordpress/element';
import { applyFilters } from '@wordpress/hooks';

/**
* Internal dependencies
Expand Down Expand Up @@ -191,11 +190,7 @@ function BlockEditor( props ) {
);
}

export default withDispatch( ( dispatch, _ownProps, { select } ) => {
export default withDispatch( ( dispatch ) => {
const { redo, undo } = dispatch( 'isolated/editor' );

return {
redo: applyFilters( 'isoEditor.blockEditor.redo', redo ),
undo: applyFilters( 'isoEditor.blockEditor.undo', undo ),
};
return { redo, undo };
} )( BlockEditor );
7 changes: 6 additions & 1 deletion src/components/collaborative-editing/use-yjs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ async function initYDoc( { settings, registry } ) {

doc.onRemoteDataChange( ( changes ) => {
debug( 'remote change received by ydoc', changes );
dispatch( 'isolated/editor' ).updateBlocksWithUndo( changes.blocks );
dispatch( 'isolated/editor' ).updateBlocksWithUndo( changes.blocks, { isTriggeredByYDoc: true } );
} );

doc.onUndoRedo( ( changes ) => {
debug( 'ydoc updated by undo manager', changes );
dispatch( 'isolated/editor' ).updateBlocksWithUndo( changes.blocks, { isTriggeredByYDoc: true } );
} );

const { isFirstInChannel } = await transport.connect( {
Expand Down
35 changes: 25 additions & 10 deletions src/components/collaborative-editing/use-yjs/yjs-doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,31 @@ export function createDocument( { identity, applyDataChanges, getData, sendMessa
const doc = new yjs.Doc();
let state = 'off';

let listeners = [];
let remoteChangeListeners = [];
let stateListeners = [];
let undoRedoListeners = [];

doc.on( 'update', ( update, origin ) => {
const isLocalOrigin = origin === identity || origin instanceof yjs.UndoManager;
const isRemoteOrigin = origin !== identity && ! ( origin instanceof yjs.UndoManager );

if ( isLocalOrigin && state === 'on' ) {
if ( isRemoteOrigin ) {
const newData = getData( doc );
remoteChangeListeners.forEach( ( listener ) => listener( newData ) );
return;
}

if ( origin instanceof yjs.UndoManager ) {
const newData = getData( doc );
undoRedoListeners.forEach( ( listener ) => listener( newData ) );
}

if ( ! isRemoteOrigin && state === 'on' ) {
sendMessage( {
protocol: 'yjs1',
messageType: 'syncUpdate',
update: encodeArray( update ),
} );
}

if ( origin !== identity ) {
const newData = getData( doc );
listeners.forEach( ( listener ) => listener( newData ) );
}
} );

const setState = ( newState ) => {
Expand Down Expand Up @@ -124,10 +131,10 @@ export function createDocument( { identity, applyDataChanges, getData, sendMessa
},

onRemoteDataChange( listener ) {
listeners.push( listener );
remoteChangeListeners.push( listener );

return () => {
listeners = listeners.filter( ( l ) => l !== listener );
remoteChangeListeners = remoteChangeListeners.filter( ( l ) => l !== listener );
};
},

Expand All @@ -139,6 +146,14 @@ export function createDocument( { identity, applyDataChanges, getData, sendMessa
};
},

onUndoRedo( listener ) {
undoRedoListeners.push( listener );

return () => {
undoRedoListeners = undoRedoListeners.filter( ( l ) => l !== listener );
};
},

getState() {
return state;
},
Expand Down
50 changes: 22 additions & 28 deletions src/components/collaborative-editing/use-yjs/yjs-undo.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
*/
import * as yjs from 'yjs';

/**
* WordPress dependencies
*/
import { addFilter } from '@wordpress/hooks';

const debugUndo = require( 'debug' )( 'iso-editor:collab:undo' );

/**
Expand All @@ -34,38 +29,37 @@ export function setupUndoManager( typeScope, identity, registry ) {

const undoManager = new yjs.UndoManager( typeScope, { trackedOrigins: new Set( [ identity ] ) } );

const debugUndoWithStackSizes = ( ...args ) => {
debugUndo( ...args );
debugUndo( `stack size: undo ${ undoManager.undoStack.length }, redo ${ undoManager.redoStack.length }` );
};

undoManager.on( 'stack-item-added', ( event ) => {
const selection = getSelection();
event.stackItem.meta.set( 'caret-location', selection );
debugUndo( 'undo stack item added with selection', selection );
debugUndoWithStackSizes( `${ event.type } stack item added with selection`, selection );
} );

undoManager.on( 'stack-item-popped', ( event ) => {
const selectionReferenceItem = event.type === 'undo' ? undoManager?.undoStack.at( -1 ) : event.stackItem;
const selection = selectionReferenceItem?.meta.get( 'caret-location' );
if ( event.type === 'undo' && undoManager.undoStack.length === 0 ) {
debugUndoWithStackSizes( `undo stack item popped (last item, no caret position to restore)` );
return;
}

const selectionReferenceItem =
event.type === 'undo' ? undoManager.undoStack[ undoManager.undoStack.length - 1 ] : event.stackItem;
const selection = selectionReferenceItem.meta.get( 'caret-location' );

if ( selection?.start ) {
setSelection( selection );
debugUndoWithStackSizes( `${ event.type } stack item popped with selection`, selection );
return;
}
debugUndo( 'stack item popped with selection', selection );

debugUndoWithStackSizes( `${ event.type } stack item popped without selection` );
} );

debugUndo( 'instantiated UndoManager' );
dispatch( 'isolated/editor' ).setUndoManager( undoManager );

addFilter( 'isoEditor.blockEditor.undo', 'isolated-block-editor/collab', () => () => {
debugUndo( 'undo' );
undoManager.undo();
} );
addFilter( 'isoEditor.blockEditor.redo', 'isolated-block-editor/collab', () => () => {
debugUndo( 'redo' );
undoManager.redo();
} );
addFilter(
'isoEditor.blockEditor.hasEditorUndo',
'isolated-block-editor/collab',
() => ( undoManager.undoStack.length ?? 0 ) > 1
);
addFilter(
'isoEditor.blockEditor.hasEditorRedo',
'isolated-block-editor/collab',
() => !! undoManager.redoStack.length
);
debugUndo( 'instantiated UndoManager' );
}
8 changes: 4 additions & 4 deletions src/store/blocks/actions.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ActionCreators } from 'redux-undo';

const actions = {
undo() {
return ActionCreators.undo();
*undo() {
return yield ActionCreators.undo();
},
redo() {
return ActionCreators.redo();
*redo() {
return yield ActionCreators.redo();
},
/**
* Update blocks without undo history
Expand Down
18 changes: 15 additions & 3 deletions src/store/blocks/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function getBlocks( state ) {
* @param {object} state - Current state
* @returns {object}
*/
export function getEditorSelection( state ) {
export function getEditorSelection( state ) {
return state.blocks.present.selection;
}

Expand All @@ -24,7 +24,13 @@ export function getBlocks( state ) {
* @returns {boolean}
*/
export function hasEditorUndo( state ) {
return state.blocks.past.length > 0 && getEditorMode( state ) === 'visual';
if ( getEditorMode( state ) !== 'visual' ) return false;

if ( state.collab?.undoManager ) {
return !! state.collab.undoManager.undoStack.length;
}

return state.blocks.past.length > 0;
}

/**
Expand All @@ -33,7 +39,13 @@ export function hasEditorUndo( state ) {
* @returns {boolean}
*/
export function hasEditorRedo( state ) {
return state.blocks.future.length > 0 && getEditorMode( state ) === 'visual';
if ( getEditorMode( state ) !== 'visual' ) return false;

if ( state.collab?.undoManager ) {
return !! state.collab.undoManager.redoStack.length;
}

return state.blocks.future.length > 0;
}

/**
Expand Down
9 changes: 8 additions & 1 deletion src/store/collab/actions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
export function setYDoc( doc ) {
return {
type: 'SET_YJS_DOC',
type: 'SET_COLLAB_YJS_DOC',
doc,
};
}

export function setUndoManager( undoManager ) {
return {
type: 'SET_COLLAB_UNDO_MANAGER',
undoManager,
};
}
36 changes: 33 additions & 3 deletions src/store/collab/controls.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
import { ActionCreators } from 'redux-undo';

import { createRegistryControl } from '@wordpress/data';

const debugUndo = require( 'debug' )( 'iso-editor:collab:undo' );

const applyChangesToYDoc = createRegistryControl( ( registry ) => ( action ) => {
const doc = registry.select( 'isolated/editor' ).getYDoc();

if ( doc ) {
if ( doc && ! action.isTriggeredByYDoc ) {
doc.applyDataChanges( { blocks: action.blocks } );
}

return action;
} );

export const UPDATE_BLOCKS_WITH_UNDO = applyChangesToYDoc;
export const UPDATE_BLOCKS_WITHOUT_UNDO = applyChangesToYDoc;
export default {
UPDATE_BLOCKS_WITH_UNDO: applyChangesToYDoc,
UPDATE_BLOCKS_WITHOUT_UNDO: applyChangesToYDoc,

[ ActionCreators.undo().type ]: createRegistryControl( ( registry ) => ( action ) => {
const undoManager = registry.select( 'isolated/editor' ).getUndoManager();

if ( ! undoManager ) {
return action;
}

debugUndo( 'undo' );
undoManager.undo();
return; // prevent default action
} ),

[ ActionCreators.redo().type ]: createRegistryControl( ( registry ) => ( action ) => {
const undoManager = registry.select( 'isolated/editor' ).getUndoManager();

if ( ! undoManager ) {
return action;
}

debugUndo( 'redo' );
registry.select( 'isolated/editor' ).getUndoManager().redo();
return; // prevent default action
} ),
};
5 changes: 4 additions & 1 deletion src/store/collab/reducer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
const reducer = ( state = {}, action ) => {
switch ( action.type ) {
case 'SET_YJS_DOC': {
case 'SET_COLLAB_YJS_DOC': {
return { ...state, yDoc: action.doc };
}
case 'SET_COLLAB_UNDO_MANAGER': {
return { ...state, undoManager: action.undoManager };
}
}

return state;
Expand Down
4 changes: 4 additions & 0 deletions src/store/collab/selectors.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export function getYDoc( state ) {
return state.collab.yDoc;
}

export function getUndoManager( state ) {
return state.collab.undoManager;
}
16 changes: 8 additions & 8 deletions src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
*/

import { combineReducers } from '@wordpress/data';
import { controls } from '@wordpress/data-controls';

/**
* Internal dependencies
Expand All @@ -17,17 +16,19 @@ import preferencesReducer from './preferences/reducer';
import preferenceActions from './preferences/actions';
import optionsReducer from './options/reducer';
import optionActions from './options/actions';
import peersReducer from './peers/reducer';
import peersActions from './peers/actions';
import collabReducer from './collab/reducer';
import * as collabActions from './collab/actions';
import * as blockSelectors from './blocks/selectors';
import * as editorSelectors from './editor/selectors';
import * as preferenceSelectors from './preferences/selectors';
import * as optionSelectors from './options/selectors';
import * as peersSelectors from './peers/selectors';

// Collaborative Editing
import * as collabActions from './collab/actions';
import * as collabSelectors from './collab/selectors';
import * as collabControls from './collab/controls';
import * as peersSelectors from './peers/selectors';
import collabControls from './collab/controls'; // will safely noop if collab isn't initialized
import collabReducer from './collab/reducer';
import peersActions from './peers/actions';
import peersReducer from './peers/reducer';

function storeConfig( preferencesKey, defaultPreferences ) {
return {
Expand Down Expand Up @@ -59,7 +60,6 @@ function storeConfig( preferencesKey, defaultPreferences ) {
},

controls: {
...controls,
...collabControls,
},

Expand Down

0 comments on commit 7cedd34

Please sign in to comment.