Skip to content

Experiment with adding undo/redo #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
},
"devDependencies": {
"@wordpress/env": "^2.0.0",
"@wordpress/data-controls": "^1.14.0",
"@wordpress/eslint-plugin": "^7.2.0",
"@wordpress/keycodes": "^2.13.0",
"@wordpress/scripts": "^12.2.0",
"autoprefixer": "^9.8.6",
"css-loader": "^4.3.0",
Expand All @@ -44,6 +46,7 @@
"@wordpress/format-library": "^1.23.0",
"@wordpress/i18n": "^3.15.0",
"@wordpress/interface": "^0.8.0",
"@wordpress/media-utils": "^1.16.0"
"@wordpress/media-utils": "^1.16.0",
"redux-undo": "^1.0.1"
}
}
28 changes: 15 additions & 13 deletions src/components/block-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import {
import Sidebar from 'components/sidebar';

function BlockEditor( { settings: _settings } ) {
const [ blocks, updateBlocks ] = useState( [] );
const blocks = useSelect((select) => select("getdavesbe").getBlocks());
const { updateBlocks } = useDispatch("getdavesbe");
const { createInfoNotice } = useDispatch( 'core/notices' );

const canUserCreateMedia = useSelect( ( select ) => {
Expand All @@ -47,17 +48,17 @@ function BlockEditor( { settings: _settings } ) {
};
}, [ canUserCreateMedia, _settings ] );

useEffect( () => {
const storedBlocks = window.localStorage.getItem( 'getdavesbeBlocks' );
// useEffect( () => {
// const storedBlocks = window.localStorage.getItem( 'getdavesbeBlocks' );

if ( storedBlocks?.length ) {
handleUpdateBlocks(() => parse(storedBlocks));
createInfoNotice( 'Blocks loaded', {
type: 'snackbar',
isDismissible: true,
} );
}
}, [] );
// if ( storedBlocks?.length ) {
// handleUpdateBlocks(() => parse(storedBlocks));
// createInfoNotice( 'Blocks loaded', {
// type: 'snackbar',
// isDismissible: true,
// } );
// }
// }, [] );

/**
* Wrapper for updating blocks. Required as `onInput` callback passed to
Expand All @@ -70,8 +71,9 @@ function BlockEditor( { settings: _settings } ) {
}

function handlePersistBlocks( newBlocks ) {
updateBlocks( newBlocks );
window.localStorage.setItem( 'getdavesbeBlocks', serialize( newBlocks ) );
updateBlocks( newBlocks, true );
// persistBlocks(newBlocks);
// window.localStorage.setItem( 'getdavesbeBlocks', serialize( newBlocks ) );
}

return (
Expand Down
14 changes: 12 additions & 2 deletions src/components/header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,27 @@
*/
import { __ } from '@wordpress/i18n';


import HistoryUndo from './undo';
import HistoryRedo from "./redo";

export default function Header() {
function handleUndo() {
console.log( 'undo' );
}
return (
<div
className="getdavesbe-header"
role="region"
aria-label={ __( 'Standalone Editor top bar.', 'getdavesbe' ) }
aria-label={__("Standalone Editor top bar.", "getdavesbe")}
tabIndex="-1"
>
<h1 className="getdavesbe-header__title">
{ __( 'Standalone Block Editor', 'getdavesbe' ) }
{__("Standalone Block Editor", "getdavesbe")}
</h1>

<HistoryUndo />
<HistoryRedo />
</div>
);
}
35 changes: 35 additions & 0 deletions src/components/header/redo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import { withSelect, withDispatch } from '@wordpress/data';
import { compose } from '@wordpress/compose';
import { displayShortcut } from '@wordpress/keycodes';
import { redo as redoIcon } from '@wordpress/icons';


function HistoryRedo( { hasRedo, redo, ...props } ) {
return (
<Button
{ ...props }
icon={ redoIcon }
label={ __( 'Redo' ) }
shortcut={ displayShortcut.primary( 'x' ) }
// If there are no redo levels we don't want to actually disable this
// button, because it will remove focus for keyboard users.
// See: https://github.com/WordPress/gutenberg/issues/3486
aria-disabled={ ! hasRedo }
onClick={ hasRedo ? redo : undefined }
className="editor-history__redo"
/>
);
}

const EnhancedHistoryRedo = compose( [
withSelect( ( select ) => ( {
hasRedo: select( 'getdavesbe' ).hasRedo(),
} ) ),
withDispatch( ( dispatch ) => ( {
redo: dispatch( 'getdavesbe' ).redo,
} ) ),
] )( HistoryRedo );

export default EnhancedHistoryRedo;
35 changes: 35 additions & 0 deletions src/components/header/undo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import { withSelect, withDispatch } from '@wordpress/data';
import { compose } from '@wordpress/compose';
import { displayShortcut } from '@wordpress/keycodes';
import { undo as undoIcon } from '@wordpress/icons';


function HistoryUndo( { hasUndo, undo, ...props } ) {
return (
<Button
{ ...props }
icon={ undoIcon }
label={ __( 'Undo' ) }
shortcut={ displayShortcut.primary( 'z' ) }
// If there are no undo levels we don't want to actually disable this
// button, because it will remove focus for keyboard users.
// See: https://github.com/WordPress/gutenberg/issues/3486
aria-disabled={ ! hasUndo }
onClick={ hasUndo ? undo : undefined }
className="editor-history__undo"
/>
);
}

const EnhancedHistoryUndo = compose( [
withSelect( ( select ) => ( {
hasUndo: select( 'getdavesbe' ).hasUndo(),
} ) ),
withDispatch( ( dispatch ) => ( {
undo: dispatch( 'getdavesbe' ).undo,
} ) ),
] )( HistoryUndo );

export default EnhancedHistoryUndo;
10 changes: 7 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import { render } from '@wordpress/element';
import { registerCoreBlocks } from '@wordpress/block-library';
import Editor from './editor';

import './store';

import './styles.scss';

domReady( function() {
domReady( function () {
const settings = window.getdaveSbeSettings || {};
registerCoreBlocks();
render( <Editor settings={ settings } />, document.getElementById( 'getdave-sbe-block-editor' ) );
render(
<Editor settings={ settings } />,
document.getElementById( 'getdave-sbe-block-editor' )
);
} );

4 changes: 4 additions & 0 deletions src/store/action-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const UPDATE_BLOCKS = "UPDATE_BLOCKS";
export const PERSIST_BLOCKS = "PERSIST_BLOCKS";
export const FETCH_BLOCKS_FROM_STORAGE = "FETCH_BLOCKS_FROM_STORAGE";
export const PERSIST_BLOCKS_TO_STORAGE = "PERSIST_BLOCKS_TO_STORAGE";
43 changes: 43 additions & 0 deletions src/store/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
UPDATE_BLOCKS,
PERSIST_BLOCKS,
FETCH_BLOCKS_FROM_STORAGE,
PERSIST_BLOCKS_TO_STORAGE,
} from "./action-types";
import { ActionCreators as ReduxUndo } from "redux-undo";


export function undo() {
return ReduxUndo.undo();
}

export function redo() {
return ReduxUndo.redo();
}

export function *updateBlocks( blocks, persist = false ) {

if( persist ) {
yield persistBlocksToStorage(blocks);
}

return {
type: persist ? PERSIST_BLOCKS : UPDATE_BLOCKS,
blocks,
};
}

export function fetchBlocksFromStorage() {
return {
type: FETCH_BLOCKS_FROM_STORAGE,
};
};

export function persistBlocksToStorage(blocks) {
return {
type: PERSIST_BLOCKS_TO_STORAGE,
blocks,
};
}


21 changes: 21 additions & 0 deletions src/store/controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
FETCH_BLOCKS_FROM_STORAGE,
PERSIST_BLOCKS_TO_STORAGE,
} from "./action-types";
import { serialize } from "@wordpress/blocks";

export default {
[PERSIST_BLOCKS_TO_STORAGE](action) {
return new Promise((resolve, reject) => {
window.localStorage.setItem("getdavesbeBlocks", serialize(action.blocks));
resolve(action.blocks);
});
},
[FETCH_BLOCKS_FROM_STORAGE]() {
return new Promise((resolve, reject) => {
const storedBlocks =
window.localStorage.getItem("getdavesbeBlocks") || [];
resolve(storedBlocks);
});
},
};
30 changes: 30 additions & 0 deletions src/store/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* WordPress dependencies
*/
import { registerStore } from '@wordpress/data';

/**
* Internal dependencies
*/
import reducer from './reducer';
import * as selectors from './selectors';
import * as actions from './actions';
import * as resolvers from "./resolvers";
import controls from './controls';

/**
* Module Constants
*/
const MODULE_KEY = 'getdavesbe';

const store = registerStore(MODULE_KEY, {
reducer,
selectors,
actions,
controls,
resolvers,
});

window.getDaveStore = store;

export default store;
24 changes: 24 additions & 0 deletions src/store/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

import undoable, { groupByActionTypes, includeAction } from "redux-undo";



import { UPDATE_BLOCKS, PERSIST_BLOCKS } from "./action-types";

function blocksReducer(state = [], action) {
switch (action.type) {
case UPDATE_BLOCKS:
case PERSIST_BLOCKS:
const { blocks } = action;

return {
blocks,
};
}

return state;
}

export default undoable(blocksReducer, {
filter: includeAction(PERSIST_BLOCKS),
});
10 changes: 10 additions & 0 deletions src/store/resolvers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { parse } from "@wordpress/blocks";
import { fetchBlocksFromStorage, updateBlocks } from "./actions";

export function *getBlocks() {
const rawBlocks = yield fetchBlocksFromStorage();
const persist = false;
const blocks = parse(rawBlocks);
yield updateBlocks(blocks, persist);
return blocks;
}
19 changes: 19 additions & 0 deletions src/store/selectors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { createRegistrySelector } from '@wordpress/data';






export const getBlocks = ( state ) => {
return state.present.blocks || [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated this to allow for a default/fallback set of blocks, so that can reload blocks previously worked on;

export const getBlocks = ( state, defaultBlocks=[] ) => {
	return state.present.blocks || defaultBlocks;
}
  const blocks = useSelect((select) => select("getdavesbe").getBlocks(parse(storedBlocks)));

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep although grabbing for storage is a side effect so should probably be handled in a control action. Still working that one out.

}

export const hasUndo = (state) => {
return state.past?.length;
};

export const hasRedo = (state) => {
return state.future?.length;
};

1 change: 1 addition & 0 deletions src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@import "~@wordpress/base-styles/breakpoints";
@import "~@wordpress/base-styles/animations";
@import "~@wordpress/base-styles/z-index";
@import "~@wordpress/interface/src/style.scss";



Expand Down
3 changes: 2 additions & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ module.exports = {
...defaultConfig.resolve,
// alias directories to paths you can use in import() statements
alias: {
components: path.join( paths.srcDir, 'components' ),
components: path.join(paths.srcDir, "components"),
store: path.join(paths.srcDir, "store"),
},
},
};
Expand Down