From 8db58260a9052616592cca69914c81685d8f9988 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 21 Aug 2019 10:57:01 +0200 Subject: [PATCH 01/80] Snapping proof-of-concept --- .eslintrc | 6 + .../blocks/amp-story-page/edit.js | 5 +- .../components/animation-controls.js | 2 +- .../higher-order/with-amp-story-settings.js | 3 + .../higher-order/with-page-number.js | 14 +- .../higher-order/with-snap-lines.js | 69 ++++++++++ .../higher-order/with-snap-targets.js | 125 +++++++++++++++++ assets/src/stories-editor/components/index.js | 2 + .../components/resizable-box/index.js | 130 +++++++++++++++++- .../components/with-wrapper-props.js | 4 +- assets/src/stories-editor/constants.js | 2 + assets/src/stories-editor/helpers/index.js | 10 +- assets/src/stories-editor/index.js | 2 + assets/src/stories-editor/store/actions.js | 25 ++++ assets/src/stories-editor/store/index.js | 4 + assets/src/stories-editor/store/reducer.js | 51 ++++++- assets/src/stories-editor/store/selectors.js | 19 +++ .../src/stories-editor/store/test/actions.js | 45 ++++++ .../src/stories-editor/store/test/reducer.js | 37 +++++ package.json | 1 + 20 files changed, 528 insertions(+), 28 deletions(-) create mode 100644 assets/src/stories-editor/components/higher-order/with-snap-lines.js create mode 100644 assets/src/stories-editor/components/higher-order/with-snap-targets.js diff --git a/.eslintrc b/.eslintrc index 765f3ab5bd4..d053221d2a9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -42,6 +42,12 @@ "no-template-curly-in-string": "error", "no-throw-literal": "error", "no-unmodified-loop-condition": "error", + "no-unused-vars": [ + "error", + { + "ignoreRestSiblings": true, + } + ], "no-useless-call": "error", "no-useless-concat": "error", "prefer-object-spread": "error", diff --git a/assets/src/stories-editor/blocks/amp-story-page/edit.js b/assets/src/stories-editor/blocks/amp-story-page/edit.js index 6568ca10395..f884d7d094a 100644 --- a/assets/src/stories-editor/blocks/amp-story-page/edit.js +++ b/assets/src/stories-editor/blocks/amp-story-page/edit.js @@ -531,7 +531,10 @@ export default compose( } ), withSelect( ( select, { clientId, attributes } ) => { const { getMedia } = select( 'core' ); - const { getBlockOrder, getBlockRootClientId } = select( 'core/block-editor' ); + const { + getBlockOrder, + getBlockRootClientId, + } = select( 'core/block-editor' ); const { getAnimatedBlocks } = select( 'amp/story' ); const isFirstPage = getBlockOrder().indexOf( clientId ) === 0; diff --git a/assets/src/stories-editor/components/animation-controls.js b/assets/src/stories-editor/components/animation-controls.js index f256c7e6f8a..7d635d158ab 100644 --- a/assets/src/stories-editor/components/animation-controls.js +++ b/assets/src/stories-editor/components/animation-controls.js @@ -92,7 +92,7 @@ AnimationControls.propTypes = { onAnimationDelayChange: PropTypes.func.isRequired, onAnimationAfterChange: PropTypes.func.isRequired, animationType: PropTypes.string, - animationDuration: PropTypes.string, + animationDuration: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number ] ), animationDelay: PropTypes.string, animationAfter: PropTypes.string, selectedBlock: PropTypes.string, diff --git a/assets/src/stories-editor/components/higher-order/with-amp-story-settings.js b/assets/src/stories-editor/components/higher-order/with-amp-story-settings.js index f5d51f2a960..eb291d60ff4 100644 --- a/assets/src/stories-editor/components/higher-order/with-amp-story-settings.js +++ b/assets/src/stories-editor/components/higher-order/with-amp-story-settings.js @@ -36,6 +36,7 @@ import { BLOCKS_WITH_RESIZING, BLOCK_ROTATION_SNAPS, BLOCK_ROTATION_SNAP_GAP, + BLOCK_RESIZING_SNAP_GAP, } from '../../constants'; import { getBlockOrderDescription, maybeEnqueueFontStyle, getCallToActionBlock } from '../../helpers'; import bringForwardIcon from '../../../../images/stories-editor/bring-forward.svg'; @@ -257,10 +258,12 @@ export default createHigherOrderComponent( stopBlockActions(); } } blockName={ name } + clientId={ clientId } ampFitText={ ampFitText } onResizeStart={ () => { startBlockActions(); } } + snapGap={ BLOCK_RESIZING_SNAP_GAP } > { const { @@ -32,11 +31,6 @@ const applyWithSelect = withSelect( ( select, props ) => { }; } ); -const wrapperWithSelect = compose( - applyWithSelect, - withBlockName, -); - /** * Higher-order component that adds a page number label to page blocks * @@ -44,11 +38,11 @@ const wrapperWithSelect = compose( */ export default createHigherOrderComponent( ( BlockEdit ) => { - return wrapperWithSelect( ( props ) => { - const { blockName, pageNumber, isReordering } = props; + return applyWithSelect( ( props ) => { + const { name, pageNumber, isReordering } = props; // Not a valid top level block. - if ( ! ALLOWED_TOP_LEVEL_BLOCKS.includes( blockName ) || ! pageNumber ) { + if ( ! ALLOWED_TOP_LEVEL_BLOCKS.includes( name ) || ! pageNumber ) { return ; } diff --git a/assets/src/stories-editor/components/higher-order/with-snap-lines.js b/assets/src/stories-editor/components/higher-order/with-snap-lines.js new file mode 100644 index 00000000000..8b9e93089b8 --- /dev/null +++ b/assets/src/stories-editor/components/higher-order/with-snap-lines.js @@ -0,0 +1,69 @@ +/** + * WordPress dependencies + */ +import { withSelect } from '@wordpress/data'; +import { SVG } from '@wordpress/components'; +import { createHigherOrderComponent } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; + +const applyWithSelect = withSelect( ( select, { clientId } ) => { + const { getCurrentPage, snapLinesVisible, getSnapLines } = select( 'amp/story' ); + + return { + snapLinesVisible: snapLinesVisible(), + snapLines: getSnapLines(), + isActivePage: getCurrentPage() === clientId, + }; +} ); + +/** + * Higher-order component that adds snap lines to page blocks + * + * @return {Function} Higher-order component. + */ +export default createHigherOrderComponent( + ( BlockEdit ) => { + return applyWithSelect( ( props ) => { + const { snapLinesVisible, snapLines, isActivePage } = props; + + if ( ! isActivePage ) { + return ; + } + + if ( ! snapLinesVisible || ! snapLines.length ) { + return ; + } + + return ( + <> + + + { snapLines.map( ( [ start, end ], index ) => ( + + ) ) } + + + ); + } ); + }, + 'withSnapLines' +); diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js new file mode 100644 index 00000000000..6a64c76a8d6 --- /dev/null +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -0,0 +1,125 @@ +/** + * WordPress dependencies + */ +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose, createHigherOrderComponent } from '@wordpress/compose'; +import isShallowEqual from '@wordpress/is-shallow-equal'; + +/** + * Internal dependencies + */ +import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; +import { getPixelsFromPercentage } from '../../helpers'; + +const applyWithSelect = withSelect( ( select, { clientId, angle } ) => { + const { + getBlocksByClientId, + getBlockRootClientId, + getBlockOrder, + } = select( 'core/block-editor' ); + const { getSnapLines } = select( 'amp/story' ); + + const parentBlock = getBlockRootClientId( clientId ); + const siblings = getBlocksByClientId( getBlockOrder( parentBlock ) ) + .filter( ( { clientId: blockId } ) => blockId !== clientId ) + .filter( ( { attributes } ) => ! attributes.rotationAngle ); + + const snapLines = getSnapLines(); + const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); + + return { + horizontalSnaps: () => { + // @todo: Support snapping for a rotated block that is being resized. + if ( angle ) { + return []; + } + + const pageSnaps = [ + 0, + STORY_PAGE_INNER_WIDTH / 2, + STORY_PAGE_INNER_WIDTH, + ]; + + const blockSnaps = siblings + .map( ( { attributes } ) => { + const { positionLeft, width } = attributes; + const positionInPx = getPixelsFromPercentage( 'x', positionLeft ); + return [ positionInPx, positionInPx + width ]; + } ) + .reduce( ( result, snaps ) => { + for ( const snap of snaps ) { + if ( ! result.includes( snap ) && ! pageSnaps.includes( snap ) ) { + result.push( snap ); + } + } + + return result; + }, [] ); + + return [ ...pageSnaps, ...blockSnaps ]; + }, + verticalSnaps: () => { + // @todo: Support snapping for a rotated block that is being resized. + if ( angle ) { + return []; + } + + const pageSnaps = [ + 0, + STORY_PAGE_INNER_HEIGHT / 2, + STORY_PAGE_INNER_HEIGHT, + ]; + + const blockSnaps = siblings + .map( ( { attributes } ) => { + const { positionTop, height } = attributes; + const positionInPx = getPixelsFromPercentage( 'y', positionTop ); + return [ positionInPx, positionInPx + height ]; + } ) + .reduce( ( result, snaps ) => { + for ( const snap of snaps ) { + if ( ! result.includes( snap ) && ! pageSnaps.includes( snap ) ) { + result.push( snap ); + } + } + + return result; + }, [] ); + + return [ ...pageSnaps, ...blockSnaps ]; + }, + snapLines, + hasSnapLine, + }; +} ); + +const applyWithDispatch = withDispatch( ( dispatch ) => { + const { + showSnapLines, + hideSnapLines, + setSnapLines, + clearSnapLines, + } = dispatch( 'amp/story' ); + + return { + showSnapLines, + hideSnapLines, + setSnapLines, + clearSnapLines, + }; +} ); + +const enhance = compose( + applyWithSelect, + applyWithDispatch, +); + +/** + * Higher-order component that provides snap targets. + * + * @return {Function} Higher-order component. + */ +export default createHigherOrderComponent( + enhance, + 'withSnapTargets' +); diff --git a/assets/src/stories-editor/components/index.js b/assets/src/stories-editor/components/index.js index dc1bed21354..d97e32ee915 100644 --- a/assets/src/stories-editor/components/index.js +++ b/assets/src/stories-editor/components/index.js @@ -29,6 +29,8 @@ export { default as withCustomVideoBlockEdit } from './with-custom-video-block-e export { default as CustomVideoBlockEdit } from './custom-video-block-edit'; export { default as withIsReordering } from './higher-order/with-is-reordering'; export { default as withSelectedBlock } from './higher-order/with-selected-block'; +export { default as withSnapLines } from './higher-order/with-snap-lines'; +export { default as withSnapTargets } from './higher-order/with-snap-targets'; export { default as withWrapperProps } from './with-wrapper-props'; export { default as withActivePageState } from './with-active-page-state'; export { default as withStoryBlockDropZone } from './with-story-block-drop-zone'; diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index f9761772443..724b4fd00b0 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -13,10 +13,12 @@ import { ResizableBox } from '@wordpress/components'; /** * Internal dependencies */ +import withSnapTargets from '../higher-order/with-snap-targets'; import './edit.css'; import { getPercentageFromPixels, getPixelsFromPercentage, + findClosestSnap, } from '../../helpers'; import { getBlockPositioning, @@ -28,6 +30,8 @@ import { } from './helpers'; import { + STORY_PAGE_INNER_HEIGHT, + STORY_PAGE_INNER_WIDTH, TEXT_BLOCK_PADDING, REVERSE_WIDTH_CALCULATIONS, REVERSE_HEIGHT_CALCULATIONS, @@ -77,9 +81,24 @@ class EnhancedResizableBox extends Component { const isImage = 'core/image' === blockName; const isText = 'amp/amp-story-text' === blockName; + // Ensure that these props are not passed down. + const { + clientId, + snapGap, + horizontalSnaps, + verticalSnaps, + snapLines, + showSnapLines, + hideSnapLines, + setSnapLines, + clearSnapLines, + hasSnapLine, + ...childProps + } = otherProps; + return ( { // eslint-disable-line complexity + const newSnapLines = []; + const { deltaW, deltaH } = getResizedWidthAndHeight( event, angle, lastSeenX, lastSeenY, direction ); // Handle case where media is inserted from URL. @@ -162,8 +193,10 @@ class EnhancedResizableBox extends Component { width = blockElement.clientWidth; height = blockElement.clientHeight; } + let appliedWidth = minWidth <= width + deltaW ? width + deltaW : minWidth; let appliedHeight = minHeight <= height + deltaH ? height + deltaH : minHeight; + const isReducing = 0 > deltaW || 0 > deltaH; if ( textElement && isReducing ) { @@ -207,18 +240,79 @@ class EnhancedResizableBox extends Component { if ( ! angle ) { // If the resizing is to left or top then we have to compensate if ( REVERSE_WIDTH_CALCULATIONS.includes( direction ) ) { - const leftInPx = getPixelsFromPercentage( 'x', parseFloat( blockElementLeft ) ); - blockElement.style.left = getPercentageFromPixels( 'x', leftInPx - lastDeltaW ) + '%'; + let leftInPx = getPixelsFromPercentage( 'x', parseFloat( blockElementLeft ) ); + let leftSnap = 0; + + leftInPx = leftInPx - lastDeltaW; + + if ( lastDeltaW ) { + leftSnap = findClosestSnap( leftInPx, horizontalSnaps, snapGap ); + + if ( leftSnap !== leftInPx ) { + const leftSnapLine = [ [ leftSnap, 0 ], [ leftSnap, STORY_PAGE_INNER_HEIGHT ] ]; + if ( ! hasSnapLine( leftSnapLine ) ) { + newSnapLines.push( leftSnapLine ); + } + + appliedWidth += leftInPx - leftSnap; + leftInPx = leftSnap; + } + } + + blockElement.style.left = getPercentageFromPixels( 'x', leftInPx ) + '%'; + } else if ( lastDeltaW ) { + const widthSnap = findClosestSnap( blockElement.offsetLeft + appliedWidth, horizontalSnaps, snapGap ); + + if ( ( widthSnap - blockElement.offsetLeft ) !== appliedWidth ) { + const widthSnapLine = [ [ widthSnap, 0 ], [ widthSnap, STORY_PAGE_INNER_HEIGHT ] ]; + if ( ! hasSnapLine( widthSnapLine ) ) { + newSnapLines.push( widthSnapLine ); + } + + appliedWidth = widthSnap - blockElement.offsetLeft; + } } + if ( REVERSE_HEIGHT_CALCULATIONS.includes( direction ) ) { - const topInPx = getPixelsFromPercentage( 'y', parseFloat( blockElementTop ) ); - blockElement.style.top = getPercentageFromPixels( 'y', topInPx - lastDeltaH ) + '%'; + let topInPx = getPixelsFromPercentage( 'y', parseFloat( blockElementTop ) ); + let topSnap = 0; + + topInPx = topInPx - lastDeltaH; + + if ( lastDeltaH ) { + topSnap = findClosestSnap( topInPx, verticalSnaps, snapGap ); + + if ( topSnap !== topInPx ) { + const topSnapLine = [ [ 0, topSnap ], [ STORY_PAGE_INNER_WIDTH, topSnap ] ]; + + if ( ! hasSnapLine( topSnapLine ) ) { + newSnapLines.push( topSnapLine ); + } + + appliedHeight += topInPx - topSnap; + topInPx = topSnap; + } + } + + blockElement.style.top = getPercentageFromPixels( 'y', topInPx ) + '%'; + } else if ( lastDeltaH ) { + const heightSnap = findClosestSnap( blockElement.offsetTop + appliedHeight, verticalSnaps, snapGap ); + + if ( ( heightSnap - blockElement.offsetTop ) !== appliedHeight ) { + const heightSnapLine = [ [ 0, heightSnap ], [ STORY_PAGE_INNER_WIDTH, heightSnap ] ]; + if ( ! hasSnapLine( heightSnapLine ) ) { + newSnapLines.push( heightSnapLine ); + } + } + + appliedHeight = heightSnap - blockElement.offsetTop; } } else { const radianAngle = getRadianFromDeg( angle ); // Compare position between the initial and after resizing. let initialPosition, resizedPosition; + // If it's a text block, we shouldn't consider the added padding for measuring. if ( isText ) { initialPosition = getBlockPositioning( width - ( TEXT_BLOCK_PADDING * 2 ), height - ( TEXT_BLOCK_PADDING * 2 ), radianAngle, direction ); @@ -258,6 +352,10 @@ class EnhancedResizableBox extends Component { imageWrapper.style.width = appliedWidth + 'px'; imageWrapper.style.height = appliedHeight + 'px'; } + + if ( newSnapLines.length ) { + setSnapLines( ...newSnapLines ); + } } } > { children } @@ -266,10 +364,15 @@ class EnhancedResizableBox extends Component { } } +EnhancedResizableBox.defaultProps = { + snapGap: 0, +}; + EnhancedResizableBox.propTypes = { ampFitText: PropTypes.bool, angle: PropTypes.number, blockName: PropTypes.string, + clientId: PropTypes.string, minWidth: PropTypes.number, minHeight: PropTypes.number, onResizeStart: PropTypes.func.isRequired, @@ -277,6 +380,21 @@ EnhancedResizableBox.propTypes = { children: PropTypes.any.isRequired, width: PropTypes.number, height: PropTypes.number, + horizontalSnaps: PropTypes.oneOfType( [ + PropTypes.arrayOf( PropTypes.number ), + PropTypes.func, + ] ).isRequired, + verticalSnaps: PropTypes.oneOfType( [ + PropTypes.arrayOf( PropTypes.number ), + PropTypes.func, + ] ).isRequired, + snapGap: PropTypes.number.isRequired, + snapLines: PropTypes.array.isRequired, + showSnapLines: PropTypes.func.isRequired, + hideSnapLines: PropTypes.func.isRequired, + setSnapLines: PropTypes.func.isRequired, + clearSnapLines: PropTypes.func.isRequired, + hasSnapLine: PropTypes.func.isRequired, }; -export default EnhancedResizableBox; +export default withSnapTargets( EnhancedResizableBox ); diff --git a/assets/src/stories-editor/components/with-wrapper-props.js b/assets/src/stories-editor/components/with-wrapper-props.js index 6bdb1ff7862..b07af6a97d6 100644 --- a/assets/src/stories-editor/components/with-wrapper-props.js +++ b/assets/src/stories-editor/components/with-wrapper-props.js @@ -9,7 +9,7 @@ import { compose } from '@wordpress/compose'; import { ALLOWED_BLOCKS, ALLOWED_CHILD_BLOCKS } from '../constants'; import { withAttributes, withBlockName, withHasSelectedInnerBlock } from './'; -const wrapperWithSelect = compose( +const enhance = compose( withAttributes, withBlockName, withHasSelectedInnerBlock, @@ -22,7 +22,7 @@ const wrapperWithSelect = compose( * @return {Function} Enhanced component. */ const withWrapperProps = ( BlockListBlock ) => { - return wrapperWithSelect( ( props ) => { + return enhance( ( props ) => { const { blockName, hasSelectedInnerBlock, attributes } = props; // If it's not an allowed block then lets return original; diff --git a/assets/src/stories-editor/constants.js b/assets/src/stories-editor/constants.js index b196fb62e3b..7794b4ead23 100644 --- a/assets/src/stories-editor/constants.js +++ b/assets/src/stories-editor/constants.js @@ -253,3 +253,5 @@ export const TEXT_BLOCK_PADDING = 7; export const BLOCK_ROTATION_SNAPS = [ -180, -165, -150, -135, -120, -105, -90, -75, -60, -45, -30, -15, 0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180 ]; export const BLOCK_ROTATION_SNAP_GAP = 10; + +export const BLOCK_RESIZING_SNAP_GAP = 8; diff --git a/assets/src/stories-editor/helpers/index.js b/assets/src/stories-editor/helpers/index.js index 85977275490..84e1780227a 100644 --- a/assets/src/stories-editor/helpers/index.js +++ b/assets/src/stories-editor/helpers/index.js @@ -1602,14 +1602,10 @@ export const maybeAddMissingAnchor = ( clientId ) => { }; /** - * Given a rotation angle, finds the closest angle to snap to. + * Given a number, finds the closest snap target. * - * Inspired by the implementation in re-resizable. - * - * @see https://github.com/bokuweb/re-resizable - * - * @param {number} number - * @param {Array|function} snap List of snap targets or function that provider + * @param {number} number Given number. + * @param {Array|function} snap List of snap targets or function that provides them. * @param {number} snapGap Minimum gap required in order to move to the next snapping target * @return {number} New angle. */ diff --git a/assets/src/stories-editor/index.js b/assets/src/stories-editor/index.js index a6cbbdcd6d1..091f79af2d7 100644 --- a/assets/src/stories-editor/index.js +++ b/assets/src/stories-editor/index.js @@ -40,6 +40,7 @@ import { withStoryBlockDropZone, withCallToActionValidation, withEnforcedVideoUploadType, + withSnapLines, } from './components'; import { maybeEnqueueFontStyle, @@ -320,6 +321,7 @@ addFilter( 'editor.BlockEdit', 'ampStoryEditorBlocks/addStorySettings', withAmpS addFilter( 'editor.BlockEdit', 'ampStoryEditorBlocks/addPageNumber', withPageNumber ); addFilter( 'editor.BlockEdit', 'ampStoryEditorBlocks/addEditFeaturedImage', withEditFeaturedImage ); addFilter( 'editor.BlockEdit', 'ampEditorBlocks/addVideoBlockPreview', withCustomVideoBlockEdit, 9 ); +addFilter( 'editor.BlockEdit', 'ampEditorBlocks/withSnapLines', withSnapLines ); addFilter( 'editor.PostFeaturedImage', 'ampStoryEditorBlocks/addFeaturedImageNotice', withStoryFeaturedImageNotice ); addFilter( 'editor.BlockListBlock', 'ampStoryEditorBlocks/withActivePageState', withActivePageState ); addFilter( 'editor.BlockListBlock', 'ampStoryEditorBlocks/addWrapperProps', withWrapperProps ); diff --git a/assets/src/stories-editor/store/actions.js b/assets/src/stories-editor/store/actions.js index abcc9a86027..20b215339bf 100644 --- a/assets/src/stories-editor/store/actions.js +++ b/assets/src/stories-editor/store/actions.js @@ -140,3 +140,28 @@ export function resetOrder( order ) { order, }; } + +export function setSnapLines( ...snapLines ) { + return { + type: 'SET_SNAP_LINES', + snapLines, + }; +} + +export function clearSnapLines() { + return { + type: 'CLEAR_SNAP_LINES', + }; +} + +export function showSnapLines() { + return { + type: 'SHOW_SNAP_LINES', + }; +} + +export function hideSnapLines() { + return { + type: 'HIDE_SNAP_LINES', + }; +} diff --git a/assets/src/stories-editor/store/index.js b/assets/src/stories-editor/store/index.js index 0fefd279a41..b60b15701c0 100644 --- a/assets/src/stories-editor/store/index.js +++ b/assets/src/stories-editor/store/index.js @@ -27,6 +27,10 @@ export default registerStore( order: [], isReordering: false, }, + snap: { + showSnapLines: false, + snapLines: [], + }, }, } ); diff --git a/assets/src/stories-editor/store/reducer.js b/assets/src/stories-editor/store/reducer.js index 1f9fb884184..d0c5b334f8a 100644 --- a/assets/src/stories-editor/store/reducer.js +++ b/assets/src/stories-editor/store/reducer.js @@ -159,4 +159,53 @@ export function blocks( state = {}, action ) { } } -export default combineReducers( { animations, currentPage, blocks } ); +/** + * Reducer handling block snapping. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export function snap( state = {}, action ) { + switch ( action.type ) { + case 'SHOW_SNAP_LINES': + return { + ...state, + showSnapLines: true, + }; + + case 'HIDE_SNAP_LINES': + return { + ...state, + showSnapLines: false, + }; + + case 'SET_SNAP_LINES': + const { snapLines } = action; + return { + ...state, + snapLines: [ ...snapLines ], + }; + + case 'CLEAR_SNAP_LINES': + if ( ! state.snapLines.length ) { + return state; + } + + return { + ...state, + snapLines: [], + }; + + default: + return state; + } +} + +export default combineReducers( { + animations, + currentPage, + blocks, + snap, +} ); diff --git a/assets/src/stories-editor/store/selectors.js b/assets/src/stories-editor/store/selectors.js index ff0d6c1e0ce..0ace220f231 100644 --- a/assets/src/stories-editor/store/selectors.js +++ b/assets/src/stories-editor/store/selectors.js @@ -98,3 +98,22 @@ export function getBlockIndex( state, page ) { export function isReordering( state ) { return state.blocks.isReordering || false; } + +/** + * Shared reference to an empty array for cases where it is important to avoid + * returning a new array reference on every invocation, as in a connected or + * other pure component which performs `shouldComponentUpdate` check on props. + * This should be used as a last resort, since the normalized data should be + * maintained by the reducer result in state. + * + * @type {Array} + */ +const EMPTY_ARRAY = []; + +export function snapLinesVisible( state ) { + return state.snap.showSnapLines; +} + +export function getSnapLines( state ) { + return state.snap.snapLines && state.snap.snapLines.length > 0 ? state.snap.snapLines : EMPTY_ARRAY; +} diff --git a/assets/src/stories-editor/store/test/actions.js b/assets/src/stories-editor/store/test/actions.js index 2bcf6dfd95f..3ac282dd626 100644 --- a/assets/src/stories-editor/store/test/actions.js +++ b/assets/src/stories-editor/store/test/actions.js @@ -11,6 +11,10 @@ import { movePageToPosition, saveOrder, resetOrder, + showSnapLines, + hideSnapLines, + setSnapLines, + clearSnapLines, } from '../actions'; describe( 'actions', () => { @@ -132,4 +136,45 @@ describe( 'actions', () => { } ); } ); } ); + + describe( 'setSnapLines', () => { + it( 'should return the SET_SNAP_LINES action', () => { + const result = setSnapLines( 1, 2, 3 ); + + expect( result ).toStrictEqual( { + type: 'SET_SNAP_LINES', + items: [ 1, 2, 3 ], + } ); + } ); + } ); + + describe( 'clearSnapLines', () => { + it( 'should return the CLEAR_SNAP_LINES action', () => { + const result = clearSnapLines(); + + expect( result ).toStrictEqual( { + type: 'CLEAR_SNAP_LINES', + } ); + } ); + } ); + + describe( 'showSnapLines', () => { + it( 'should return the SHOW_SNAP_LINES action', () => { + const result = showSnapLines(); + + expect( result ).toStrictEqual( { + type: 'SHOW_SNAP_LINES', + } ); + } ); + } ); + + describe( 'hideSnapLines', () => { + it( 'should return the HIDE_SNAP_LINES action', () => { + const result = hideSnapLines(); + + expect( result ).toStrictEqual( { + type: 'HIDE_SNAP_LINES', + } ); + } ); + } ); } ); diff --git a/assets/src/stories-editor/store/test/reducer.js b/assets/src/stories-editor/store/test/reducer.js index 9f994d55807..2eb1a0ca45f 100644 --- a/assets/src/stories-editor/store/test/reducer.js +++ b/assets/src/stories-editor/store/test/reducer.js @@ -10,6 +10,7 @@ import { animations, currentPage, blocks, + snap, } from '../reducer'; describe( 'reducers', () => { @@ -172,4 +173,40 @@ describe( 'reducers', () => { } ); } ); } ); + + describe( 'snap()', () => { + it( 'should show snap lines', () => { + const state = snap( undefined, { + type: 'SHOW_SNAP_LINES', + } ); + + expect( state.showSnapLines ).toBe( true ); + } ); + + it( 'should hide snap lines', () => { + const state = snap( undefined, { + type: 'HIDE_SNAP_LINES', + } ); + + expect( state.showSnapLines ).toBe( false ); + } ); + + it( 'should add snap lines', () => { + const state = snap( undefined, { + type: 'SET_SNAP_LINES', + items: [ [ [ 0, 0 ], [ 100, 100 ] ] ], + } ); + + expect( state.snapLines ).toHaveLength( 1 ); + } ); + + it( 'should clear snap lines', () => { + const original = { snapLines: [ 1, 2, 3 ] }; + const state = snap( original, { + type: 'CLEAR_SNAP_LINES', + } ); + + expect( state.snapLines ).toHaveLength( 0 ); + } ); + } ); } ); diff --git a/package.json b/package.json index 0c7bdb25181..a3f33986d3c 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@wordpress/eslint-plugin": "2.4.0", "@wordpress/hooks": "2.5.0", "@wordpress/i18n": "3.6.0", + "@wordpress/is-shallow-equal": "1.5.0", "@wordpress/keycodes": "2.5.0", "@wordpress/plugins": "2.5.0", "@wordpress/postcss-themes": "2.2.0", From 042c0f568f6b03b73bd66422f094a16abb460585 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 21 Aug 2019 21:09:35 +0200 Subject: [PATCH 02/80] First pass at snapping while dragging --- .../components/block-mover/draggable.js | 128 ++++++++++++++++-- assets/src/stories-editor/constants.js | 1 + 2 files changed, 120 insertions(+), 9 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 321d192a298..0a88a682fe6 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -1,5 +1,5 @@ /** - * This file is based on the core's Component. + * This file is based on core's Component. **/ /** @@ -12,15 +12,20 @@ import PropTypes from 'prop-types'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { withSafeTimeout } from '@wordpress/compose'; +import { compose, withSafeTimeout } from '@wordpress/compose'; /** * Internal dependencies */ -import { getPixelsFromPercentage } from '../../helpers'; +import withSnapTargets from '../higher-order/with-snap-targets'; +import { + getPixelsFromPercentage, + findClosestSnap, +} from '../../helpers'; import { STORY_PAGE_INNER_WIDTH, STORY_PAGE_INNER_HEIGHT, + BLOCK_DRAGGING_SNAP_GAP, } from '../../constants'; const { Image } = window; @@ -67,12 +72,18 @@ class Draggable extends Component { * @param {Object} event The non-custom DragEvent. */ onDragEnd( event ) { - const { onDragEnd = noop } = this.props; + const { onDragEnd = noop, snapLines, hideSnapLines, clearSnapLines } = this.props; if ( event ) { event.preventDefault(); } this.resetDragState(); + + hideSnapLines(); + if ( snapLines.length ) { + clearSnapLines(); + } + this.props.setTimeout( onDragEnd ); } @@ -82,7 +93,79 @@ class Draggable extends Component { * @param {Object} event The non-custom DragEvent. */ onDragOver( event ) { - const top = parseInt( this.cloneWrapper.style.top ) + event.clientY - this.cursorTop; + const { hasSnapLine, setSnapLines, horizontalSnaps, verticalSnaps } = this.props; + + const newSnapLines = []; + + let top = parseInt( this.cloneWrapper.style.top ) + event.clientY - this.cursorTop; + let left = parseInt( this.cloneWrapper.style.left ) + event.clientX - this.cursorLeft; + const originalTop = top; + const originalLeft = left; + const width = this.cloneWrapper.clientWidth; + const height = this.cloneWrapper.clientHeight; + const horizontalCenter = left + ( width / 2 ); + const verticalCenter = top + ( height / 2 ); + + const horizontalLeftSnap = findClosestSnap( left, horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const horizontalRightSnap = findClosestSnap( left + width, horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const horizontalCenterSnap = findClosestSnap( horizontalCenter, horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const verticalTopSnap = findClosestSnap( top, verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const verticalBottomSnap = findClosestSnap( top + height, verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const verticalCenterSnap = findClosestSnap( verticalCenter, verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + + if ( horizontalLeftSnap !== left ) { + const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; + if ( ! hasSnapLine( snapLine ) ) { + newSnapLines.push( snapLine ); + + left = horizontalLeftSnap; + } + } + + if ( horizontalRightSnap !== left + width ) { + const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; + if ( ! hasSnapLine( snapLine ) ) { + newSnapLines.push( snapLine ); + + left = horizontalRightSnap - width; + } + } + + if ( horizontalCenterSnap !== horizontalCenter ) { + const snapLine = [ [ horizontalCenterSnap, 0 ], [ horizontalCenterSnap, STORY_PAGE_INNER_HEIGHT ] ]; + if ( ! hasSnapLine( snapLine ) ) { + newSnapLines.push( snapLine ); + + left = originalLeft - ( horizontalCenter - horizontalCenterSnap ); + } + } + + if ( verticalTopSnap !== top ) { + const snapLine = [ [ 0, verticalTopSnap ], [ STORY_PAGE_INNER_WIDTH, verticalTopSnap ] ]; + if ( ! hasSnapLine( snapLine ) ) { + newSnapLines.push( snapLine ); + + top = verticalTopSnap; + } + } + + if ( verticalBottomSnap !== top + height ) { + const snapLine = [ [ 0, verticalBottomSnap ], [ STORY_PAGE_INNER_WIDTH, verticalBottomSnap ] ]; + if ( ! hasSnapLine( snapLine ) ) { + newSnapLines.push( snapLine ); + + top = verticalBottomSnap - height; + } + } + + if ( verticalCenterSnap !== verticalCenter ) { + const snapLine = [ [ 0, verticalCenterSnap ], [ STORY_PAGE_INNER_WIDTH, verticalCenterSnap ] ]; + if ( ! hasSnapLine( snapLine ) ) { + newSnapLines.push( snapLine ); + + top = originalTop - ( verticalCenter - verticalCenterSnap ); + } + } // Don't allow the CTA button to go over its top limit. if ( 'amp/amp-story-cta' === this.props.blockName ) { @@ -91,12 +174,15 @@ class Draggable extends Component { this.cloneWrapper.style.top = `${ top }px`; } - this.cloneWrapper.style.left = - `${ parseInt( this.cloneWrapper.style.left ) + event.clientX - this.cursorLeft }px`; + this.cloneWrapper.style.left = `${ left }px`; // Update cursor coordinates. this.cursorLeft = event.clientX; this.cursorTop = event.clientY; + + if ( newSnapLines.length ) { + setSnapLines( ...newSnapLines ); + } } onDrop( ) { @@ -115,7 +201,7 @@ class Draggable extends Component { * @param {Object} transferData The data to be set to the event's dataTransfer - to be accessible in any later drop logic. */ onDragStart( event ) { - const { blockName, elementId, transferData, onDragStart = noop } = this.props; + const { blockName, elementId, transferData, onDragStart = noop, snapLines, showSnapLines, clearSnapLines } = this.props; const element = document.getElementById( elementId ); const isCTABlock = 'amp/amp-story-cta' === blockName; const parentPage = element.closest( 'div[data-type="amp/amp-story-page"]' ); @@ -184,6 +270,11 @@ class Draggable extends Component { document.addEventListener( 'drop', this.onDrop ); } + showSnapLines(); + if ( snapLines.length ) { + clearSnapLines(); + } + this.props.setTimeout( onDragStart ); } @@ -226,6 +317,25 @@ Draggable.propTypes = { onDragEnd: PropTypes.func, setTimeout: PropTypes.func.isRequired, children: PropTypes.any.isRequired, + horizontalSnaps: PropTypes.oneOfType( [ + PropTypes.arrayOf( PropTypes.number ), + PropTypes.func, + ] ).isRequired, + verticalSnaps: PropTypes.oneOfType( [ + PropTypes.arrayOf( PropTypes.number ), + PropTypes.func, + ] ).isRequired, + snapLines: PropTypes.array.isRequired, + showSnapLines: PropTypes.func.isRequired, + hideSnapLines: PropTypes.func.isRequired, + setSnapLines: PropTypes.func.isRequired, + clearSnapLines: PropTypes.func.isRequired, + hasSnapLine: PropTypes.func.isRequired, }; -export default withSafeTimeout( Draggable ); +const enhance = compose( + withSnapTargets, + withSafeTimeout, +); + +export default enhance( Draggable ); diff --git a/assets/src/stories-editor/constants.js b/assets/src/stories-editor/constants.js index 7794b4ead23..860fbdd8222 100644 --- a/assets/src/stories-editor/constants.js +++ b/assets/src/stories-editor/constants.js @@ -255,3 +255,4 @@ export const BLOCK_ROTATION_SNAPS = [ -180, -165, -150, -135, -120, -105, -90, - export const BLOCK_ROTATION_SNAP_GAP = 10; export const BLOCK_RESIZING_SNAP_GAP = 8; +export const BLOCK_DRAGGING_SNAP_GAP = 8; From 477b326bb559bed991524babf8c67283e4d953a6 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 22 Aug 2019 17:09:55 +0200 Subject: [PATCH 03/80] =?UTF-8?q?Bail=20early=20if=20we=20haven=E2=80=99t?= =?UTF-8?q?=20changed=20position?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/block-mover/draggable.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 0a88a682fe6..b3bfe132f5b 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -35,6 +35,9 @@ const cloneWrapperClass = 'components-draggable__clone'; const isChromeUA = ( ) => /Chrome/i.test( window.navigator.userAgent ); const documentHasIframes = ( ) => [ ...document.getElementById( 'editor' ).querySelectorAll( 'iframe' ) ].length > 0; +let lastX; +let lastY; + class Draggable extends Component { constructor( ...args ) { super( ...args ); @@ -99,6 +102,14 @@ class Draggable extends Component { let top = parseInt( this.cloneWrapper.style.top ) + event.clientY - this.cursorTop; let left = parseInt( this.cloneWrapper.style.left ) + event.clientX - this.cursorLeft; + + if ( top === lastY && left === lastX ) { + return; + } + + lastY = top; + lastX = left; + const originalTop = top; const originalLeft = left; const width = this.cloneWrapper.clientWidth; From e2a193df121420c3bf32826a8042f8cd13bedcf5 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 22 Aug 2019 17:18:19 +0200 Subject: [PATCH 04/80] Fix drag snaps --- .../stories-editor/components/block-mover/draggable.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index b3bfe132f5b..e16ded03770 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -124,7 +124,7 @@ class Draggable extends Component { const verticalBottomSnap = findClosestSnap( top + height, verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); const verticalCenterSnap = findClosestSnap( verticalCenter, verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - if ( horizontalLeftSnap !== left ) { + if ( horizontalLeftSnap !== originalLeft ) { const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); @@ -133,8 +133,8 @@ class Draggable extends Component { } } - if ( horizontalRightSnap !== left + width ) { - const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; + if ( horizontalRightSnap !== originalLeft + width ) { + const snapLine = [ [ horizontalRightSnap, 0 ], [ horizontalRightSnap, STORY_PAGE_INNER_HEIGHT ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); @@ -151,7 +151,7 @@ class Draggable extends Component { } } - if ( verticalTopSnap !== top ) { + if ( verticalTopSnap !== originalTop ) { const snapLine = [ [ 0, verticalTopSnap ], [ STORY_PAGE_INNER_WIDTH, verticalTopSnap ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); @@ -160,7 +160,7 @@ class Draggable extends Component { } } - if ( verticalBottomSnap !== top + height ) { + if ( verticalBottomSnap !== originalTop + height ) { const snapLine = [ [ 0, verticalBottomSnap ], [ STORY_PAGE_INNER_WIDTH, verticalBottomSnap ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); From 095fc853a1cccddc1b980843d6d5706e4082044d Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 22 Aug 2019 18:04:30 +0200 Subject: [PATCH 05/80] Ensure clientId is passed down --- .../src/stories-editor/components/block-mover/block-draggable.js | 1 + assets/src/stories-editor/components/block-mover/draggable.js | 1 + 2 files changed, 2 insertions(+) diff --git a/assets/src/stories-editor/components/block-mover/block-draggable.js b/assets/src/stories-editor/components/block-mover/block-draggable.js index f1c25b6f863..5beaa5fe2b5 100644 --- a/assets/src/stories-editor/components/block-mover/block-draggable.js +++ b/assets/src/stories-editor/components/block-mover/block-draggable.js @@ -32,6 +32,7 @@ const BlockDraggable = ( { children, clientId, blockName, rootClientId, blockEle transferData={ transferData } onDragStart={ onDragStart } onDragEnd={ onDragEnd } + clientId={ clientId } > { ( { onDraggableStart, onDraggableEnd } ) => { diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index e16ded03770..25d43b2e543 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -321,6 +321,7 @@ class Draggable extends Component { } Draggable.propTypes = { + clientId: PropTypes.string.isRequired, blockName: PropTypes.string, elementId: PropTypes.string, transferData: PropTypes.object, From 11c0f6e0cb7a9f823f4142c35345fda7b5b74a8d Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 22 Aug 2019 18:25:44 +0200 Subject: [PATCH 06/80] Rename var --- assets/src/stories-editor/store/test/actions.js | 2 +- assets/src/stories-editor/store/test/reducer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/src/stories-editor/store/test/actions.js b/assets/src/stories-editor/store/test/actions.js index 3ac282dd626..2713bd834bc 100644 --- a/assets/src/stories-editor/store/test/actions.js +++ b/assets/src/stories-editor/store/test/actions.js @@ -143,7 +143,7 @@ describe( 'actions', () => { expect( result ).toStrictEqual( { type: 'SET_SNAP_LINES', - items: [ 1, 2, 3 ], + snapLines: [ 1, 2, 3 ], } ); } ); } ); diff --git a/assets/src/stories-editor/store/test/reducer.js b/assets/src/stories-editor/store/test/reducer.js index 2eb1a0ca45f..43ea06c08c6 100644 --- a/assets/src/stories-editor/store/test/reducer.js +++ b/assets/src/stories-editor/store/test/reducer.js @@ -194,7 +194,7 @@ describe( 'reducers', () => { it( 'should add snap lines', () => { const state = snap( undefined, { type: 'SET_SNAP_LINES', - items: [ [ [ 0, 0 ], [ 100, 100 ] ] ], + snapLines: [ [ [ 0, 0 ], [ 100, 100 ] ] ], } ); expect( state.snapLines ).toHaveLength( 1 ); From 0bbaf1aae59163c99739b460be860e5847b16058 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 23 Aug 2019 13:54:53 +0200 Subject: [PATCH 07/80] Take block rotation into account when building list of snap targets --- .../components/block-mover/draggable.js | 10 ++- .../higher-order/with-snap-targets.js | 71 ++++++++++++------- assets/src/stories-editor/helpers/index.js | 24 +++++++ 3 files changed, 75 insertions(+), 30 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 25d43b2e543..64a00c6015d 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -107,9 +107,6 @@ class Draggable extends Component { return; } - lastY = top; - lastX = left; - const originalTop = top; const originalLeft = left; const width = this.cloneWrapper.clientWidth; @@ -178,6 +175,13 @@ class Draggable extends Component { } } + if ( top === lastY && left === lastX ) { + return; + } + + lastY = top; + lastX = left; + // Don't allow the CTA button to go over its top limit. if ( 'amp/amp-story-cta' === this.props.blockName ) { this.cloneWrapper.style.top = top >= 0 ? `${ top }px` : '0px'; diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index 6a64c76a8d6..5ed9eef3dd8 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -9,31 +9,39 @@ import isShallowEqual from '@wordpress/is-shallow-equal'; * Internal dependencies */ import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; -import { getPixelsFromPercentage } from '../../helpers'; +import { getBlockInnerElement } from '../../helpers'; -const applyWithSelect = withSelect( ( select, { clientId, angle } ) => { +const applyWithSelect = withSelect( ( select, { clientId } ) => { const { getBlocksByClientId, getBlockRootClientId, + getBlock, getBlockOrder, } = select( 'core/block-editor' ); - const { getSnapLines } = select( 'amp/story' ); + const { getCurrentPage, getSnapLines } = select( 'amp/story' ); const parentBlock = getBlockRootClientId( clientId ); + + if ( getCurrentPage() !== parentBlock ) { + return { + horizontalSnaps: [], + verticalSnaps: [], + snapLines: [], + hasSnapLine: () => false, + }; + } + + const parentBlockElement = getBlockInnerElement( getBlock( parentBlock ) ); + const { left: parentBLockOffsetLeft = 0, top: parentBlockOffsetTop = 0 } = parentBlockElement ? parentBlockElement.getBoundingClientRect() : {}; + const siblings = getBlocksByClientId( getBlockOrder( parentBlock ) ) - .filter( ( { clientId: blockId } ) => blockId !== clientId ) - .filter( ( { attributes } ) => ! attributes.rotationAngle ); + .filter( ( { clientId: blockId } ) => blockId !== clientId ); const snapLines = getSnapLines(); const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); return { horizontalSnaps: () => { - // @todo: Support snapping for a rotated block that is being resized. - if ( angle ) { - return []; - } - const pageSnaps = [ 0, STORY_PAGE_INNER_WIDTH / 2, @@ -41,16 +49,23 @@ const applyWithSelect = withSelect( ( select, { clientId, angle } ) => { ]; const blockSnaps = siblings - .map( ( { attributes } ) => { - const { positionLeft, width } = attributes; - const positionInPx = getPixelsFromPercentage( 'x', positionLeft ); - return [ positionInPx, positionInPx + width ]; + .map( ( block ) => { + const blockElement = getBlockInnerElement( block ); + + if ( ! blockElement ) { + return []; + } + + const { left, right } = blockElement.getBoundingClientRect(); + return [ left - parentBLockOffsetLeft, right - parentBLockOffsetLeft ]; } ) .reduce( ( result, snaps ) => { for ( const snap of snaps ) { - if ( ! result.includes( snap ) && ! pageSnaps.includes( snap ) ) { - result.push( snap ); + if ( snap < 0 || snap > STORY_PAGE_INNER_WIDTH || result.includes( snap ) || pageSnaps.includes( snap ) ) { + continue; } + + result.push( snap ); } return result; @@ -59,11 +74,6 @@ const applyWithSelect = withSelect( ( select, { clientId, angle } ) => { return [ ...pageSnaps, ...blockSnaps ]; }, verticalSnaps: () => { - // @todo: Support snapping for a rotated block that is being resized. - if ( angle ) { - return []; - } - const pageSnaps = [ 0, STORY_PAGE_INNER_HEIGHT / 2, @@ -71,16 +81,23 @@ const applyWithSelect = withSelect( ( select, { clientId, angle } ) => { ]; const blockSnaps = siblings - .map( ( { attributes } ) => { - const { positionTop, height } = attributes; - const positionInPx = getPixelsFromPercentage( 'y', positionTop ); - return [ positionInPx, positionInPx + height ]; + .map( ( block ) => { + const blockElement = getBlockInnerElement( block ); + + if ( ! blockElement ) { + return []; + } + + const { top, bottom } = blockElement.getBoundingClientRect(); + return [ top - parentBlockOffsetTop, bottom - parentBlockOffsetTop ]; } ) .reduce( ( result, snaps ) => { for ( const snap of snaps ) { - if ( ! result.includes( snap ) && ! pageSnaps.includes( snap ) ) { - result.push( snap ); + if ( snap < 0 || snap > STORY_PAGE_INNER_HEIGHT || result.includes( snap ) || pageSnaps.includes( snap ) ) { + continue; } + + result.push( snap ); } return result; diff --git a/assets/src/stories-editor/helpers/index.js b/assets/src/stories-editor/helpers/index.js index 84e1780227a..2124b78c805 100644 --- a/assets/src/stories-editor/helpers/index.js +++ b/assets/src/stories-editor/helpers/index.js @@ -1159,6 +1159,30 @@ const getBlockInnerTextElement = ( block ) => { } }; +/** + * Returns a movable block's inner element. + * + * @param {Object} block Block object. + * + * @return {null|Element} The inner element. + */ +export const getBlockInnerElement = ( block ) => { + const { name, clientId } = block; + const isPage = 'amp/amp-story-page' === name; + const isCTABlock = 'amp/amp-story-cta' === name; + + if ( isPage ) { + return document.querySelector( `[data-block="${ clientId }"]` ); + } + + if ( isCTABlock ) { + // Not the block itself is movable, only the button within. + return document.querySelector( `amp-story-cta-button-${ clientId }` ); + } + + return document.querySelector( `#block-${ clientId }` ); +}; + /** * Updates a block's font size in case it uses amp-fit-text and the content has changed. * From 5cab0d58c1415104ac7ef40c147440bec6ccd04a Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 23 Aug 2019 14:19:00 +0200 Subject: [PATCH 08/80] Add todos --- assets/src/stories-editor/components/block-mover/draggable.js | 2 ++ assets/src/stories-editor/components/resizable-box/index.js | 3 +++ 2 files changed, 5 insertions(+) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 64a00c6015d..3af0cd649f9 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -114,6 +114,8 @@ class Draggable extends Component { const horizontalCenter = left + ( width / 2 ); const verticalCenter = top + ( height / 2 ); + // @todo Fix slowness. + const horizontalLeftSnap = findClosestSnap( left, horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); const horizontalRightSnap = findClosestSnap( left + width, horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); const horizontalCenterSnap = findClosestSnap( horizontalCenter, horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 724b4fd00b0..bf56ff1ad74 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -329,6 +329,9 @@ class EnhancedResizableBox extends Component { const originalPos = getResizedBlockPosition( direction, blockElementLeft, blockElementTop, lastDeltaW, lastDeltaH ); const updatedPos = getUpdatedBlockPosition( direction, originalPos, diff ); + // @todo: Get outermost left position and calculate snap. + // @todo: Get outermost top position and calculate snap. + blockElement.style.left = getPercentageFromPixels( 'x', updatedPos.left ) + '%'; blockElement.style.top = getPercentageFromPixels( 'y', updatedPos.top ) + '%'; } From c0132df9dcf83dc43f93d6d24153fa3d9fa8bf01 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 23 Aug 2019 14:22:47 +0200 Subject: [PATCH 09/80] Try clearing snap lines if there are old ones but not new ones --- .../src/stories-editor/components/block-mover/draggable.js | 6 ++++-- assets/src/stories-editor/components/resizable-box/index.js | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 3af0cd649f9..2b3eb8680e5 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -95,8 +95,8 @@ class Draggable extends Component { * * @param {Object} event The non-custom DragEvent. */ - onDragOver( event ) { - const { hasSnapLine, setSnapLines, horizontalSnaps, verticalSnaps } = this.props; + onDragOver( event ) { // eslint-disable-line complexity + const { snapLines, clearSnapLines, hasSnapLine, setSnapLines, horizontalSnaps, verticalSnaps } = this.props; const newSnapLines = []; @@ -199,6 +199,8 @@ class Draggable extends Component { if ( newSnapLines.length ) { setSnapLines( ...newSnapLines ); + } else if ( snapLines.length ) { + clearSnapLines(); } } diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index bf56ff1ad74..d84fa3b0660 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -358,6 +358,8 @@ class EnhancedResizableBox extends Component { if ( newSnapLines.length ) { setSnapLines( ...newSnapLines ); + } else if ( snapLines.length ) { + clearSnapLines(); } } } > From d5b994710fcab6c221b160c4cc110922f81c0f22 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 23 Aug 2019 16:21:11 +0200 Subject: [PATCH 10/80] Performance improvements for dragging --- .../components/block-mover/draggable.js | 93 ++++++++++++------- .../higher-order/with-snap-targets.js | 12 +-- .../components/resizable-box/index.js | 4 +- assets/src/stories-editor/store/reducer.js | 11 ++- 4 files changed, 76 insertions(+), 44 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 2b3eb8680e5..8716dd7c09e 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -13,6 +13,7 @@ import PropTypes from 'prop-types'; */ import { Component } from '@wordpress/element'; import { compose, withSafeTimeout } from '@wordpress/compose'; +import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies @@ -96,7 +97,11 @@ class Draggable extends Component { * @param {Object} event The non-custom DragEvent. */ onDragOver( event ) { // eslint-disable-line complexity - const { snapLines, clearSnapLines, hasSnapLine, setSnapLines, horizontalSnaps, verticalSnaps } = this.props; + const { snapLines, clearSnapLines, setSnapLines, setTimeout, clearTimeout } = this.props; + + const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); + + clearTimeout(); const newSnapLines = []; @@ -107,74 +112,76 @@ class Draggable extends Component { return; } - const originalTop = top; - const originalLeft = left; const width = this.cloneWrapper.clientWidth; const height = this.cloneWrapper.clientHeight; - const horizontalCenter = left + ( width / 2 ); - const verticalCenter = top + ( height / 2 ); + const originalTop = top; + const originalLeft = left; + const originalBottom = originalTop + height; + const originalRight = originalLeft + width; + const horizontalCenter = originalLeft + ( width / 2 ); + const verticalCenter = originalTop + ( height / 2 ); // @todo Fix slowness. - const horizontalLeftSnap = findClosestSnap( left, horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const horizontalRightSnap = findClosestSnap( left + width, horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const horizontalCenterSnap = findClosestSnap( horizontalCenter, horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const verticalTopSnap = findClosestSnap( top, verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const verticalBottomSnap = findClosestSnap( top + height, verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const verticalCenterSnap = findClosestSnap( verticalCenter, verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const horizontalLeftSnap = findClosestSnap( originalLeft, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const horizontalRightSnap = findClosestSnap( originalLeft + width, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const horizontalCenterSnap = findClosestSnap( horizontalCenter, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const verticalTopSnap = findClosestSnap( originalTop, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const verticalBottomSnap = findClosestSnap( originalTop + height, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const verticalCenterSnap = findClosestSnap( verticalCenter, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); if ( horizontalLeftSnap !== originalLeft ) { const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); - - left = horizontalLeftSnap; } + + left = horizontalLeftSnap; } - if ( horizontalRightSnap !== originalLeft + width ) { + if ( horizontalRightSnap !== originalRight ) { const snapLine = [ [ horizontalRightSnap, 0 ], [ horizontalRightSnap, STORY_PAGE_INNER_HEIGHT ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); - - left = horizontalRightSnap - width; } + + left = horizontalRightSnap - width; } if ( horizontalCenterSnap !== horizontalCenter ) { const snapLine = [ [ horizontalCenterSnap, 0 ], [ horizontalCenterSnap, STORY_PAGE_INNER_HEIGHT ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); - - left = originalLeft - ( horizontalCenter - horizontalCenterSnap ); } + + left = originalLeft - ( horizontalCenter - horizontalCenterSnap ); } if ( verticalTopSnap !== originalTop ) { const snapLine = [ [ 0, verticalTopSnap ], [ STORY_PAGE_INNER_WIDTH, verticalTopSnap ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); - - top = verticalTopSnap; } + + top = verticalTopSnap; } - if ( verticalBottomSnap !== originalTop + height ) { + if ( verticalBottomSnap !== originalBottom ) { const snapLine = [ [ 0, verticalBottomSnap ], [ STORY_PAGE_INNER_WIDTH, verticalBottomSnap ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); - - top = verticalBottomSnap - height; } + + top = verticalBottomSnap - height; } if ( verticalCenterSnap !== verticalCenter ) { const snapLine = [ [ 0, verticalCenterSnap ], [ STORY_PAGE_INNER_WIDTH, verticalCenterSnap ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); - - top = originalTop - ( verticalCenter - verticalCenterSnap ); } + + top = originalTop - ( verticalCenter - verticalCenterSnap ); } if ( top === lastY && left === lastX ) { @@ -197,11 +204,20 @@ class Draggable extends Component { this.cursorLeft = event.clientX; this.cursorTop = event.clientY; - if ( newSnapLines.length ) { - setSnapLines( ...newSnapLines ); - } else if ( snapLines.length ) { - clearSnapLines(); - } + clearTimeout( this.timeoutId ); + this.timeoutId = setTimeout( () => { + if ( snapLines.length === newSnapLines.length ) { + if ( newSnapLines.every( hasSnapLine ) ) { + return; + } + } + + if ( newSnapLines.length ) { + setSnapLines( ...newSnapLines ); + } else if ( snapLines.length ) { + clearSnapLines(); + } + }, 50 ); } onDrop( ) { @@ -220,7 +236,17 @@ class Draggable extends Component { * @param {Object} transferData The data to be set to the event's dataTransfer - to be accessible in any later drop logic. */ onDragStart( event ) { - const { blockName, elementId, transferData, onDragStart = noop, snapLines, showSnapLines, clearSnapLines } = this.props; + const { + blockName, + elementId, + transferData, + onDragStart = noop, + snapLines, + showSnapLines, + clearSnapLines, + horizontalSnaps, + verticalSnaps, + } = this.props; const element = document.getElementById( elementId ); const isCTABlock = 'amp/amp-story-cta' === blockName; const parentPage = element.closest( 'div[data-type="amp/amp-story-page"]' ); @@ -294,6 +320,9 @@ class Draggable extends Component { clearSnapLines(); } + this.horizontalSnaps = horizontalSnaps(); + this.verticalSnaps = verticalSnaps(); + this.props.setTimeout( onDragStart ); } @@ -335,6 +364,7 @@ Draggable.propTypes = { transferData: PropTypes.object, onDragStart: PropTypes.func, onDragEnd: PropTypes.func, + clearTimeout: PropTypes.func.isRequired, setTimeout: PropTypes.func.isRequired, children: PropTypes.any.isRequired, horizontalSnaps: PropTypes.oneOfType( [ @@ -350,7 +380,6 @@ Draggable.propTypes = { hideSnapLines: PropTypes.func.isRequired, setSnapLines: PropTypes.func.isRequired, clearSnapLines: PropTypes.func.isRequired, - hasSnapLine: PropTypes.func.isRequired, }; const enhance = compose( diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index 5ed9eef3dd8..2e4450cf7b7 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -3,7 +3,6 @@ */ import { withDispatch, withSelect } from '@wordpress/data'; import { compose, createHigherOrderComponent } from '@wordpress/compose'; -import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies @@ -27,7 +26,6 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { horizontalSnaps: [], verticalSnaps: [], snapLines: [], - hasSnapLine: () => false, }; } @@ -37,9 +35,6 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { const siblings = getBlocksByClientId( getBlockOrder( parentBlock ) ) .filter( ( { clientId: blockId } ) => blockId !== clientId ); - const snapLines = getSnapLines(); - const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); - return { horizontalSnaps: () => { const pageSnaps = [ @@ -57,7 +52,7 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { } const { left, right } = blockElement.getBoundingClientRect(); - return [ left - parentBLockOffsetLeft, right - parentBLockOffsetLeft ]; + return [ Math.round( left - parentBLockOffsetLeft ), Math.round( right - parentBLockOffsetLeft ) ]; } ) .reduce( ( result, snaps ) => { for ( const snap of snaps ) { @@ -89,7 +84,7 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { } const { top, bottom } = blockElement.getBoundingClientRect(); - return [ top - parentBlockOffsetTop, bottom - parentBlockOffsetTop ]; + return [ Math.round( top - parentBlockOffsetTop ), Math.round( bottom - parentBlockOffsetTop ) ]; } ) .reduce( ( result, snaps ) => { for ( const snap of snaps ) { @@ -105,8 +100,7 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { return [ ...pageSnaps, ...blockSnaps ]; }, - snapLines, - hasSnapLine, + snapLines: getSnapLines(), }; } ); diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index d84fa3b0660..6d2bc275635 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -9,6 +9,7 @@ import PropTypes from 'prop-types'; */ import { Component } from '@wordpress/element'; import { ResizableBox } from '@wordpress/components'; +import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies @@ -92,7 +93,6 @@ class EnhancedResizableBox extends Component { hideSnapLines, setSnapLines, clearSnapLines, - hasSnapLine, ...childProps } = otherProps; @@ -185,6 +185,7 @@ class EnhancedResizableBox extends Component { } } onResize={ ( event, direction, element ) => { // eslint-disable-line complexity const newSnapLines = []; + const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); const { deltaW, deltaH } = getResizedWidthAndHeight( event, angle, lastSeenX, lastSeenY, direction ); @@ -399,7 +400,6 @@ EnhancedResizableBox.propTypes = { hideSnapLines: PropTypes.func.isRequired, setSnapLines: PropTypes.func.isRequired, clearSnapLines: PropTypes.func.isRequired, - hasSnapLine: PropTypes.func.isRequired, }; export default withSnapTargets( EnhancedResizableBox ); diff --git a/assets/src/stories-editor/store/reducer.js b/assets/src/stories-editor/store/reducer.js index d0c5b334f8a..42f09c8e38f 100644 --- a/assets/src/stories-editor/store/reducer.js +++ b/assets/src/stories-editor/store/reducer.js @@ -7,6 +7,7 @@ import { combineReducers } from '@wordpress/data'; * Internal dependencies */ import { isValidAnimationPredecessor } from './selectors'; +import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Reducer handling animation state changes. @@ -182,7 +183,15 @@ export function snap( state = {}, action ) { }; case 'SET_SNAP_LINES': - const { snapLines } = action; + const { snapLines = [] } = action; + + if ( snapLines.length === state.snapLines.length ) { + const hasSnapLine = ( item ) => state.snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); + if ( snapLines.every( hasSnapLine ) ) { + return state; + } + } + return { ...state, snapLines: [ ...snapLines ], From d7ad7d70fa159e7e0ee2dd0b39786e4ef3f5301a Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 23 Aug 2019 17:14:19 +0200 Subject: [PATCH 11/80] Hide resize and rotation handles when dragging --- .../src/stories-editor/components/block-mover/edit.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/assets/src/stories-editor/components/block-mover/edit.css b/assets/src/stories-editor/components/block-mover/edit.css index 18b665b632b..bc6e3603513 100644 --- a/assets/src/stories-editor/components/block-mover/edit.css +++ b/assets/src/stories-editor/components/block-mover/edit.css @@ -34,3 +34,12 @@ .wp-block[data-type="core/quote"] .editor-rich-text__editable:hover { cursor: default; } + +.components-draggable__clone .components-resizable-box__handle, +.components-draggable__clone .rotatable-box-wrap::before, +.components-draggable__clone .rotatable-box-wrap__handle { + display: none; + opacity: 0; + visibility: hidden; + content: none; +} From 28fb06d5e0f659e8d4ecbb9694660288facb0cb5 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 23 Aug 2019 17:25:54 +0200 Subject: [PATCH 12/80] Use getBoundingClientRect when dragging a rotated block --- .../components/block-mover/draggable.js | 66 +++++++++++-------- .../higher-order/with-snap-targets.js | 8 ++- .../components/resizable-box/index.js | 4 ++ 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 8716dd7c09e..24434590fec 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -97,7 +97,15 @@ class Draggable extends Component { * @param {Object} event The non-custom DragEvent. */ onDragOver( event ) { // eslint-disable-line complexity - const { snapLines, clearSnapLines, setSnapLines, setTimeout, clearTimeout } = this.props; + const { + snapLines, + clearSnapLines, + setSnapLines, + setTimeout, + clearTimeout, + parentBlockOffsetTop, + parentBlockOffsetLeft, + } = this.props; const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); @@ -105,47 +113,51 @@ class Draggable extends Component { const newSnapLines = []; - let top = parseInt( this.cloneWrapper.style.top ) + event.clientY - this.cursorTop; - let left = parseInt( this.cloneWrapper.style.left ) + event.clientX - this.cursorLeft; + const top = parseInt( this.cloneWrapper.style.top ) + event.clientY - this.cursorTop; + const left = parseInt( this.cloneWrapper.style.left ) + event.clientX - this.cursorLeft; if ( top === lastY && left === lastX ) { return; } - const width = this.cloneWrapper.clientWidth; - const height = this.cloneWrapper.clientHeight; - const originalTop = top; - const originalLeft = left; - const originalBottom = originalTop + height; - const originalRight = originalLeft + width; - const horizontalCenter = originalLeft + ( width / 2 ); - const verticalCenter = originalTop + ( height / 2 ); + const originalTop = top; // eslint-disable-line no-unused-vars + const originalLeft = left; // eslint-disable-line no-unused-vars + + // We calculate with the block's actual dimensions relative to the page it's on. + let { top: actualTop, right: actualRight, bottom: actualBottom, left: actualLeft } = this.cloneWrapper.getBoundingClientRect(); + actualTop -= parentBlockOffsetTop; + actualRight -= parentBlockOffsetLeft; + actualBottom -= parentBlockOffsetTop; + actualLeft -= parentBlockOffsetLeft; - // @todo Fix slowness. + const horizontalCenter = actualLeft + ( ( actualRight - actualLeft ) / 2 ); + const verticalCenter = actualTop + ( ( actualBottom - actualTop ) / 2 ); - const horizontalLeftSnap = findClosestSnap( originalLeft, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const horizontalRightSnap = findClosestSnap( originalLeft + width, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const horizontalLeftSnap = findClosestSnap( actualLeft, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const horizontalRightSnap = findClosestSnap( actualRight, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); const horizontalCenterSnap = findClosestSnap( horizontalCenter, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const verticalTopSnap = findClosestSnap( originalTop, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const verticalBottomSnap = findClosestSnap( originalTop + height, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const verticalTopSnap = findClosestSnap( actualTop, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const verticalBottomSnap = findClosestSnap( actualBottom, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); const verticalCenterSnap = findClosestSnap( verticalCenter, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - if ( horizontalLeftSnap !== originalLeft ) { + // @todo: Fix top / left calculation when snapping. + + if ( horizontalLeftSnap !== actualLeft ) { const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); } - left = horizontalLeftSnap; + //left = originalLeft - ( actualLeft - horizontalLeftSnap ); } - if ( horizontalRightSnap !== originalRight ) { + if ( horizontalRightSnap !== actualRight ) { const snapLine = [ [ horizontalRightSnap, 0 ], [ horizontalRightSnap, STORY_PAGE_INNER_HEIGHT ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); } - left = horizontalRightSnap - width; + //left = originalLeft - ( actualRight - horizontalRightSnap ); } if ( horizontalCenterSnap !== horizontalCenter ) { @@ -154,25 +166,25 @@ class Draggable extends Component { newSnapLines.push( snapLine ); } - left = originalLeft - ( horizontalCenter - horizontalCenterSnap ); + //left = originalLeft - ( horizontalCenter - horizontalCenterSnap ); } - if ( verticalTopSnap !== originalTop ) { + if ( verticalTopSnap !== actualTop ) { const snapLine = [ [ 0, verticalTopSnap ], [ STORY_PAGE_INNER_WIDTH, verticalTopSnap ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); } - top = verticalTopSnap; + //top = originalTop - ( verticalTopSnap - actualTop ); } - if ( verticalBottomSnap !== originalBottom ) { + if ( verticalBottomSnap !== actualBottom ) { const snapLine = [ [ 0, verticalBottomSnap ], [ STORY_PAGE_INNER_WIDTH, verticalBottomSnap ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); } - top = verticalBottomSnap - height; + //top = originalTop - ( verticalBottomSnap - actualBottom ); } if ( verticalCenterSnap !== verticalCenter ) { @@ -181,7 +193,7 @@ class Draggable extends Component { newSnapLines.push( snapLine ); } - top = originalTop - ( verticalCenter - verticalCenterSnap ); + //top = originalTop - ( verticalCenter - verticalCenterSnap ); } if ( top === lastY && left === lastX ) { @@ -380,6 +392,8 @@ Draggable.propTypes = { hideSnapLines: PropTypes.func.isRequired, setSnapLines: PropTypes.func.isRequired, clearSnapLines: PropTypes.func.isRequired, + parentBlockOffsetTop: PropTypes.number.isRequired, + parentBlockOffsetLeft: PropTypes.number.isRequired, }; const enhance = compose( diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index 2e4450cf7b7..1aa2d736ccd 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -26,11 +26,13 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { horizontalSnaps: [], verticalSnaps: [], snapLines: [], + parentBlockOffsetTop: 0, + parentBlockOffsetLeft: 0, }; } const parentBlockElement = getBlockInnerElement( getBlock( parentBlock ) ); - const { left: parentBLockOffsetLeft = 0, top: parentBlockOffsetTop = 0 } = parentBlockElement ? parentBlockElement.getBoundingClientRect() : {}; + const { left: parentBlockOffsetLeft = 0, top: parentBlockOffsetTop = 0 } = parentBlockElement ? parentBlockElement.getBoundingClientRect() : {}; const siblings = getBlocksByClientId( getBlockOrder( parentBlock ) ) .filter( ( { clientId: blockId } ) => blockId !== clientId ); @@ -52,7 +54,7 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { } const { left, right } = blockElement.getBoundingClientRect(); - return [ Math.round( left - parentBLockOffsetLeft ), Math.round( right - parentBLockOffsetLeft ) ]; + return [ Math.round( left - parentBlockOffsetLeft ), Math.round( right - parentBlockOffsetLeft ) ]; } ) .reduce( ( result, snaps ) => { for ( const snap of snaps ) { @@ -101,6 +103,8 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { return [ ...pageSnaps, ...blockSnaps ]; }, snapLines: getSnapLines(), + parentBlockOffsetTop, + parentBlockOffsetLeft, }; } ); diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 6d2bc275635..7fc4502aa3f 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -93,6 +93,8 @@ class EnhancedResizableBox extends Component { hideSnapLines, setSnapLines, clearSnapLines, + parentBlockOffsetTop, + parentBlockOffsetLeft, ...childProps } = otherProps; @@ -400,6 +402,8 @@ EnhancedResizableBox.propTypes = { hideSnapLines: PropTypes.func.isRequired, setSnapLines: PropTypes.func.isRequired, clearSnapLines: PropTypes.func.isRequired, + parentBlockOffsetTop: PropTypes.number.isRequired, + parentBlockOffsetLeft: PropTypes.number.isRequired, }; export default withSnapTargets( EnhancedResizableBox ); From 0c1dd96d45a95f5a3f2c472da21ea7e54f6bde60 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 26 Aug 2019 11:43:04 +0200 Subject: [PATCH 13/80] Check for existing snap lines in reducer --- assets/src/stories-editor/store/reducer.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/src/stories-editor/store/reducer.js b/assets/src/stories-editor/store/reducer.js index 42f09c8e38f..fc92b117bfe 100644 --- a/assets/src/stories-editor/store/reducer.js +++ b/assets/src/stories-editor/store/reducer.js @@ -183,10 +183,11 @@ export function snap( state = {}, action ) { }; case 'SET_SNAP_LINES': + const { snapLines: existingSnapLines = [] } = state; const { snapLines = [] } = action; - if ( snapLines.length === state.snapLines.length ) { - const hasSnapLine = ( item ) => state.snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); + if ( snapLines.length === existingSnapLines.length ) { + const hasSnapLine = ( item ) => existingSnapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); if ( snapLines.every( hasSnapLine ) ) { return state; } From 6ddfa881183a68f3a2f4311bf02e3019096056a4 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 26 Aug 2019 11:43:16 +0200 Subject: [PATCH 14/80] =?UTF-8?q?Return=20early=20when=20parent=20element?= =?UTF-8?q?=20wasn=E2=80=99t=20found?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../higher-order/with-snap-targets.js | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index 1aa2d736ccd..ce6029ddd6d 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -21,18 +21,25 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { const parentBlock = getBlockRootClientId( clientId ); + const defaultData = { + horizontalSnaps: [], + verticalSnaps: [], + snapLines: [], + parentBlockOffsetTop: 0, + parentBlockOffsetLeft: 0, + }; + if ( getCurrentPage() !== parentBlock ) { - return { - horizontalSnaps: [], - verticalSnaps: [], - snapLines: [], - parentBlockOffsetTop: 0, - parentBlockOffsetLeft: 0, - }; + return defaultData; } const parentBlockElement = getBlockInnerElement( getBlock( parentBlock ) ); - const { left: parentBlockOffsetLeft = 0, top: parentBlockOffsetTop = 0 } = parentBlockElement ? parentBlockElement.getBoundingClientRect() : {}; + + if ( ! parentBlockElement ) { + return defaultData; + } + + const { left: parentBlockOffsetLeft, top: parentBlockOffsetTop } = parentBlockElement.getBoundingClientRect(); const siblings = getBlocksByClientId( getBlockOrder( parentBlock ) ) .filter( ( { clientId: blockId } ) => blockId !== clientId ); From be6cf105f591a9678ecf218abbbbbf6b6fcc20c9 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 26 Aug 2019 13:05:41 +0200 Subject: [PATCH 15/80] Update return description --- assets/src/stories-editor/helpers/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/src/stories-editor/helpers/index.js b/assets/src/stories-editor/helpers/index.js index 2124b78c805..3da998c8c88 100644 --- a/assets/src/stories-editor/helpers/index.js +++ b/assets/src/stories-editor/helpers/index.js @@ -1631,7 +1631,7 @@ export const maybeAddMissingAnchor = ( clientId ) => { * @param {number} number Given number. * @param {Array|function} snap List of snap targets or function that provides them. * @param {number} snapGap Minimum gap required in order to move to the next snapping target - * @return {number} New angle. + * @return {number} Snap target if found, or the given number. */ export const findClosestSnap = memize( ( number, snap, snapGap ) => { const snapArray = typeof snap === 'function' ? snap( number ) : snap; From 06324370d9384353e085574436b891f4b2661ea1 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 26 Aug 2019 16:04:19 +0200 Subject: [PATCH 16/80] Update snapping position while dragging rotated blocks Also disables snapping when Alt key is pressed --- .../components/block-mover/draggable.js | 51 +++++++++++++++---- .../components/rotatable-box/index.js | 6 ++- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 24434590fec..538bce0128c 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -113,8 +113,8 @@ class Draggable extends Component { const newSnapLines = []; - const top = parseInt( this.cloneWrapper.style.top ) + event.clientY - this.cursorTop; - const left = parseInt( this.cloneWrapper.style.left ) + event.clientX - this.cursorLeft; + let top = parseInt( this.cloneWrapper.style.top ) + event.clientY - this.cursorTop; + let left = parseInt( this.cloneWrapper.style.left ) + event.clientX - this.cursorLeft; if ( top === lastY && left === lastX ) { return; @@ -123,16 +123,33 @@ class Draggable extends Component { const originalTop = top; // eslint-disable-line no-unused-vars const originalLeft = left; // eslint-disable-line no-unused-vars + const dimensions = this.cloneWrapper.getBoundingClientRect(); + // We calculate with the block's actual dimensions relative to the page it's on. - let { top: actualTop, right: actualRight, bottom: actualBottom, left: actualLeft } = this.cloneWrapper.getBoundingClientRect(); + let { + top: actualTop, + right: actualRight, + bottom: actualBottom, + left: actualLeft, + } = dimensions; + actualTop -= parentBlockOffsetTop; actualRight -= parentBlockOffsetLeft; actualBottom -= parentBlockOffsetTop; actualLeft -= parentBlockOffsetLeft; + const { + width: actualWidth, + height: actualHeight, + } = dimensions; + const horizontalCenter = actualLeft + ( ( actualRight - actualLeft ) / 2 ); const verticalCenter = actualTop + ( ( actualBottom - actualTop ) / 2 ); + // The difference in width/height caused by rotation. + const rotatedWidthDiff = ( this.cloneWrapper.offsetWidth - actualWidth ) / 2; + const rotatedHeightDiff = ( this.cloneWrapper.offsetHeight - actualHeight ) / 2; + const horizontalLeftSnap = findClosestSnap( actualLeft, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); const horizontalRightSnap = findClosestSnap( actualRight, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); const horizontalCenterSnap = findClosestSnap( horizontalCenter, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); @@ -140,7 +157,7 @@ class Draggable extends Component { const verticalBottomSnap = findClosestSnap( actualBottom, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); const verticalCenterSnap = findClosestSnap( verticalCenter, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - // @todo: Fix top / left calculation when snapping. + const snappingEnabled = ! event.getModifierState( 'Alt' ); if ( horizontalLeftSnap !== actualLeft ) { const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; @@ -148,7 +165,9 @@ class Draggable extends Component { newSnapLines.push( snapLine ); } - //left = originalLeft - ( actualLeft - horizontalLeftSnap ); + if ( snappingEnabled ) { + left = horizontalLeftSnap - rotatedWidthDiff; + } } if ( horizontalRightSnap !== actualRight ) { @@ -157,7 +176,9 @@ class Draggable extends Component { newSnapLines.push( snapLine ); } - //left = originalLeft - ( actualRight - horizontalRightSnap ); + if ( snappingEnabled ) { + left = originalLeft - ( actualRight - horizontalRightSnap ); + } } if ( horizontalCenterSnap !== horizontalCenter ) { @@ -166,7 +187,10 @@ class Draggable extends Component { newSnapLines.push( snapLine ); } - //left = originalLeft - ( horizontalCenter - horizontalCenterSnap ); + if ( snappingEnabled ) { + // @todo Fix calculation. + left = horizontalCenterSnap - ( ( actualWidth / 2 ) - rotatedWidthDiff ); + } } if ( verticalTopSnap !== actualTop ) { @@ -175,7 +199,9 @@ class Draggable extends Component { newSnapLines.push( snapLine ); } - //top = originalTop - ( verticalTopSnap - actualTop ); + if ( snappingEnabled ) { + top = verticalTopSnap - rotatedHeightDiff; + } } if ( verticalBottomSnap !== actualBottom ) { @@ -184,7 +210,9 @@ class Draggable extends Component { newSnapLines.push( snapLine ); } - //top = originalTop - ( verticalBottomSnap - actualBottom ); + if ( snappingEnabled ) { + top = originalTop - ( actualBottom - verticalBottomSnap ); + } } if ( verticalCenterSnap !== verticalCenter ) { @@ -193,7 +221,10 @@ class Draggable extends Component { newSnapLines.push( snapLine ); } - //top = originalTop - ( verticalCenter - verticalCenterSnap ); + if ( snappingEnabled ) { + // @todo Fix calculation. + top = verticalCenterSnap - ( ( actualHeight / 2 ) - rotatedHeightDiff ); + } } if ( top === lastY && left === lastX ) { diff --git a/assets/src/stories-editor/components/rotatable-box/index.js b/assets/src/stories-editor/components/rotatable-box/index.js index 79c02140d21..d9120353869 100644 --- a/assets/src/stories-editor/components/rotatable-box/index.js +++ b/assets/src/stories-editor/components/rotatable-box/index.js @@ -128,7 +128,11 @@ class RotatableBox extends Component { const rad2deg = ( 180 / Math.PI ); let angle = Math.ceil( -( rad2deg * Math.atan2( x, y ) ) ); - angle = findClosestSnap( angle, this.props.snap, this.props.snapGap ); + const snappingEnabled = ! e.getModifierState( 'Alt' ); + + if ( snappingEnabled ) { + angle = findClosestSnap( angle, this.props.snap, this.props.snapGap ); + } if ( this.state.angle === angle ) { return; From 693b7bec2e89fa06f48bbaff7d4ea38e14e6c273 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 26 Aug 2019 16:40:38 +0200 Subject: [PATCH 17/80] Allow disabling snapping when dragging --- .../components/resizable-box/index.js | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 7fc4502aa3f..d3efc243038 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -240,6 +240,8 @@ class EnhancedResizableBox extends Component { lastDeltaW = deltaW; } + const snappingEnabled = ! event.getModifierState( 'Alt' ); + if ( ! angle ) { // If the resizing is to left or top then we have to compensate if ( REVERSE_WIDTH_CALCULATIONS.includes( direction ) ) { @@ -257,8 +259,10 @@ class EnhancedResizableBox extends Component { newSnapLines.push( leftSnapLine ); } - appliedWidth += leftInPx - leftSnap; - leftInPx = leftSnap; + if ( snappingEnabled ) { + appliedWidth += leftInPx - leftSnap; + leftInPx = leftSnap; + } } } @@ -272,7 +276,9 @@ class EnhancedResizableBox extends Component { newSnapLines.push( widthSnapLine ); } - appliedWidth = widthSnap - blockElement.offsetLeft; + if ( snappingEnabled ) { + appliedWidth = widthSnap - blockElement.offsetLeft; + } } } @@ -292,8 +298,10 @@ class EnhancedResizableBox extends Component { newSnapLines.push( topSnapLine ); } - appliedHeight += topInPx - topSnap; - topInPx = topSnap; + if ( snappingEnabled ) { + appliedHeight += topInPx - topSnap; + topInPx = topSnap; + } } } @@ -306,9 +314,11 @@ class EnhancedResizableBox extends Component { if ( ! hasSnapLine( heightSnapLine ) ) { newSnapLines.push( heightSnapLine ); } - } - appliedHeight = heightSnap - blockElement.offsetTop; + if ( snappingEnabled ) { + appliedHeight = heightSnap - blockElement.offsetTop; + } + } } } else { const radianAngle = getRadianFromDeg( angle ); From 7f788242303875e9bf79995efeef83ef2673baf2 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 26 Aug 2019 16:50:56 +0200 Subject: [PATCH 18/80] Fix center calculation --- .../src/stories-editor/components/block-mover/draggable.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 538bce0128c..8f5009d9569 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -188,8 +188,7 @@ class Draggable extends Component { } if ( snappingEnabled ) { - // @todo Fix calculation. - left = horizontalCenterSnap - ( ( actualWidth / 2 ) - rotatedWidthDiff ); + left = originalLeft - ( horizontalCenter - horizontalCenterSnap ); } } @@ -222,8 +221,7 @@ class Draggable extends Component { } if ( snappingEnabled ) { - // @todo Fix calculation. - top = verticalCenterSnap - ( ( actualHeight / 2 ) - rotatedHeightDiff ); + top = originalTop - ( verticalCenter - verticalCenterSnap ); } } From 1cca639a2aee8957c8fa6fc2d8c3a3a900badeea Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 26 Aug 2019 16:58:52 +0200 Subject: [PATCH 19/80] Add todo comment --- assets/src/stories-editor/components/block-mover/draggable.js | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 8f5009d9569..f240401c2df 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -225,6 +225,7 @@ class Draggable extends Component { } } + // @todo Prevent block from being stuck in snapping position. if ( top === lastY && left === lastX ) { return; } From 329ff3244fbc57614f2ad4c42f2bf2b6423c4cec Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 27 Aug 2019 09:46:21 +0200 Subject: [PATCH 20/80] Make leftSnap const --- assets/src/stories-editor/components/resizable-box/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index d3efc243038..395900e981a 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -246,12 +246,11 @@ class EnhancedResizableBox extends Component { // If the resizing is to left or top then we have to compensate if ( REVERSE_WIDTH_CALCULATIONS.includes( direction ) ) { let leftInPx = getPixelsFromPercentage( 'x', parseFloat( blockElementLeft ) ); - let leftSnap = 0; leftInPx = leftInPx - lastDeltaW; if ( lastDeltaW ) { - leftSnap = findClosestSnap( leftInPx, horizontalSnaps, snapGap ); + const leftSnap = findClosestSnap( leftInPx, horizontalSnaps, snapGap ); if ( leftSnap !== leftInPx ) { const leftSnapLine = [ [ leftSnap, 0 ], [ leftSnap, STORY_PAGE_INNER_HEIGHT ] ]; From 416e26f6f8281be08f895180e3eb6b070d02eafb Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 27 Aug 2019 09:59:49 +0200 Subject: [PATCH 21/80] Add additional resizing snap targets Example use case: if the block is 200px from the left, it should allow snapping to 200px from the right side as well. --- .../components/resizable-box/index.js | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 395900e981a..510dd864406 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -8,6 +8,8 @@ import PropTypes from 'prop-types'; * WordPress dependencies */ import { Component } from '@wordpress/element'; +import { withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; import { ResizableBox } from '@wordpress/components'; import isShallowEqual from '@wordpress/is-shallow-equal'; @@ -20,6 +22,7 @@ import { getPercentageFromPixels, getPixelsFromPercentage, findClosestSnap, + getBlockInnerElement, } from '../../helpers'; import { getBlockPositioning, @@ -415,4 +418,64 @@ EnhancedResizableBox.propTypes = { parentBlockOffsetLeft: PropTypes.number.isRequired, }; -export default withSnapTargets( EnhancedResizableBox ); +const withResizingSnapTargets = withSelect( ( select, ownProps ) => { + const { getBlock } = select( 'core/block-editor' ); + + const { + clientId, + horizontalSnaps, + verticalSnaps, + parentBlockOffsetTop, + parentBlockOffsetLeft, + } = ownProps; + + return { + horizontalSnaps: () => { + const blockInnerElement = getBlockInnerElement( getBlock( clientId ) ); + + if ( ! blockInnerElement ) { + return horizontalSnaps(); + } + + const dimensions = blockInnerElement.getBoundingClientRect(); + + // We calculate with the block's actual dimensions relative to the page it's on. + let { right: actualRight, left: actualLeft } = dimensions; + + actualRight -= parentBlockOffsetLeft; + actualLeft -= parentBlockOffsetLeft; + + return [ + ...horizontalSnaps(), + ...[ STORY_PAGE_INNER_WIDTH - actualLeft, STORY_PAGE_INNER_WIDTH - actualRight ], + ]; + }, + verticalSnaps: () => { + const blockInnerElement = getBlockInnerElement( getBlock( clientId ) ); + + if ( ! blockInnerElement ) { + return horizontalSnaps(); + } + + const dimensions = blockInnerElement.getBoundingClientRect(); + + // We calculate with the block's actual dimensions relative to the page it's on. + let { top: actualTop, bottom: actualBottom } = dimensions; + + actualTop -= parentBlockOffsetTop; + actualBottom -= parentBlockOffsetTop; + + return [ + ...verticalSnaps(), + ...[ STORY_PAGE_INNER_HEIGHT - actualTop, STORY_PAGE_INNER_HEIGHT - actualBottom ], + ]; + }, + }; +} ); + +const enhance = compose( + withSnapTargets, + withResizingSnapTargets, +); + +export default enhance( EnhancedResizableBox ); From 78ba5928b7957ce4222cc3af5210b805437944ff Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 27 Aug 2019 10:36:53 +0200 Subject: [PATCH 22/80] Add some docs --- .../components/block-mover/draggable.js | 2 ++ .../components/higher-order/with-snap-targets.js | 13 +++++++++++++ .../components/resizable-box/index.js | 7 +++++++ 3 files changed, 22 insertions(+) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index f240401c2df..a2e366d7886 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -159,6 +159,8 @@ class Draggable extends Component { const snappingEnabled = ! event.getModifierState( 'Alt' ); + // @todo: Rely on withSnapTargets to provide the data for the snapping lines so this isn't a concern of this component. + if ( horizontalLeftSnap !== actualLeft ) { const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; if ( ! hasSnapLine( snapLine ) ) { diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index ce6029ddd6d..5d8cb13ce3e 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -10,6 +10,19 @@ import { compose, createHigherOrderComponent } from '@wordpress/compose'; import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; import { getBlockInnerElement } from '../../helpers'; +/** + * Higher-order component that returns snap targets for the current block. + * + * Snap targets are divided into horizontal and vertical ones, and are based on the following data: + * + * - The page's width / height constraints and center. + * - The block's position in relation to its siblings. + * + * @todo Update horizontalSnaps() and verticalSnaps() to return a map of snap targets -> snap lines + * in order to allow showing multiple snap lines for a single snapping point that can differ from the actual target. + * + * @type {Component} + */ const applyWithSelect = withSelect( ( select, { clientId } ) => { const { getBlocksByClientId, diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 510dd864406..334e985dc24 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -418,6 +418,13 @@ EnhancedResizableBox.propTypes = { parentBlockOffsetLeft: PropTypes.number.isRequired, }; +/** + * Enhances the default snap targets specifically for resizing. + * + * This allows resizing a block so that it can be nicely centered. + * + * @type {Component} + */ const withResizingSnapTargets = withSelect( ( select, ownProps ) => { const { getBlock } = select( 'core/block-editor' ); From 2d1c5a7394da7e675231ac3c6c9cd3bbb30f1b64 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 27 Aug 2019 10:37:05 +0200 Subject: [PATCH 23/80] Make resize snapping a bit more robust --- .../components/resizable-box/index.js | 64 +++++++++---------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 334e985dc24..2cb63081e7e 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -186,11 +186,13 @@ class EnhancedResizableBox extends Component { clearSnapLines(); } + this.horizontalSnaps = horizontalSnaps(); + this.verticalSnaps = verticalSnaps(); + onResizeStart(); } } onResize={ ( event, direction, element ) => { // eslint-disable-line complexity const newSnapLines = []; - const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); const { deltaW, deltaH } = getResizedWidthAndHeight( event, angle, lastSeenX, lastSeenY, direction ); @@ -253,72 +255,62 @@ class EnhancedResizableBox extends Component { leftInPx = leftInPx - lastDeltaW; if ( lastDeltaW ) { - const leftSnap = findClosestSnap( leftInPx, horizontalSnaps, snapGap ); + const horizontalLeftSnap = findClosestSnap( leftInPx, this.horizontalSnaps, snapGap ); - if ( leftSnap !== leftInPx ) { - const leftSnapLine = [ [ leftSnap, 0 ], [ leftSnap, STORY_PAGE_INNER_HEIGHT ] ]; - if ( ! hasSnapLine( leftSnapLine ) ) { - newSnapLines.push( leftSnapLine ); - } + if ( this.horizontalSnaps.includes( horizontalLeftSnap ) ) { + const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; + newSnapLines.push( snapLine ); if ( snappingEnabled ) { - appliedWidth += leftInPx - leftSnap; - leftInPx = leftSnap; + appliedWidth += leftInPx - horizontalLeftSnap; + leftInPx = horizontalLeftSnap; } } } blockElement.style.left = getPercentageFromPixels( 'x', leftInPx ) + '%'; } else if ( lastDeltaW ) { - const widthSnap = findClosestSnap( blockElement.offsetLeft + appliedWidth, horizontalSnaps, snapGap ); + const horizontalRightSnap = findClosestSnap( blockElement.offsetLeft + appliedWidth, this.horizontalSnaps, snapGap ); - if ( ( widthSnap - blockElement.offsetLeft ) !== appliedWidth ) { - const widthSnapLine = [ [ widthSnap, 0 ], [ widthSnap, STORY_PAGE_INNER_HEIGHT ] ]; - if ( ! hasSnapLine( widthSnapLine ) ) { - newSnapLines.push( widthSnapLine ); - } + if ( this.horizontalSnaps.includes( horizontalRightSnap ) ) { + const snapLine = [ [ horizontalRightSnap, 0 ], [ horizontalRightSnap, STORY_PAGE_INNER_HEIGHT ] ]; + newSnapLines.push( snapLine ); if ( snappingEnabled ) { - appliedWidth = widthSnap - blockElement.offsetLeft; + appliedWidth = horizontalRightSnap - blockElement.offsetLeft; } } } if ( REVERSE_HEIGHT_CALCULATIONS.includes( direction ) ) { let topInPx = getPixelsFromPercentage( 'y', parseFloat( blockElementTop ) ); - let topSnap = 0; topInPx = topInPx - lastDeltaH; if ( lastDeltaH ) { - topSnap = findClosestSnap( topInPx, verticalSnaps, snapGap ); - - if ( topSnap !== topInPx ) { - const topSnapLine = [ [ 0, topSnap ], [ STORY_PAGE_INNER_WIDTH, topSnap ] ]; + const verticalTopSnap = findClosestSnap( topInPx, this.verticalSnaps, snapGap ); - if ( ! hasSnapLine( topSnapLine ) ) { - newSnapLines.push( topSnapLine ); - } + if ( this.verticalSnaps.includes( verticalTopSnap ) ) { + const snapLine = [ [ 0, verticalTopSnap ], [ STORY_PAGE_INNER_WIDTH, verticalTopSnap ] ]; + newSnapLines.push( snapLine ); if ( snappingEnabled ) { - appliedHeight += topInPx - topSnap; - topInPx = topSnap; + appliedHeight += topInPx - verticalTopSnap; + topInPx = verticalTopSnap; } } } blockElement.style.top = getPercentageFromPixels( 'y', topInPx ) + '%'; } else if ( lastDeltaH ) { - const heightSnap = findClosestSnap( blockElement.offsetTop + appliedHeight, verticalSnaps, snapGap ); + const verticalBottomSnap = findClosestSnap( blockElement.offsetTop + appliedHeight, this.verticalSnaps, snapGap ); - if ( ( heightSnap - blockElement.offsetTop ) !== appliedHeight ) { - const heightSnapLine = [ [ 0, heightSnap ], [ STORY_PAGE_INNER_WIDTH, heightSnap ] ]; - if ( ! hasSnapLine( heightSnapLine ) ) { - newSnapLines.push( heightSnapLine ); - } + if ( this.verticalSnaps.includes( verticalBottomSnap ) ) { + const snapLine = [ [ 0, verticalBottomSnap ], [ STORY_PAGE_INNER_WIDTH, verticalBottomSnap ] ]; + newSnapLines.push( snapLine ); if ( snappingEnabled ) { - appliedHeight = heightSnap - blockElement.offsetTop; + appliedHeight = verticalBottomSnap - blockElement.offsetTop; } } } @@ -371,8 +363,12 @@ class EnhancedResizableBox extends Component { imageWrapper.style.height = appliedHeight + 'px'; } + const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); + if ( newSnapLines.length ) { - setSnapLines( ...newSnapLines ); + if ( ! newSnapLines.every( hasSnapLine ) ) { + setSnapLines( ...newSnapLines ); + } } else if ( snapLines.length ) { clearSnapLines(); } From 9e16d06e36067bf1f59d98b723075d8d8e0a4083 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 27 Aug 2019 11:02:34 +0200 Subject: [PATCH 24/80] Fix snap checks when dragging --- .../components/block-mover/draggable.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index a2e366d7886..7b1cdf2390a 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -161,7 +161,7 @@ class Draggable extends Component { // @todo: Rely on withSnapTargets to provide the data for the snapping lines so this isn't a concern of this component. - if ( horizontalLeftSnap !== actualLeft ) { + if ( this.horizontalSnaps.includes( horizontalLeftSnap ) ) { const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); @@ -172,7 +172,7 @@ class Draggable extends Component { } } - if ( horizontalRightSnap !== actualRight ) { + if ( this.horizontalSnaps.includes( horizontalRightSnap ) ) { const snapLine = [ [ horizontalRightSnap, 0 ], [ horizontalRightSnap, STORY_PAGE_INNER_HEIGHT ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); @@ -183,7 +183,7 @@ class Draggable extends Component { } } - if ( horizontalCenterSnap !== horizontalCenter ) { + if ( this.horizontalSnaps.includes( horizontalCenterSnap ) ) { const snapLine = [ [ horizontalCenterSnap, 0 ], [ horizontalCenterSnap, STORY_PAGE_INNER_HEIGHT ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); @@ -194,7 +194,7 @@ class Draggable extends Component { } } - if ( verticalTopSnap !== actualTop ) { + if ( this.verticalSnaps.includes( verticalTopSnap ) ) { const snapLine = [ [ 0, verticalTopSnap ], [ STORY_PAGE_INNER_WIDTH, verticalTopSnap ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); @@ -205,7 +205,7 @@ class Draggable extends Component { } } - if ( verticalBottomSnap !== actualBottom ) { + if ( this.verticalSnaps.includes( verticalBottomSnap ) ) { const snapLine = [ [ 0, verticalBottomSnap ], [ STORY_PAGE_INNER_WIDTH, verticalBottomSnap ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); @@ -216,7 +216,7 @@ class Draggable extends Component { } } - if ( verticalCenterSnap !== verticalCenter ) { + if ( this.verticalSnaps.includes( verticalCenterSnap ) ) { const snapLine = [ [ 0, verticalCenterSnap ], [ STORY_PAGE_INNER_WIDTH, verticalCenterSnap ] ]; if ( ! hasSnapLine( snapLine ) ) { newSnapLines.push( snapLine ); From 929554c92880f9d9a87e991123a4e80d9bffbfd9 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 27 Aug 2019 11:34:14 +0200 Subject: [PATCH 25/80] Simplify drag-snapping a big --- .../components/block-mover/draggable.js | 62 +++++-------------- 1 file changed, 17 insertions(+), 45 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 7b1cdf2390a..b42636a3e9b 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -101,27 +101,24 @@ class Draggable extends Component { snapLines, clearSnapLines, setSnapLines, - setTimeout, - clearTimeout, parentBlockOffsetTop, parentBlockOffsetLeft, } = this.props; - const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); - - clearTimeout(); - const newSnapLines = []; let top = parseInt( this.cloneWrapper.style.top ) + event.clientY - this.cursorTop; let left = parseInt( this.cloneWrapper.style.left ) + event.clientX - this.cursorLeft; + const originalTop = top; + const originalLeft = left; + if ( top === lastY && left === lastX ) { return; } - const originalTop = top; // eslint-disable-line no-unused-vars - const originalLeft = left; // eslint-disable-line no-unused-vars + lastY = top; + lastX = left; const dimensions = this.cloneWrapper.getBoundingClientRect(); @@ -163,9 +160,7 @@ class Draggable extends Component { if ( this.horizontalSnaps.includes( horizontalLeftSnap ) ) { const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; - if ( ! hasSnapLine( snapLine ) ) { - newSnapLines.push( snapLine ); - } + newSnapLines.push( snapLine ); if ( snappingEnabled ) { left = horizontalLeftSnap - rotatedWidthDiff; @@ -174,9 +169,7 @@ class Draggable extends Component { if ( this.horizontalSnaps.includes( horizontalRightSnap ) ) { const snapLine = [ [ horizontalRightSnap, 0 ], [ horizontalRightSnap, STORY_PAGE_INNER_HEIGHT ] ]; - if ( ! hasSnapLine( snapLine ) ) { - newSnapLines.push( snapLine ); - } + newSnapLines.push( snapLine ); if ( snappingEnabled ) { left = originalLeft - ( actualRight - horizontalRightSnap ); @@ -185,9 +178,7 @@ class Draggable extends Component { if ( this.horizontalSnaps.includes( horizontalCenterSnap ) ) { const snapLine = [ [ horizontalCenterSnap, 0 ], [ horizontalCenterSnap, STORY_PAGE_INNER_HEIGHT ] ]; - if ( ! hasSnapLine( snapLine ) ) { - newSnapLines.push( snapLine ); - } + newSnapLines.push( snapLine ); if ( snappingEnabled ) { left = originalLeft - ( horizontalCenter - horizontalCenterSnap ); @@ -196,9 +187,7 @@ class Draggable extends Component { if ( this.verticalSnaps.includes( verticalTopSnap ) ) { const snapLine = [ [ 0, verticalTopSnap ], [ STORY_PAGE_INNER_WIDTH, verticalTopSnap ] ]; - if ( ! hasSnapLine( snapLine ) ) { - newSnapLines.push( snapLine ); - } + newSnapLines.push( snapLine ); if ( snappingEnabled ) { top = verticalTopSnap - rotatedHeightDiff; @@ -207,9 +196,7 @@ class Draggable extends Component { if ( this.verticalSnaps.includes( verticalBottomSnap ) ) { const snapLine = [ [ 0, verticalBottomSnap ], [ STORY_PAGE_INNER_WIDTH, verticalBottomSnap ] ]; - if ( ! hasSnapLine( snapLine ) ) { - newSnapLines.push( snapLine ); - } + newSnapLines.push( snapLine ); if ( snappingEnabled ) { top = originalTop - ( actualBottom - verticalBottomSnap ); @@ -218,23 +205,13 @@ class Draggable extends Component { if ( this.verticalSnaps.includes( verticalCenterSnap ) ) { const snapLine = [ [ 0, verticalCenterSnap ], [ STORY_PAGE_INNER_WIDTH, verticalCenterSnap ] ]; - if ( ! hasSnapLine( snapLine ) ) { - newSnapLines.push( snapLine ); - } + newSnapLines.push( snapLine ); if ( snappingEnabled ) { top = originalTop - ( verticalCenter - verticalCenterSnap ); } } - // @todo Prevent block from being stuck in snapping position. - if ( top === lastY && left === lastX ) { - return; - } - - lastY = top; - lastX = left; - // Don't allow the CTA button to go over its top limit. if ( 'amp/amp-story-cta' === this.props.blockName ) { this.cloneWrapper.style.top = top >= 0 ? `${ top }px` : '0px'; @@ -248,20 +225,15 @@ class Draggable extends Component { this.cursorLeft = event.clientX; this.cursorTop = event.clientY; - clearTimeout( this.timeoutId ); - this.timeoutId = setTimeout( () => { - if ( snapLines.length === newSnapLines.length ) { - if ( newSnapLines.every( hasSnapLine ) ) { - return; - } - } + const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); - if ( newSnapLines.length ) { + if ( newSnapLines.length ) { + if ( ! newSnapLines.every( hasSnapLine ) ) { setSnapLines( ...newSnapLines ); - } else if ( snapLines.length ) { - clearSnapLines(); } - }, 50 ); + } else if ( snapLines.length ) { + clearSnapLines(); + } } onDrop( ) { From 9d681915d6b5ef5e2640cea33bc95f73b127561e Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Tue, 27 Aug 2019 15:03:44 +0200 Subject: [PATCH 26/80] Fix getting stuck with left position --- .../components/block-mover/draggable.js | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index b42636a3e9b..894f24cf666 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -38,6 +38,10 @@ const documentHasIframes = ( ) => [ ...document.getElementById( 'editor' ).query let lastX; let lastY; +let originalX; +let originalY; +let initialBlockX; +let initialBlockY; class Draggable extends Component { constructor( ...args ) { @@ -158,12 +162,19 @@ class Draggable extends Component { // @todo: Rely on withSnapTargets to provide the data for the snapping lines so this isn't a concern of this component. + // What the cursor has moved since the beginning. + const leftDiff = event.clientX - originalX; + // Where the original block would be positioned based on that. + const leftToCompareWith = initialBlockX + leftDiff; + if ( this.horizontalSnaps.includes( horizontalLeftSnap ) ) { const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; newSnapLines.push( snapLine ); if ( snappingEnabled ) { - left = horizontalLeftSnap - rotatedWidthDiff; + if ( BLOCK_DRAGGING_SNAP_GAP > Math.abs( leftToCompareWith - horizontalLeftSnap ) ) { + left = horizontalLeftSnap - rotatedWidthDiff; + } } } @@ -172,7 +183,9 @@ class Draggable extends Component { newSnapLines.push( snapLine ); if ( snappingEnabled ) { - left = originalLeft - ( actualRight - horizontalRightSnap ); + if ( BLOCK_DRAGGING_SNAP_GAP > Math.abs( leftToCompareWith + actualWidth - horizontalRightSnap ) ) { + left = originalLeft - ( actualRight - horizontalRightSnap ); + } } } @@ -181,7 +194,9 @@ class Draggable extends Component { newSnapLines.push( snapLine ); if ( snappingEnabled ) { - left = originalLeft - ( horizontalCenter - horizontalCenterSnap ); + if ( BLOCK_DRAGGING_SNAP_GAP > Math.abs( leftToCompareWith + ( actualWidth / 2 ) - horizontalCenterSnap ) ) { + left = originalLeft - ( horizontalCenter - horizontalCenterSnap ); + } } } @@ -298,6 +313,7 @@ class Draggable extends Component { // 20% of the full value in case of CTA block. const baseHeight = isCTABlock ? STORY_PAGE_INNER_HEIGHT / 5 : STORY_PAGE_INNER_HEIGHT; + initialBlockX = getPixelsFromPercentage( 'x', parseInt( clone.style.left ), STORY_PAGE_INNER_WIDTH ); // Position clone over the original element. this.cloneWrapper.style.top = `${ getPixelsFromPercentage( 'y', parseInt( clone.style.top ), baseHeight ) }px`; this.cloneWrapper.style.left = `${ getPixelsFromPercentage( 'x', parseInt( clone.style.left ), STORY_PAGE_INNER_WIDTH ) }px`; @@ -316,6 +332,8 @@ class Draggable extends Component { // Mark the current cursor coordinates. this.cursorLeft = event.clientX; this.cursorTop = event.clientY; + originalX = event.clientX; + originalY = event.clientY; // Update cursor to 'grabbing', document wide. document.body.classList.add( 'is-dragging-components-draggable' ); document.addEventListener( 'dragover', this.onDragOver ); From 0350b01e6e9d5918c075955f192bdfa3f44b13e1 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Tue, 27 Aug 2019 15:08:28 +0200 Subject: [PATCH 27/80] Fix flickering for right horizontal snap. --- assets/src/stories-editor/components/block-mover/draggable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 894f24cf666..bfe206ec91c 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -184,7 +184,7 @@ class Draggable extends Component { if ( snappingEnabled ) { if ( BLOCK_DRAGGING_SNAP_GAP > Math.abs( leftToCompareWith + actualWidth - horizontalRightSnap ) ) { - left = originalLeft - ( actualRight - horizontalRightSnap ); + left = horizontalRightSnap - actualWidth; } } } From eb0f48c8dfed939a0d73ee37188b97100d28e928 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 27 Aug 2019 20:04:40 +0200 Subject: [PATCH 28/80] Apply fixes for snapping and flickering for vertical snaps as well --- .../components/block-mover/draggable.js | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index bfe206ec91c..1939e3f9e5d 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -164,15 +164,17 @@ class Draggable extends Component { // What the cursor has moved since the beginning. const leftDiff = event.clientX - originalX; + const topDiff = event.clientY - originalY; // Where the original block would be positioned based on that. const leftToCompareWith = initialBlockX + leftDiff; + const topToCompareWith = initialBlockY + topDiff; if ( this.horizontalSnaps.includes( horizontalLeftSnap ) ) { const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; newSnapLines.push( snapLine ); if ( snappingEnabled ) { - if ( BLOCK_DRAGGING_SNAP_GAP > Math.abs( leftToCompareWith - horizontalLeftSnap ) ) { + if ( Math.abs( leftToCompareWith - horizontalLeftSnap ) <= BLOCK_DRAGGING_SNAP_GAP ) { left = horizontalLeftSnap - rotatedWidthDiff; } } @@ -183,7 +185,7 @@ class Draggable extends Component { newSnapLines.push( snapLine ); if ( snappingEnabled ) { - if ( BLOCK_DRAGGING_SNAP_GAP > Math.abs( leftToCompareWith + actualWidth - horizontalRightSnap ) ) { + if ( Math.abs( leftToCompareWith + actualWidth - horizontalRightSnap ) <= BLOCK_DRAGGING_SNAP_GAP ) { left = horizontalRightSnap - actualWidth; } } @@ -194,7 +196,7 @@ class Draggable extends Component { newSnapLines.push( snapLine ); if ( snappingEnabled ) { - if ( BLOCK_DRAGGING_SNAP_GAP > Math.abs( leftToCompareWith + ( actualWidth / 2 ) - horizontalCenterSnap ) ) { + if ( Math.abs( leftToCompareWith + ( actualWidth / 2 ) - horizontalCenterSnap ) <= BLOCK_DRAGGING_SNAP_GAP ) { left = originalLeft - ( horizontalCenter - horizontalCenterSnap ); } } @@ -205,7 +207,9 @@ class Draggable extends Component { newSnapLines.push( snapLine ); if ( snappingEnabled ) { - top = verticalTopSnap - rotatedHeightDiff; + if ( Math.abs( topToCompareWith - verticalTopSnap ) <= BLOCK_DRAGGING_SNAP_GAP ) { + top = verticalTopSnap - rotatedHeightDiff; + } } } @@ -214,7 +218,9 @@ class Draggable extends Component { newSnapLines.push( snapLine ); if ( snappingEnabled ) { - top = originalTop - ( actualBottom - verticalBottomSnap ); + if ( Math.abs( topToCompareWith + actualHeight - verticalBottomSnap ) <= BLOCK_DRAGGING_SNAP_GAP ) { + top = originalTop - ( actualBottom - verticalBottomSnap ); + } } } @@ -223,7 +229,9 @@ class Draggable extends Component { newSnapLines.push( snapLine ); if ( snappingEnabled ) { - top = originalTop - ( verticalCenter - verticalCenterSnap ); + if ( Math.abs( topToCompareWith + ( actualHeight / 2 ) - verticalCenterSnap ) <= BLOCK_DRAGGING_SNAP_GAP ) { + top = originalTop - ( verticalCenter - verticalCenterSnap ); + } } } @@ -308,12 +316,14 @@ class Draggable extends Component { this.cloneWrapper.style.height = `${ element.clientHeight }px`; const clone = element.cloneNode( true ); + initialBlockX = getPixelsFromPercentage( 'x', parseInt( clone.style.left ), STORY_PAGE_INNER_WIDTH ); + initialBlockY = getPixelsFromPercentage( 'y', parseInt( clone.style.top ), STORY_PAGE_INNER_HEIGHT ); + this.cloneWrapper.style.transform = clone.style.transform; // 20% of the full value in case of CTA block. const baseHeight = isCTABlock ? STORY_PAGE_INNER_HEIGHT / 5 : STORY_PAGE_INNER_HEIGHT; - initialBlockX = getPixelsFromPercentage( 'x', parseInt( clone.style.left ), STORY_PAGE_INNER_WIDTH ); // Position clone over the original element. this.cloneWrapper.style.top = `${ getPixelsFromPercentage( 'y', parseInt( clone.style.top ), baseHeight ) }px`; this.cloneWrapper.style.left = `${ getPixelsFromPercentage( 'x', parseInt( clone.style.left ), STORY_PAGE_INNER_WIDTH ) }px`; From bb30343fb9b97fb6b2ee9a0eb657b9c939bed523 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 28 Aug 2019 12:49:44 +0200 Subject: [PATCH 29/80] Make findClosestSnap return null --- .../components/block-mover/draggable.js | 12 ++++++------ .../stories-editor/components/resizable-box/index.js | 8 ++++---- .../stories-editor/components/rotatable-box/index.js | 5 ++++- assets/src/stories-editor/helpers/index.js | 4 ++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 1939e3f9e5d..0e5be81ddc0 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -169,7 +169,7 @@ class Draggable extends Component { const leftToCompareWith = initialBlockX + leftDiff; const topToCompareWith = initialBlockY + topDiff; - if ( this.horizontalSnaps.includes( horizontalLeftSnap ) ) { + if ( horizontalLeftSnap !== null ) { const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; newSnapLines.push( snapLine ); @@ -180,7 +180,7 @@ class Draggable extends Component { } } - if ( this.horizontalSnaps.includes( horizontalRightSnap ) ) { + if ( horizontalRightSnap !== null ) { const snapLine = [ [ horizontalRightSnap, 0 ], [ horizontalRightSnap, STORY_PAGE_INNER_HEIGHT ] ]; newSnapLines.push( snapLine ); @@ -191,7 +191,7 @@ class Draggable extends Component { } } - if ( this.horizontalSnaps.includes( horizontalCenterSnap ) ) { + if ( horizontalCenterSnap !== null ) { const snapLine = [ [ horizontalCenterSnap, 0 ], [ horizontalCenterSnap, STORY_PAGE_INNER_HEIGHT ] ]; newSnapLines.push( snapLine ); @@ -202,7 +202,7 @@ class Draggable extends Component { } } - if ( this.verticalSnaps.includes( verticalTopSnap ) ) { + if ( verticalTopSnap !== null ) { const snapLine = [ [ 0, verticalTopSnap ], [ STORY_PAGE_INNER_WIDTH, verticalTopSnap ] ]; newSnapLines.push( snapLine ); @@ -213,7 +213,7 @@ class Draggable extends Component { } } - if ( this.verticalSnaps.includes( verticalBottomSnap ) ) { + if ( verticalBottomSnap !== null ) { const snapLine = [ [ 0, verticalBottomSnap ], [ STORY_PAGE_INNER_WIDTH, verticalBottomSnap ] ]; newSnapLines.push( snapLine ); @@ -224,7 +224,7 @@ class Draggable extends Component { } } - if ( this.verticalSnaps.includes( verticalCenterSnap ) ) { + if ( verticalCenterSnap !== null ) { const snapLine = [ [ 0, verticalCenterSnap ], [ STORY_PAGE_INNER_WIDTH, verticalCenterSnap ] ]; newSnapLines.push( snapLine ); diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 2cb63081e7e..30279b761d2 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -257,7 +257,7 @@ class EnhancedResizableBox extends Component { if ( lastDeltaW ) { const horizontalLeftSnap = findClosestSnap( leftInPx, this.horizontalSnaps, snapGap ); - if ( this.horizontalSnaps.includes( horizontalLeftSnap ) ) { + if ( horizontalLeftSnap !== null ) { const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; newSnapLines.push( snapLine ); @@ -272,7 +272,7 @@ class EnhancedResizableBox extends Component { } else if ( lastDeltaW ) { const horizontalRightSnap = findClosestSnap( blockElement.offsetLeft + appliedWidth, this.horizontalSnaps, snapGap ); - if ( this.horizontalSnaps.includes( horizontalRightSnap ) ) { + if ( horizontalRightSnap !== null ) { const snapLine = [ [ horizontalRightSnap, 0 ], [ horizontalRightSnap, STORY_PAGE_INNER_HEIGHT ] ]; newSnapLines.push( snapLine ); @@ -290,7 +290,7 @@ class EnhancedResizableBox extends Component { if ( lastDeltaH ) { const verticalTopSnap = findClosestSnap( topInPx, this.verticalSnaps, snapGap ); - if ( this.verticalSnaps.includes( verticalTopSnap ) ) { + if ( verticalTopSnap !== null ) { const snapLine = [ [ 0, verticalTopSnap ], [ STORY_PAGE_INNER_WIDTH, verticalTopSnap ] ]; newSnapLines.push( snapLine ); @@ -305,7 +305,7 @@ class EnhancedResizableBox extends Component { } else if ( lastDeltaH ) { const verticalBottomSnap = findClosestSnap( blockElement.offsetTop + appliedHeight, this.verticalSnaps, snapGap ); - if ( this.verticalSnaps.includes( verticalBottomSnap ) ) { + if ( verticalBottomSnap !== null ) { const snapLine = [ [ 0, verticalBottomSnap ], [ STORY_PAGE_INNER_WIDTH, verticalBottomSnap ] ]; newSnapLines.push( snapLine ); diff --git a/assets/src/stories-editor/components/rotatable-box/index.js b/assets/src/stories-editor/components/rotatable-box/index.js index d9120353869..dc089157143 100644 --- a/assets/src/stories-editor/components/rotatable-box/index.js +++ b/assets/src/stories-editor/components/rotatable-box/index.js @@ -131,7 +131,10 @@ class RotatableBox extends Component { const snappingEnabled = ! e.getModifierState( 'Alt' ); if ( snappingEnabled ) { - angle = findClosestSnap( angle, this.props.snap, this.props.snapGap ); + const angleSnap = findClosestSnap( angle, this.props.snap, this.props.snapGap ); + if ( angleSnap !== null ) { + angle = angleSnap; + } } if ( this.state.angle === angle ) { diff --git a/assets/src/stories-editor/helpers/index.js b/assets/src/stories-editor/helpers/index.js index 3da998c8c88..5c6dfd58b02 100644 --- a/assets/src/stories-editor/helpers/index.js +++ b/assets/src/stories-editor/helpers/index.js @@ -1631,7 +1631,7 @@ export const maybeAddMissingAnchor = ( clientId ) => { * @param {number} number Given number. * @param {Array|function} snap List of snap targets or function that provides them. * @param {number} snapGap Minimum gap required in order to move to the next snapping target - * @return {number} Snap target if found, or the given number. + * @return {?number} Snap target if found. */ export const findClosestSnap = memize( ( number, snap, snapGap ) => { const snapArray = typeof snap === 'function' ? snap( number ) : snap; @@ -1642,7 +1642,7 @@ export const findClosestSnap = memize( ( number, snap, snapGap ) => { ); const gap = Math.abs( snapArray[ closestGapIndex ] - number ); - return snapGap === 0 || gap < snapGap ? snapArray[ closestGapIndex ] : number; + return snapGap === 0 || gap < snapGap ? snapArray[ closestGapIndex ] : null; } ); /** From 86834b5067cf908bab13417f9d640de87b9304aa Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 28 Aug 2019 13:18:35 +0200 Subject: [PATCH 30/80] Show snap lines when resizing a rotated block --- .../components/resizable-box/index.js | 107 +++++++++++------- 1 file changed, 67 insertions(+), 40 deletions(-) diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 30279b761d2..b9cdf68caf9 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -39,6 +39,7 @@ import { TEXT_BLOCK_PADDING, REVERSE_WIDTH_CALCULATIONS, REVERSE_HEIGHT_CALCULATIONS, + BLOCK_DRAGGING_SNAP_GAP, } from '../../constants'; let lastSeenX = 0, @@ -247,6 +248,61 @@ class EnhancedResizableBox extends Component { const snappingEnabled = ! event.getModifierState( 'Alt' ); + const dimensions = blockElement.getBoundingClientRect(); + + // We calculate with the block's actual dimensions relative to the page it's on. + let { + top: actualTop, + right: actualRight, + bottom: actualBottom, + left: actualLeft, + } = dimensions; + + actualTop -= parentBlockOffsetTop; + actualRight -= parentBlockOffsetLeft; + actualBottom -= parentBlockOffsetTop; + actualLeft -= parentBlockOffsetLeft; + + const horizontalCenter = actualLeft + ( ( actualRight - actualLeft ) / 2 ); + const verticalCenter = actualTop + ( ( actualBottom - actualTop ) / 2 ); + + const horizontalLeftSnap = findClosestSnap( actualLeft, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const horizontalRightSnap = findClosestSnap( actualRight, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const horizontalCenterSnap = findClosestSnap( horizontalCenter, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const verticalTopSnap = findClosestSnap( actualTop, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const verticalBottomSnap = findClosestSnap( actualBottom, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const verticalCenterSnap = findClosestSnap( verticalCenter, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + + if ( horizontalLeftSnap !== null ) { + const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; + newSnapLines.push( snapLine ); + } + + if ( horizontalRightSnap !== null ) { + const snapLine = [ [ horizontalRightSnap, 0 ], [ horizontalRightSnap, STORY_PAGE_INNER_HEIGHT ] ]; + newSnapLines.push( snapLine ); + } + + if ( horizontalCenterSnap !== null ) { + const snapLine = [ [ horizontalCenterSnap, 0 ], [ horizontalCenterSnap, STORY_PAGE_INNER_HEIGHT ] ]; + newSnapLines.push( snapLine ); + } + + if ( verticalTopSnap !== null ) { + const snapLine = [ [ 0, verticalTopSnap ], [ STORY_PAGE_INNER_WIDTH, verticalTopSnap ] ]; + newSnapLines.push( snapLine ); + } + + if ( verticalBottomSnap !== null ) { + const snapLine = [ [ 0, verticalBottomSnap ], [ STORY_PAGE_INNER_WIDTH, verticalBottomSnap ] ]; + newSnapLines.push( snapLine ); + } + + if ( verticalCenterSnap !== null ) { + const snapLine = [ [ 0, verticalCenterSnap ], [ STORY_PAGE_INNER_WIDTH, verticalCenterSnap ] ]; + newSnapLines.push( snapLine ); + } + if ( ! angle ) { // If the resizing is to left or top then we have to compensate if ( REVERSE_WIDTH_CALCULATIONS.includes( direction ) ) { @@ -255,30 +311,16 @@ class EnhancedResizableBox extends Component { leftInPx = leftInPx - lastDeltaW; if ( lastDeltaW ) { - const horizontalLeftSnap = findClosestSnap( leftInPx, this.horizontalSnaps, snapGap ); - - if ( horizontalLeftSnap !== null ) { - const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; - newSnapLines.push( snapLine ); - - if ( snappingEnabled ) { - appliedWidth += leftInPx - horizontalLeftSnap; - leftInPx = horizontalLeftSnap; - } + if ( horizontalLeftSnap !== null && snappingEnabled ) { + appliedWidth += leftInPx - horizontalLeftSnap; + leftInPx = horizontalLeftSnap; } } blockElement.style.left = getPercentageFromPixels( 'x', leftInPx ) + '%'; } else if ( lastDeltaW ) { - const horizontalRightSnap = findClosestSnap( blockElement.offsetLeft + appliedWidth, this.horizontalSnaps, snapGap ); - - if ( horizontalRightSnap !== null ) { - const snapLine = [ [ horizontalRightSnap, 0 ], [ horizontalRightSnap, STORY_PAGE_INNER_HEIGHT ] ]; - newSnapLines.push( snapLine ); - - if ( snappingEnabled ) { - appliedWidth = horizontalRightSnap - blockElement.offsetLeft; - } + if ( horizontalRightSnap !== null && snappingEnabled ) { + appliedWidth = horizontalRightSnap - blockElement.offsetLeft; } } @@ -288,30 +330,16 @@ class EnhancedResizableBox extends Component { topInPx = topInPx - lastDeltaH; if ( lastDeltaH ) { - const verticalTopSnap = findClosestSnap( topInPx, this.verticalSnaps, snapGap ); - - if ( verticalTopSnap !== null ) { - const snapLine = [ [ 0, verticalTopSnap ], [ STORY_PAGE_INNER_WIDTH, verticalTopSnap ] ]; - newSnapLines.push( snapLine ); - - if ( snappingEnabled ) { - appliedHeight += topInPx - verticalTopSnap; - topInPx = verticalTopSnap; - } + if ( verticalTopSnap !== null && snappingEnabled ) { + appliedHeight += topInPx - verticalTopSnap; + topInPx = verticalTopSnap; } } blockElement.style.top = getPercentageFromPixels( 'y', topInPx ) + '%'; } else if ( lastDeltaH ) { - const verticalBottomSnap = findClosestSnap( blockElement.offsetTop + appliedHeight, this.verticalSnaps, snapGap ); - - if ( verticalBottomSnap !== null ) { - const snapLine = [ [ 0, verticalBottomSnap ], [ STORY_PAGE_INNER_WIDTH, verticalBottomSnap ] ]; - newSnapLines.push( snapLine ); - - if ( snappingEnabled ) { - appliedHeight = verticalBottomSnap - blockElement.offsetTop; - } + if ( verticalBottomSnap !== null && snappingEnabled ) { + appliedHeight = verticalBottomSnap - blockElement.offsetTop; } } } else { @@ -336,8 +364,7 @@ class EnhancedResizableBox extends Component { const originalPos = getResizedBlockPosition( direction, blockElementLeft, blockElementTop, lastDeltaW, lastDeltaH ); const updatedPos = getUpdatedBlockPosition( direction, originalPos, diff ); - // @todo: Get outermost left position and calculate snap. - // @todo: Get outermost top position and calculate snap. + // @todo: Do actual snapping. blockElement.style.left = getPercentageFromPixels( 'x', updatedPos.left ) + '%'; blockElement.style.top = getPercentageFromPixels( 'y', updatedPos.top ) + '%'; From 1ea577694b5ae5a755b555fbde04777f0cf10ffd Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 30 Aug 2019 17:59:52 +0200 Subject: [PATCH 31/80] Use same position calculation also for non-rotated blocks --- .../components/resizable-box/index.js | 79 +++++-------------- 1 file changed, 19 insertions(+), 60 deletions(-) diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index b9cdf68caf9..743dc4f4327 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -303,72 +303,31 @@ class EnhancedResizableBox extends Component { newSnapLines.push( snapLine ); } - if ( ! angle ) { - // If the resizing is to left or top then we have to compensate - if ( REVERSE_WIDTH_CALCULATIONS.includes( direction ) ) { - let leftInPx = getPixelsFromPercentage( 'x', parseFloat( blockElementLeft ) ); - - leftInPx = leftInPx - lastDeltaW; - - if ( lastDeltaW ) { - if ( horizontalLeftSnap !== null && snappingEnabled ) { - appliedWidth += leftInPx - horizontalLeftSnap; - leftInPx = horizontalLeftSnap; - } - } - - blockElement.style.left = getPercentageFromPixels( 'x', leftInPx ) + '%'; - } else if ( lastDeltaW ) { - if ( horizontalRightSnap !== null && snappingEnabled ) { - appliedWidth = horizontalRightSnap - blockElement.offsetLeft; - } - } - - if ( REVERSE_HEIGHT_CALCULATIONS.includes( direction ) ) { - let topInPx = getPixelsFromPercentage( 'y', parseFloat( blockElementTop ) ); - - topInPx = topInPx - lastDeltaH; + const radianAngle = getRadianFromDeg( angle ); - if ( lastDeltaH ) { - if ( verticalTopSnap !== null && snappingEnabled ) { - appliedHeight += topInPx - verticalTopSnap; - topInPx = verticalTopSnap; - } - } + // Compare position between the initial and after resizing. + let initialPosition, resizedPosition; - blockElement.style.top = getPercentageFromPixels( 'y', topInPx ) + '%'; - } else if ( lastDeltaH ) { - if ( verticalBottomSnap !== null && snappingEnabled ) { - appliedHeight = verticalBottomSnap - blockElement.offsetTop; - } - } + // If it's a text block, we shouldn't consider the added padding for measuring. + if ( isText ) { + initialPosition = getBlockPositioning( width - ( TEXT_BLOCK_PADDING * 2 ), height - ( TEXT_BLOCK_PADDING * 2 ), radianAngle, direction ); + resizedPosition = getBlockPositioning( appliedWidth - ( TEXT_BLOCK_PADDING * 2 ), appliedHeight - ( TEXT_BLOCK_PADDING * 2 ), radianAngle, direction ); } else { - const radianAngle = getRadianFromDeg( angle ); - - // Compare position between the initial and after resizing. - let initialPosition, resizedPosition; - - // If it's a text block, we shouldn't consider the added padding for measuring. - if ( isText ) { - initialPosition = getBlockPositioning( width - ( TEXT_BLOCK_PADDING * 2 ), height - ( TEXT_BLOCK_PADDING * 2 ), radianAngle, direction ); - resizedPosition = getBlockPositioning( appliedWidth - ( TEXT_BLOCK_PADDING * 2 ), appliedHeight - ( TEXT_BLOCK_PADDING * 2 ), radianAngle, direction ); - } else { - initialPosition = getBlockPositioning( width, height, radianAngle, direction ); - resizedPosition = getBlockPositioning( appliedWidth, appliedHeight, radianAngle, direction ); - } - const diff = { - left: resizedPosition.left - initialPosition.left, - top: resizedPosition.top - initialPosition.top, - }; + initialPosition = getBlockPositioning( width, height, radianAngle, direction ); + resizedPosition = getBlockPositioning( appliedWidth, appliedHeight, radianAngle, direction ); + } + const diff = { + left: resizedPosition.left - initialPosition.left, + top: resizedPosition.top - initialPosition.top, + }; - const originalPos = getResizedBlockPosition( direction, blockElementLeft, blockElementTop, lastDeltaW, lastDeltaH ); - const updatedPos = getUpdatedBlockPosition( direction, originalPos, diff ); + const originalPos = getResizedBlockPosition( direction, blockElementLeft, blockElementTop, lastDeltaW, lastDeltaH ); + const updatedPos = getUpdatedBlockPosition( direction, originalPos, diff ); - // @todo: Do actual snapping. + // @todo: Do actual snapping. - blockElement.style.left = getPercentageFromPixels( 'x', updatedPos.left ) + '%'; - blockElement.style.top = getPercentageFromPixels( 'y', updatedPos.top ) + '%'; - } + blockElement.style.left = getPercentageFromPixels( 'x', updatedPos.left ) + '%'; + blockElement.style.top = getPercentageFromPixels( 'y', updatedPos.top ) + '%'; element.style.width = appliedWidth + 'px'; element.style.height = appliedHeight + 'px'; From 8b078ab7c213902b31a8633b1feb9415fd694596 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 30 Aug 2019 18:11:17 +0200 Subject: [PATCH 32/80] Remove unused consts --- assets/src/stories-editor/components/resizable-box/index.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 743dc4f4327..6a15c1d3b44 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -20,7 +20,6 @@ import withSnapTargets from '../higher-order/with-snap-targets'; import './edit.css'; import { getPercentageFromPixels, - getPixelsFromPercentage, findClosestSnap, getBlockInnerElement, } from '../../helpers'; @@ -37,8 +36,6 @@ import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH, TEXT_BLOCK_PADDING, - REVERSE_WIDTH_CALCULATIONS, - REVERSE_HEIGHT_CALCULATIONS, BLOCK_DRAGGING_SNAP_GAP, } from '../../constants'; @@ -246,8 +243,6 @@ class EnhancedResizableBox extends Component { lastDeltaW = deltaW; } - const snappingEnabled = ! event.getModifierState( 'Alt' ); - const dimensions = blockElement.getBoundingClientRect(); // We calculate with the block's actual dimensions relative to the page it's on. From 2225b70ded21724764b25823de44b196621b1416 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 10 Sep 2019 14:54:10 +0200 Subject: [PATCH 33/80] Remove specific resizing snap targets for now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit They didn’t work as intended --- .../components/resizable-box/index.js | 72 +------------------ 1 file changed, 1 insertion(+), 71 deletions(-) diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 6a15c1d3b44..c8048a5b94e 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -8,8 +8,6 @@ import PropTypes from 'prop-types'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; import { ResizableBox } from '@wordpress/components'; import isShallowEqual from '@wordpress/is-shallow-equal'; @@ -21,7 +19,6 @@ import './edit.css'; import { getPercentageFromPixels, findClosestSnap, - getBlockInnerElement, } from '../../helpers'; import { getBlockPositioning, @@ -395,71 +392,4 @@ EnhancedResizableBox.propTypes = { parentBlockOffsetLeft: PropTypes.number.isRequired, }; -/** - * Enhances the default snap targets specifically for resizing. - * - * This allows resizing a block so that it can be nicely centered. - * - * @type {Component} - */ -const withResizingSnapTargets = withSelect( ( select, ownProps ) => { - const { getBlock } = select( 'core/block-editor' ); - - const { - clientId, - horizontalSnaps, - verticalSnaps, - parentBlockOffsetTop, - parentBlockOffsetLeft, - } = ownProps; - - return { - horizontalSnaps: () => { - const blockInnerElement = getBlockInnerElement( getBlock( clientId ) ); - - if ( ! blockInnerElement ) { - return horizontalSnaps(); - } - - const dimensions = blockInnerElement.getBoundingClientRect(); - - // We calculate with the block's actual dimensions relative to the page it's on. - let { right: actualRight, left: actualLeft } = dimensions; - - actualRight -= parentBlockOffsetLeft; - actualLeft -= parentBlockOffsetLeft; - - return [ - ...horizontalSnaps(), - ...[ STORY_PAGE_INNER_WIDTH - actualLeft, STORY_PAGE_INNER_WIDTH - actualRight ], - ]; - }, - verticalSnaps: () => { - const blockInnerElement = getBlockInnerElement( getBlock( clientId ) ); - - if ( ! blockInnerElement ) { - return horizontalSnaps(); - } - - const dimensions = blockInnerElement.getBoundingClientRect(); - - // We calculate with the block's actual dimensions relative to the page it's on. - let { top: actualTop, bottom: actualBottom } = dimensions; - - actualTop -= parentBlockOffsetTop; - actualBottom -= parentBlockOffsetTop; - - return [ - ...verticalSnaps(), - ...[ STORY_PAGE_INNER_HEIGHT - actualTop, STORY_PAGE_INNER_HEIGHT - actualBottom ], - ]; - }, - }; -} ); - -const enhance = compose( - withSnapTargets, - withResizingSnapTargets, -); - -export default enhance( EnhancedResizableBox ); +export default withSnapTargets( EnhancedResizableBox ); From bbaf35549b80050243b91cf09475a27d4fdde52e Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 10 Sep 2019 15:26:36 +0200 Subject: [PATCH 34/80] Fix import order --- assets/src/stories-editor/store/reducer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/src/stories-editor/store/reducer.js b/assets/src/stories-editor/store/reducer.js index fc92b117bfe..4b08e9812c9 100644 --- a/assets/src/stories-editor/store/reducer.js +++ b/assets/src/stories-editor/store/reducer.js @@ -2,12 +2,12 @@ * WordPress dependencies */ import { combineReducers } from '@wordpress/data'; +import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies */ import { isValidAnimationPredecessor } from './selectors'; -import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Reducer handling animation state changes. From 7c32015518c613d7aec2ff3c10809b0cb40ddf46 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 10 Sep 2019 23:44:27 +0200 Subject: [PATCH 35/80] Remove now unneeded binds --- .../src/stories-editor/components/block-mover/draggable.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 870d7ed799c..dd87b22db78 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -47,12 +47,6 @@ class Draggable extends Component { constructor( ...args ) { super( ...args ); - this.onDragStart = this.onDragStart.bind( this ); - this.onDragOver = this.onDragOver.bind( this ); - this.onDrop = this.onDrop.bind( this ); - this.onDragEnd = this.onDragEnd.bind( this ); - this.resetDragState = this.resetDragState.bind( this ); - this.isChromeAndHasIframes = false; } From 063b24cd336380193f8393fb6a40b1f746988541 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 11 Sep 2019 15:26:32 +0200 Subject: [PATCH 36/80] Remove actual snapping and only keep indicators for now --- .../components/block-mover/draggable.js | 145 +++++------------- .../higher-order/with-snap-targets.js | 142 +++++++++-------- .../components/resizable-box/index.js | 64 ++++---- 3 files changed, 145 insertions(+), 206 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index dd87b22db78..990b66c0e42 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -12,7 +12,6 @@ import PropTypes from 'prop-types'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { compose, withSafeTimeout } from '@wordpress/compose'; import isShallowEqual from '@wordpress/is-shallow-equal'; /** @@ -38,10 +37,6 @@ const documentHasIframes = ( ) => [ ...document.getElementById( 'editor' ).query let lastX; let lastY; -let originalX; -let originalY; -let initialBlockX; -let initialBlockY; class Draggable extends Component { constructor( ...args ) { @@ -103,20 +98,14 @@ class Draggable extends Component { parentBlockOffsetLeft, } = this.props; - const newSnapLines = []; - - let top = parseInt( this.cloneWrapper.style.top ) + event.clientY - this.cursorTop; - let left = parseInt( this.cloneWrapper.style.left ) + event.clientX - this.cursorLeft; - - const originalTop = top; - const originalLeft = left; + const top = parseInt( this.cloneWrapper.style.top ) + event.clientY - this.cursorTop; + const left = parseInt( this.cloneWrapper.style.left ) + event.clientX - this.cursorLeft; if ( top === lastY && left === lastX ) { return; } - lastY = top; - lastX = left; + const snappingEnabled = ! event.getModifierState( 'Alt' ); const dimensions = this.cloneWrapper.getBoundingClientRect(); @@ -133,100 +122,51 @@ class Draggable extends Component { actualBottom -= parentBlockOffsetTop; actualLeft -= parentBlockOffsetLeft; - const { - width: actualWidth, - height: actualHeight, - } = dimensions; - const horizontalCenter = actualLeft + ( ( actualRight - actualLeft ) / 2 ); const verticalCenter = actualTop + ( ( actualBottom - actualTop ) / 2 ); - // The difference in width/height caused by rotation. - const rotatedWidthDiff = ( this.cloneWrapper.offsetWidth - actualWidth ) / 2; - const rotatedHeightDiff = ( this.cloneWrapper.offsetHeight - actualHeight ) / 2; - - const horizontalLeftSnap = findClosestSnap( actualLeft, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const horizontalRightSnap = findClosestSnap( actualRight, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const horizontalCenterSnap = findClosestSnap( horizontalCenter, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const verticalTopSnap = findClosestSnap( actualTop, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const verticalBottomSnap = findClosestSnap( actualBottom, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const verticalCenterSnap = findClosestSnap( verticalCenter, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - - const snappingEnabled = ! event.getModifierState( 'Alt' ); - - // @todo: Rely on withSnapTargets to provide the data for the snapping lines so this isn't a concern of this component. - - // What the cursor has moved since the beginning. - const leftDiff = event.clientX - originalX; - const topDiff = event.clientY - originalY; - // Where the original block would be positioned based on that. - const leftToCompareWith = initialBlockX + leftDiff; - const topToCompareWith = initialBlockY + topDiff; + const newSnapLines = []; - if ( horizontalLeftSnap !== null ) { - const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; - newSnapLines.push( snapLine ); + if ( snappingEnabled ) { + const horizontalLeftSnap = findClosestSnap( actualLeft, this.horizontalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); + const horizontalRightSnap = findClosestSnap( actualRight, this.horizontalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); + const horizontalCenterSnap = findClosestSnap( horizontalCenter, this.horizontalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); + const verticalTopSnap = findClosestSnap( actualTop, this.verticalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); + const verticalBottomSnap = findClosestSnap( actualBottom, this.verticalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); + const verticalCenterSnap = findClosestSnap( verticalCenter, this.verticalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); - if ( snappingEnabled ) { - if ( Math.abs( leftToCompareWith - horizontalLeftSnap ) <= BLOCK_DRAGGING_SNAP_GAP ) { - left = horizontalLeftSnap - rotatedWidthDiff; - } + if ( horizontalLeftSnap !== null ) { + newSnapLines.push( ...this.horizontalSnaps[ horizontalLeftSnap ] ); } - } - if ( horizontalRightSnap !== null ) { - const snapLine = [ [ horizontalRightSnap, 0 ], [ horizontalRightSnap, STORY_PAGE_INNER_HEIGHT ] ]; - newSnapLines.push( snapLine ); - - if ( snappingEnabled ) { - if ( Math.abs( leftToCompareWith + actualWidth - horizontalRightSnap ) <= BLOCK_DRAGGING_SNAP_GAP ) { - left = horizontalRightSnap - actualWidth; - } + if ( horizontalRightSnap !== null ) { + newSnapLines.push( ...this.horizontalSnaps[ horizontalRightSnap ] ); } - } - - if ( horizontalCenterSnap !== null ) { - const snapLine = [ [ horizontalCenterSnap, 0 ], [ horizontalCenterSnap, STORY_PAGE_INNER_HEIGHT ] ]; - newSnapLines.push( snapLine ); - if ( snappingEnabled ) { - if ( Math.abs( leftToCompareWith + ( actualWidth / 2 ) - horizontalCenterSnap ) <= BLOCK_DRAGGING_SNAP_GAP ) { - left = originalLeft - ( horizontalCenter - horizontalCenterSnap ); - } + if ( horizontalCenterSnap !== null ) { + newSnapLines.push( ...this.horizontalSnaps[ horizontalCenterSnap ] ); } - } - - if ( verticalTopSnap !== null ) { - const snapLine = [ [ 0, verticalTopSnap ], [ STORY_PAGE_INNER_WIDTH, verticalTopSnap ] ]; - newSnapLines.push( snapLine ); - if ( snappingEnabled ) { - if ( Math.abs( topToCompareWith - verticalTopSnap ) <= BLOCK_DRAGGING_SNAP_GAP ) { - top = verticalTopSnap - rotatedHeightDiff; - } + if ( verticalTopSnap !== null ) { + newSnapLines.push( ...this.verticalSnaps[ verticalTopSnap ] ); } - } - if ( verticalBottomSnap !== null ) { - const snapLine = [ [ 0, verticalBottomSnap ], [ STORY_PAGE_INNER_WIDTH, verticalBottomSnap ] ]; - newSnapLines.push( snapLine ); + if ( verticalBottomSnap !== null ) { + newSnapLines.push( ...this.verticalSnaps[ verticalBottomSnap ] ); + } - if ( snappingEnabled ) { - if ( Math.abs( topToCompareWith + actualHeight - verticalBottomSnap ) <= BLOCK_DRAGGING_SNAP_GAP ) { - top = originalTop - ( actualBottom - verticalBottomSnap ); - } + if ( verticalCenterSnap !== null ) { + newSnapLines.push( ...this.verticalSnaps[ verticalCenterSnap ] ); } } - if ( verticalCenterSnap !== null ) { - const snapLine = [ [ 0, verticalCenterSnap ], [ STORY_PAGE_INNER_WIDTH, verticalCenterSnap ] ]; - newSnapLines.push( snapLine ); - - if ( snappingEnabled ) { - if ( Math.abs( topToCompareWith + ( actualHeight / 2 ) - verticalCenterSnap ) <= BLOCK_DRAGGING_SNAP_GAP ) { - top = originalTop - ( verticalCenter - verticalCenterSnap ); - } + if ( newSnapLines.length ) { + const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); + if ( ! newSnapLines.every( hasSnapLine ) ) { + setSnapLines( ...newSnapLines ); } + } else if ( snapLines.length ) { + clearSnapLines(); } // Don't allow the CTA button to go over its top limit. @@ -242,15 +182,8 @@ class Draggable extends Component { this.cursorLeft = event.clientX; this.cursorTop = event.clientY; - const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); - - if ( newSnapLines.length ) { - if ( ! newSnapLines.every( hasSnapLine ) ) { - setSnapLines( ...newSnapLines ); - } - } else if ( snapLines.length ) { - clearSnapLines(); - } + lastY = top; + lastX = left; } onDrop = () => { @@ -308,8 +241,6 @@ class Draggable extends Component { this.cloneWrapper.style.height = `${ element.clientHeight }px`; const clone = element.cloneNode( true ); - initialBlockX = getPixelsFromPercentage( 'x', parseInt( clone.style.left ), STORY_PAGE_INNER_WIDTH ); - initialBlockY = getPixelsFromPercentage( 'y', parseInt( clone.style.top ), STORY_PAGE_INNER_HEIGHT ); this.cloneWrapper.style.transform = clone.style.transform; @@ -334,8 +265,7 @@ class Draggable extends Component { // Mark the current cursor coordinates. this.cursorLeft = event.clientX; this.cursorTop = event.clientY; - originalX = event.clientX; - originalY = event.clientY; + // Update cursor to 'grabbing', document wide. document.body.classList.add( 'is-dragging-components-draggable' ); document.addEventListener( 'dragover', this.onDragOver ); @@ -357,7 +287,9 @@ class Draggable extends Component { } this.horizontalSnaps = horizontalSnaps(); + this.horizontalSnapKeys = Object.keys( this.horizontalSnaps ); this.verticalSnaps = verticalSnaps(); + this.verticalSnapKeys = Object.keys( this.verticalSnaps ); this.props.setTimeout( onDragStart ); } @@ -420,9 +352,4 @@ Draggable.propTypes = { parentBlockOffsetLeft: PropTypes.number.isRequired, }; -const enhance = compose( - withSnapTargets, - withSafeTimeout, -); - -export default enhance( Draggable ); +export default withSnapTargets( Draggable ); diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index a796f02572d..67326893c0a 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -3,6 +3,7 @@ */ import { withDispatch, withSelect } from '@wordpress/data'; import { compose, createHigherOrderComponent } from '@wordpress/compose'; +import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies @@ -18,7 +19,7 @@ import { getBlockInnerElement } from '../../helpers'; * - The page's width / height constraints and center. * - The block's position in relation to its siblings. * - * @todo Update horizontalSnaps() and verticalSnaps() to return a map of snap targets -> snap lines in order to allow showing multiple snap lines for a single snapping point that can differ from the actual target. + * The snap targets are stored in a map with the snap target as the key and the snap lines to be shown as the value. */ const applyWithSelect = withSelect( ( select, { clientId } ) => { const { @@ -51,73 +52,88 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { const { left: parentBlockOffsetLeft, top: parentBlockOffsetTop } = parentBlockElement.getBoundingClientRect(); - const siblings = getBlocksByClientId( getBlockOrder( parentBlock ) ) - .filter( ( { clientId: blockId } ) => blockId !== clientId ); + const siblings = getBlocksByClientId( getBlockOrder( parentBlock ) ).filter( ( { clientId: blockId } ) => blockId !== clientId ); + + const getVerticalLine = ( offsetX ) => [ [ offsetX, 0 ], [ offsetX, STORY_PAGE_INNER_HEIGHT ] ]; + const getHorizontalLine = ( offsetY ) => [ [ 0, offsetY ], [ STORY_PAGE_INNER_WIDTH, offsetY ] ]; + + // Setter used for the proxied objects. + const proxySet = ( obj, prop, value ) => { + prop = Math.round( prop ); + + if ( prop < 0 || prop > STORY_PAGE_INNER_WIDTH ) { + return true; + } + + const hasSnapLine = ( item ) => obj[ prop ].find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); + + if ( obj.hasOwnProperty( prop ) && ! hasSnapLine( value ) ) { + obj[ prop ].push( value ); + } else { + obj[ prop ] = [ value ]; + } + + obj[ prop ] = obj[ prop ].sort(); + + return true; + }; return { horizontalSnaps: () => { - const pageSnaps = [ - 0, - STORY_PAGE_INNER_WIDTH / 2, - STORY_PAGE_INNER_WIDTH, - ]; - - const blockSnaps = siblings - .map( ( block ) => { - const blockElement = getBlockInnerElement( block ); - - if ( ! blockElement ) { - return []; - } - - const { left, right } = blockElement.getBoundingClientRect(); - return [ Math.round( left - parentBlockOffsetLeft ), Math.round( right - parentBlockOffsetLeft ) ]; - } ) - .reduce( ( result, snaps ) => { - for ( const snap of snaps ) { - if ( snap < 0 || snap > STORY_PAGE_INNER_WIDTH || result.includes( snap ) || pageSnaps.includes( snap ) ) { - continue; - } - - result.push( snap ); - } - - return result; - }, [] ); - - return [ ...pageSnaps, ...blockSnaps ]; + const snaps = new Proxy( { + // Left page border. + 0: [ getVerticalLine( 0 ) ], + // Center of the page. + [ STORY_PAGE_INNER_WIDTH / 2 ]: [ getVerticalLine( STORY_PAGE_INNER_WIDTH / 2 ) ], + // Right page border. + [ STORY_PAGE_INNER_WIDTH ]: [ getVerticalLine( STORY_PAGE_INNER_WIDTH ) ], + }, + { + set: proxySet, + } ); + + for ( const block of siblings ) { + const blockElement = getBlockInnerElement( block ); + + if ( ! blockElement ) { + continue; + } + + const { left, right } = blockElement.getBoundingClientRect(); + + snaps[ left - parentBlockOffsetLeft ] = getVerticalLine( left - parentBlockOffsetLeft ); + snaps[ right - parentBlockOffsetLeft ] = getVerticalLine( right - parentBlockOffsetLeft ); + } + + return snaps; }, verticalSnaps: () => { - const pageSnaps = [ - 0, - STORY_PAGE_INNER_HEIGHT / 2, - STORY_PAGE_INNER_HEIGHT, - ]; - - const blockSnaps = siblings - .map( ( block ) => { - const blockElement = getBlockInnerElement( block ); - - if ( ! blockElement ) { - return []; - } - - const { top, bottom } = blockElement.getBoundingClientRect(); - return [ Math.round( top - parentBlockOffsetTop ), Math.round( bottom - parentBlockOffsetTop ) ]; - } ) - .reduce( ( result, snaps ) => { - for ( const snap of snaps ) { - if ( snap < 0 || snap > STORY_PAGE_INNER_HEIGHT || result.includes( snap ) || pageSnaps.includes( snap ) ) { - continue; - } - - result.push( snap ); - } - - return result; - }, [] ); - - return [ ...pageSnaps, ...blockSnaps ]; + const snaps = new Proxy( { + // Top page border. + 0: [ getHorizontalLine( 0 ) ], + // Center of the page. + [ STORY_PAGE_INNER_HEIGHT / 2 ]: [ getHorizontalLine( STORY_PAGE_INNER_HEIGHT / 2 ) ], + // Bottom page border. + [ STORY_PAGE_INNER_HEIGHT ]: [ getHorizontalLine( STORY_PAGE_INNER_HEIGHT ) ], + }, + { + set: proxySet, + } ); + + for ( const block of siblings ) { + const blockElement = getBlockInnerElement( block ); + + if ( ! blockElement ) { + continue; + } + + const { top, bottom } = blockElement.getBoundingClientRect(); + + snaps[ top - parentBlockOffsetTop ] = getVerticalLine( top - parentBlockOffsetTop ); + snaps[ bottom - parentBlockOffsetTop ] = getVerticalLine( bottom - parentBlockOffsetTop ); + } + + return snaps; }, snapLines: getSnapLines(), parentBlockOffsetTop, diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index c8048a5b94e..ae0439af038 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -28,10 +28,7 @@ import { getRadianFromDeg, getBlockTextElement, } from './helpers'; - import { - STORY_PAGE_INNER_HEIGHT, - STORY_PAGE_INNER_WIDTH, TEXT_BLOCK_PADDING, BLOCK_DRAGGING_SNAP_GAP, } from '../../constants'; @@ -182,7 +179,9 @@ class EnhancedResizableBox extends Component { } this.horizontalSnaps = horizontalSnaps(); + this.horizontalSnapKeys = Object.keys( this.horizontalSnaps ); this.verticalSnaps = verticalSnaps(); + this.verticalSnapKeys = Object.keys( this.verticalSnaps ); onResizeStart(); } } @@ -258,41 +257,39 @@ class EnhancedResizableBox extends Component { const horizontalCenter = actualLeft + ( ( actualRight - actualLeft ) / 2 ); const verticalCenter = actualTop + ( ( actualBottom - actualTop ) / 2 ); - const horizontalLeftSnap = findClosestSnap( actualLeft, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const horizontalRightSnap = findClosestSnap( actualRight, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const horizontalCenterSnap = findClosestSnap( horizontalCenter, this.horizontalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const verticalTopSnap = findClosestSnap( actualTop, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const verticalBottomSnap = findClosestSnap( actualBottom, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); - const verticalCenterSnap = findClosestSnap( verticalCenter, this.verticalSnaps, BLOCK_DRAGGING_SNAP_GAP ); + const snappingEnabled = ! event.getModifierState( 'Alt' ); - if ( horizontalLeftSnap !== null ) { - const snapLine = [ [ horizontalLeftSnap, 0 ], [ horizontalLeftSnap, STORY_PAGE_INNER_HEIGHT ] ]; - newSnapLines.push( snapLine ); - } + if ( snappingEnabled ) { + const horizontalLeftSnap = findClosestSnap( actualLeft, this.horizontalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); + const horizontalRightSnap = findClosestSnap( actualRight, this.horizontalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); + const horizontalCenterSnap = findClosestSnap( horizontalCenter, this.horizontalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); + const verticalTopSnap = findClosestSnap( actualTop, this.verticalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); + const verticalBottomSnap = findClosestSnap( actualBottom, this.verticalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); + const verticalCenterSnap = findClosestSnap( verticalCenter, this.verticalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); - if ( horizontalRightSnap !== null ) { - const snapLine = [ [ horizontalRightSnap, 0 ], [ horizontalRightSnap, STORY_PAGE_INNER_HEIGHT ] ]; - newSnapLines.push( snapLine ); - } + if ( horizontalLeftSnap !== null ) { + newSnapLines.push( ...this.horizontalSnaps[ horizontalLeftSnap ] ); + } - if ( horizontalCenterSnap !== null ) { - const snapLine = [ [ horizontalCenterSnap, 0 ], [ horizontalCenterSnap, STORY_PAGE_INNER_HEIGHT ] ]; - newSnapLines.push( snapLine ); - } + if ( horizontalRightSnap !== null ) { + newSnapLines.push( ...this.horizontalSnaps[ horizontalRightSnap ] ); + } - if ( verticalTopSnap !== null ) { - const snapLine = [ [ 0, verticalTopSnap ], [ STORY_PAGE_INNER_WIDTH, verticalTopSnap ] ]; - newSnapLines.push( snapLine ); - } + if ( horizontalCenterSnap !== null ) { + newSnapLines.push( ...this.horizontalSnaps[ horizontalCenterSnap ] ); + } - if ( verticalBottomSnap !== null ) { - const snapLine = [ [ 0, verticalBottomSnap ], [ STORY_PAGE_INNER_WIDTH, verticalBottomSnap ] ]; - newSnapLines.push( snapLine ); - } + if ( verticalTopSnap !== null ) { + newSnapLines.push( ...this.verticalSnaps[ verticalTopSnap ] ); + } - if ( verticalCenterSnap !== null ) { - const snapLine = [ [ 0, verticalCenterSnap ], [ STORY_PAGE_INNER_WIDTH, verticalCenterSnap ] ]; - newSnapLines.push( snapLine ); + if ( verticalBottomSnap !== null ) { + newSnapLines.push( ...this.verticalSnaps[ verticalBottomSnap ] ); + } + + if ( verticalCenterSnap !== null ) { + newSnapLines.push( ...this.verticalSnaps[ verticalCenterSnap ] ); + } } const radianAngle = getRadianFromDeg( angle ); @@ -341,9 +338,8 @@ class EnhancedResizableBox extends Component { imageWrapper.style.height = appliedHeight + 'px'; } - const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); - if ( newSnapLines.length ) { + const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); if ( ! newSnapLines.every( hasSnapLine ) ) { setSnapLines( ...newSnapLines ); } From 301b633838a6214b508c57fc8df7ae047e93f672 Mon Sep 17 00:00:00 2001 From: Morten Barklund Date: Mon, 16 Sep 2019 18:54:19 +0200 Subject: [PATCH 37/80] Reimplement snap lines via React Context API The old snaplines were very labor intensive for the browser. The main culprit turned out to be the storage medium itself, storing the data in the WP Data store. Just issuing `dispatch` takes 40ms - even when the state doesn't change at all. This rewrite uses the React Context API in stead storing snap lines in state locally in the context for each page and passing the getters and setters to the relevant components via a context consumer accessible through a simple higher-order function. A complete rewrite of both draggable and resizable to hook based functions is recommended, but outside the scope of this task. Unit tests are still needed for the context functionality. --- .../components/block-mover/draggable.js | 5 +- .../components/contexts/Snapping.js | 98 +++++++++++++++++++ .../higher-order/with-snap-lines.js | 59 +++-------- .../higher-order/with-snap-targets.js | 29 ++---- .../components/resizable-box/index.js | 5 +- assets/src/stories-editor/store/actions.js | 25 ----- assets/src/stories-editor/store/index.js | 4 - assets/src/stories-editor/store/reducer.js | 55 ----------- assets/src/stories-editor/store/selectors.js | 19 ---- .../src/stories-editor/store/test/actions.js | 45 --------- .../src/stories-editor/store/test/reducer.js | 36 ------- 11 files changed, 122 insertions(+), 258 deletions(-) create mode 100644 assets/src/stories-editor/components/contexts/Snapping.js diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 990b66c0e42..ea67779fea0 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -161,10 +161,7 @@ class Draggable extends Component { } if ( newSnapLines.length ) { - const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); - if ( ! newSnapLines.every( hasSnapLine ) ) { - setSnapLines( ...newSnapLines ); - } + setSnapLines( newSnapLines ); } else if ( snapLines.length ) { clearSnapLines(); } diff --git a/assets/src/stories-editor/components/contexts/Snapping.js b/assets/src/stories-editor/components/contexts/Snapping.js new file mode 100644 index 00000000000..882ad53fded --- /dev/null +++ b/assets/src/stories-editor/components/contexts/Snapping.js @@ -0,0 +1,98 @@ +/** + * External dependencies + */ +import React from 'react'; +import PropTypes from 'prop-types'; + +/** + * WordPress dependencies + */ +import { SVG } from '@wordpress/components'; +import { createHigherOrderComponent } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; + +const SnapContext = React.createContext(); + +const Snapping = ( { children } ) => { + const [ snapLines, setSnapLines ] = React.useState( [] ); + const [ hasSnapLines, setHasSnapLines ] = React.useState( false ); + + const showSnapLines = () => setHasSnapLines( true ); + const hideSnapLines = () => setHasSnapLines( false ); + const clearSnapLines = () => setSnapLines( [] ); + + const context = { + showSnapLines, + hideSnapLines, + setSnapLines, + snapLines, + hasSnapLines, + clearSnapLines, + }; + + return ( + + { children } + { hasSnapLines && snapLines.length && ( + + { snapLines.map( ( [ start, end ], index ) => ( + + ) ) } + + ) } + + ); +}; + +Snapping.propTypes = { + children: PropTypes.object, +}; + +export default Snapping; + +export const withSnapContext = createHigherOrderComponent( + ( WrappedComponent ) => ( props ) => ( + + { + ( { + snapLines, + setSnapLines, + showSnapLines, + hideSnapLines, + clearSnapLines, + } ) => { + const fullProps = { + ...props, + snapLines, + setSnapLines, + showSnapLines, + hideSnapLines, + clearSnapLines, + }; + + return ; + } + } + + ), + 'withSnapContext', +); diff --git a/assets/src/stories-editor/components/higher-order/with-snap-lines.js b/assets/src/stories-editor/components/higher-order/with-snap-lines.js index 8b9e93089b8..d911413881c 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-lines.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-lines.js @@ -1,24 +1,17 @@ +/** + * External dependencies + */ +import PropTypes from 'prop-types'; + /** * WordPress dependencies */ -import { withSelect } from '@wordpress/data'; -import { SVG } from '@wordpress/components'; import { createHigherOrderComponent } from '@wordpress/compose'; /** * Internal dependencies */ -import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; - -const applyWithSelect = withSelect( ( select, { clientId } ) => { - const { getCurrentPage, snapLinesVisible, getSnapLines } = select( 'amp/story' ); - - return { - snapLinesVisible: snapLinesVisible(), - snapLines: getSnapLines(), - isActivePage: getCurrentPage() === clientId, - }; -} ); +import Snapping from '../contexts/Snapping'; /** * Higher-order component that adds snap lines to page blocks @@ -27,43 +20,23 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { */ export default createHigherOrderComponent( ( BlockEdit ) => { - return applyWithSelect( ( props ) => { - const { snapLinesVisible, snapLines, isActivePage } = props; + const Inner = ( props ) => { + const { name } = props; - if ( ! isActivePage ) { - return ; - } - - if ( ! snapLinesVisible || ! snapLines.length ) { + if ( name !== 'amp/amp-story-page' ) { return ; } return ( - <> + - - { snapLines.map( ( [ start, end ], index ) => ( - - ) ) } - - + ); - } ); + }; + + Inner.propTypes = { name: PropTypes.string.isRequired }; + + return Inner; }, 'withSnapLines' ); diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index 67326893c0a..70599989b4c 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { withDispatch, withSelect } from '@wordpress/data'; +import { withSelect } from '@wordpress/data'; import { compose, createHigherOrderComponent } from '@wordpress/compose'; import isShallowEqual from '@wordpress/is-shallow-equal'; @@ -10,6 +10,7 @@ import isShallowEqual from '@wordpress/is-shallow-equal'; */ import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; import { getBlockInnerElement } from '../../helpers'; +import { withSnapContext } from '../contexts/Snapping'; /** * Higher-order component that returns snap targets for the current block. @@ -28,14 +29,13 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { getBlock, getBlockOrder, } = select( 'core/block-editor' ); - const { getCurrentPage, getSnapLines } = select( 'amp/story' ); + const { getCurrentPage } = select( 'amp/story' ); const parentBlock = getBlockRootClientId( clientId ); const defaultData = { horizontalSnaps: [], verticalSnaps: [], - snapLines: [], parentBlockOffsetTop: 0, parentBlockOffsetLeft: 0, }; @@ -129,37 +129,20 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { const { top, bottom } = blockElement.getBoundingClientRect(); - snaps[ top - parentBlockOffsetTop ] = getVerticalLine( top - parentBlockOffsetTop ); - snaps[ bottom - parentBlockOffsetTop ] = getVerticalLine( bottom - parentBlockOffsetTop ); + snaps[ top - parentBlockOffsetTop ] = getHorizontalLine( top - parentBlockOffsetTop ); + snaps[ bottom - parentBlockOffsetTop ] = getHorizontalLine( bottom - parentBlockOffsetTop ); } return snaps; }, - snapLines: getSnapLines(), parentBlockOffsetTop, parentBlockOffsetLeft, }; } ); -const applyWithDispatch = withDispatch( ( dispatch ) => { - const { - showSnapLines, - hideSnapLines, - setSnapLines, - clearSnapLines, - } = dispatch( 'amp/story' ); - - return { - showSnapLines, - hideSnapLines, - setSnapLines, - clearSnapLines, - }; -} ); - const enhance = compose( applyWithSelect, - applyWithDispatch, + withSnapContext, ); /** diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index ae0439af038..0dbf10033a3 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -339,10 +339,7 @@ class EnhancedResizableBox extends Component { } if ( newSnapLines.length ) { - const hasSnapLine = ( item ) => snapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); - if ( ! newSnapLines.every( hasSnapLine ) ) { - setSnapLines( ...newSnapLines ); - } + setSnapLines( newSnapLines ); } else if ( snapLines.length ) { clearSnapLines(); } diff --git a/assets/src/stories-editor/store/actions.js b/assets/src/stories-editor/store/actions.js index 20b215339bf..abcc9a86027 100644 --- a/assets/src/stories-editor/store/actions.js +++ b/assets/src/stories-editor/store/actions.js @@ -140,28 +140,3 @@ export function resetOrder( order ) { order, }; } - -export function setSnapLines( ...snapLines ) { - return { - type: 'SET_SNAP_LINES', - snapLines, - }; -} - -export function clearSnapLines() { - return { - type: 'CLEAR_SNAP_LINES', - }; -} - -export function showSnapLines() { - return { - type: 'SHOW_SNAP_LINES', - }; -} - -export function hideSnapLines() { - return { - type: 'HIDE_SNAP_LINES', - }; -} diff --git a/assets/src/stories-editor/store/index.js b/assets/src/stories-editor/store/index.js index c4990f9fdc5..2e9a032bd1c 100644 --- a/assets/src/stories-editor/store/index.js +++ b/assets/src/stories-editor/store/index.js @@ -30,10 +30,6 @@ export default registerStore( order: [], isReordering: false, }, - snap: { - showSnapLines: false, - snapLines: [], - }, }, } ); diff --git a/assets/src/stories-editor/store/reducer.js b/assets/src/stories-editor/store/reducer.js index 4b08e9812c9..30608f5e4d8 100644 --- a/assets/src/stories-editor/store/reducer.js +++ b/assets/src/stories-editor/store/reducer.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { combineReducers } from '@wordpress/data'; -import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies @@ -160,62 +159,8 @@ export function blocks( state = {}, action ) { } } -/** - * Reducer handling block snapping. - * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. - * - * @return {Object} Updated state. - */ -export function snap( state = {}, action ) { - switch ( action.type ) { - case 'SHOW_SNAP_LINES': - return { - ...state, - showSnapLines: true, - }; - - case 'HIDE_SNAP_LINES': - return { - ...state, - showSnapLines: false, - }; - - case 'SET_SNAP_LINES': - const { snapLines: existingSnapLines = [] } = state; - const { snapLines = [] } = action; - - if ( snapLines.length === existingSnapLines.length ) { - const hasSnapLine = ( item ) => existingSnapLines.find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); - if ( snapLines.every( hasSnapLine ) ) { - return state; - } - } - - return { - ...state, - snapLines: [ ...snapLines ], - }; - - case 'CLEAR_SNAP_LINES': - if ( ! state.snapLines.length ) { - return state; - } - - return { - ...state, - snapLines: [], - }; - - default: - return state; - } -} - export default combineReducers( { animations, currentPage, blocks, - snap, } ); diff --git a/assets/src/stories-editor/store/selectors.js b/assets/src/stories-editor/store/selectors.js index f2ac2eded8a..c90935f47de 100644 --- a/assets/src/stories-editor/store/selectors.js +++ b/assets/src/stories-editor/store/selectors.js @@ -109,22 +109,3 @@ export function isReordering( state ) { export function getSettings( state ) { return state.editorSettings || window.ampStoriesEditorSettings || {}; } - -/** - * Shared reference to an empty array for cases where it is important to avoid - * returning a new array reference on every invocation, as in a connected or - * other pure component which performs `shouldComponentUpdate` check on props. - * This should be used as a last resort, since the normalized data should be - * maintained by the reducer result in state. - * - * @type {Array} - */ -const EMPTY_ARRAY = []; - -export function snapLinesVisible( state ) { - return state.snap.showSnapLines; -} - -export function getSnapLines( state ) { - return state.snap.snapLines && state.snap.snapLines.length > 0 ? state.snap.snapLines : EMPTY_ARRAY; -} diff --git a/assets/src/stories-editor/store/test/actions.js b/assets/src/stories-editor/store/test/actions.js index 2713bd834bc..2bcf6dfd95f 100644 --- a/assets/src/stories-editor/store/test/actions.js +++ b/assets/src/stories-editor/store/test/actions.js @@ -11,10 +11,6 @@ import { movePageToPosition, saveOrder, resetOrder, - showSnapLines, - hideSnapLines, - setSnapLines, - clearSnapLines, } from '../actions'; describe( 'actions', () => { @@ -136,45 +132,4 @@ describe( 'actions', () => { } ); } ); } ); - - describe( 'setSnapLines', () => { - it( 'should return the SET_SNAP_LINES action', () => { - const result = setSnapLines( 1, 2, 3 ); - - expect( result ).toStrictEqual( { - type: 'SET_SNAP_LINES', - snapLines: [ 1, 2, 3 ], - } ); - } ); - } ); - - describe( 'clearSnapLines', () => { - it( 'should return the CLEAR_SNAP_LINES action', () => { - const result = clearSnapLines(); - - expect( result ).toStrictEqual( { - type: 'CLEAR_SNAP_LINES', - } ); - } ); - } ); - - describe( 'showSnapLines', () => { - it( 'should return the SHOW_SNAP_LINES action', () => { - const result = showSnapLines(); - - expect( result ).toStrictEqual( { - type: 'SHOW_SNAP_LINES', - } ); - } ); - } ); - - describe( 'hideSnapLines', () => { - it( 'should return the HIDE_SNAP_LINES action', () => { - const result = hideSnapLines(); - - expect( result ).toStrictEqual( { - type: 'HIDE_SNAP_LINES', - } ); - } ); - } ); } ); diff --git a/assets/src/stories-editor/store/test/reducer.js b/assets/src/stories-editor/store/test/reducer.js index 43ea06c08c6..ee39575a1a3 100644 --- a/assets/src/stories-editor/store/test/reducer.js +++ b/assets/src/stories-editor/store/test/reducer.js @@ -173,40 +173,4 @@ describe( 'reducers', () => { } ); } ); } ); - - describe( 'snap()', () => { - it( 'should show snap lines', () => { - const state = snap( undefined, { - type: 'SHOW_SNAP_LINES', - } ); - - expect( state.showSnapLines ).toBe( true ); - } ); - - it( 'should hide snap lines', () => { - const state = snap( undefined, { - type: 'HIDE_SNAP_LINES', - } ); - - expect( state.showSnapLines ).toBe( false ); - } ); - - it( 'should add snap lines', () => { - const state = snap( undefined, { - type: 'SET_SNAP_LINES', - snapLines: [ [ [ 0, 0 ], [ 100, 100 ] ] ], - } ); - - expect( state.snapLines ).toHaveLength( 1 ); - } ); - - it( 'should clear snap lines', () => { - const original = { snapLines: [ 1, 2, 3 ] }; - const state = snap( original, { - type: 'CLEAR_SNAP_LINES', - } ); - - expect( state.snapLines ).toHaveLength( 0 ); - } ); - } ); } ); From 781b6d9810ad6a024ad6d0279fc93dbca3e3ee07 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 16 Sep 2019 19:18:51 +0200 Subject: [PATCH 38/80] Import React stuff from element package --- assets/src/stories-editor/components/contexts/Snapping.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/src/stories-editor/components/contexts/Snapping.js b/assets/src/stories-editor/components/contexts/Snapping.js index 882ad53fded..23bf7fecf33 100644 --- a/assets/src/stories-editor/components/contexts/Snapping.js +++ b/assets/src/stories-editor/components/contexts/Snapping.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import React from 'react'; import PropTypes from 'prop-types'; /** @@ -9,17 +8,18 @@ import PropTypes from 'prop-types'; */ import { SVG } from '@wordpress/components'; import { createHigherOrderComponent } from '@wordpress/compose'; +import { createContext, useState } from '@wordpress/element'; /** * Internal dependencies */ import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; -const SnapContext = React.createContext(); +const SnapContext = createContext(); const Snapping = ( { children } ) => { - const [ snapLines, setSnapLines ] = React.useState( [] ); - const [ hasSnapLines, setHasSnapLines ] = React.useState( false ); + const [ snapLines, setSnapLines ] = useState( [] ); + const [ hasSnapLines, setHasSnapLines ] = useState( false ); const showSnapLines = () => setHasSnapLines( true ); const hideSnapLines = () => setHasSnapLines( false ); From 85c2577a78109e5913c5694994baa511d35c938c Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 16 Sep 2019 19:44:42 +0200 Subject: [PATCH 39/80] Lowercase file names --- .../components/contexts/{Snapping.js => snapping.js} | 0 .../stories-editor/components/higher-order/with-snap-lines.js | 2 +- .../stories-editor/components/higher-order/with-snap-targets.js | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename assets/src/stories-editor/components/contexts/{Snapping.js => snapping.js} (100%) diff --git a/assets/src/stories-editor/components/contexts/Snapping.js b/assets/src/stories-editor/components/contexts/snapping.js similarity index 100% rename from assets/src/stories-editor/components/contexts/Snapping.js rename to assets/src/stories-editor/components/contexts/snapping.js diff --git a/assets/src/stories-editor/components/higher-order/with-snap-lines.js b/assets/src/stories-editor/components/higher-order/with-snap-lines.js index d911413881c..d21707a9130 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-lines.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-lines.js @@ -11,7 +11,7 @@ import { createHigherOrderComponent } from '@wordpress/compose'; /** * Internal dependencies */ -import Snapping from '../contexts/Snapping'; +import Snapping from '../contexts/snapping'; /** * Higher-order component that adds snap lines to page blocks diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index 70599989b4c..6b897fe7362 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -10,7 +10,7 @@ import isShallowEqual from '@wordpress/is-shallow-equal'; */ import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; import { getBlockInnerElement } from '../../helpers'; -import { withSnapContext } from '../contexts/Snapping'; +import { withSnapContext } from '../contexts/snapping'; /** * Higher-order component that returns snap targets for the current block. From 519efa9ff0db08051a8ada4324dcf7d51acac30e Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 16 Sep 2019 19:58:22 +0200 Subject: [PATCH 40/80] Add missing HOC --- .../stories-editor/components/block-mover/draggable.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 51ea8a7da21..d7a145973f4 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -12,6 +12,7 @@ import PropTypes from 'prop-types'; * WordPress dependencies */ import { Component } from '@wordpress/element'; +import { withSafeTimeout, compose } from '@wordpress/compose'; /** * Internal dependencies @@ -356,4 +357,9 @@ Draggable.propTypes = { parentBlockOffsetLeft: PropTypes.number.isRequired, }; -export default withSnapTargets( Draggable ); +const enhance = compose( + withSnapTargets, + withSafeTimeout, +); + +export default enhance( Draggable ); From 3741f23a3808cf2ffbb22fc786cc0c825e98b8c7 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 16 Sep 2019 20:04:05 +0200 Subject: [PATCH 41/80] Prevent erroneously displaying a number instead of snap lines --- assets/src/stories-editor/components/contexts/snapping.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/src/stories-editor/components/contexts/snapping.js b/assets/src/stories-editor/components/contexts/snapping.js index 23bf7fecf33..03bb676e659 100644 --- a/assets/src/stories-editor/components/contexts/snapping.js +++ b/assets/src/stories-editor/components/contexts/snapping.js @@ -37,7 +37,7 @@ const Snapping = ( { children } ) => { return ( { children } - { hasSnapLines && snapLines.length && ( + { Boolean( hasSnapLines && snapLines.length ) && ( Date: Tue, 17 Sep 2019 16:04:29 +0200 Subject: [PATCH 42/80] Get the correct dimensions in case the block is rotated Needed because #3104 added a new wrapping element --- assets/src/stories-editor/components/block-mover/draggable.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index d7a145973f4..032fbbbd5f0 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -107,7 +107,8 @@ class Draggable extends Component { const snappingEnabled = ! event.getModifierState( 'Alt' ); - const dimensions = this.cloneWrapper.getBoundingClientRect(); + // Get the correct dimensions in case the block is rotated, as rotation is only applied to the clone's inner element(s). + const dimensions = this.cloneWrapper.querySelector( '.wp-block' ).getBoundingClientRect(); // We calculate with the block's actual dimensions relative to the page it's on. let { From 6ad19280f62d51410cfc48f57c3e2f5c3dcccbb1 Mon Sep 17 00:00:00 2001 From: Morten Barklund Date: Thu, 19 Sep 2019 14:37:23 +0200 Subject: [PATCH 43/80] Add unit tests for new snapping context As this seems to be the first unit test for a hook-using component, it required some fundamental changes to the setup to make sure only a single React installation is ever in the mix. The mock for `@wordpress/element` is mostly just copied from the original, but guarantees that the React version matches that of Jest's test runner. If this is doable in a different way, please advise. The actual test of the snapping context validates, that the correct snap lines are displayed under different circumstances. --- .../components/contexts/snapping.js | 31 +--- .../test/__snapshots__/snapping.js.snap | 13 ++ .../components/contexts/test/snapping.js | 160 ++++++++++++++++++ tests/js/jest.config.js | 3 + tests/shared/test-utils/wp-element-mock.js | 112 ++++++++++++ 5 files changed, 296 insertions(+), 23 deletions(-) create mode 100644 assets/src/stories-editor/components/contexts/test/__snapshots__/snapping.js.snap create mode 100644 assets/src/stories-editor/components/contexts/test/snapping.js create mode 100644 tests/shared/test-utils/wp-element-mock.js diff --git a/assets/src/stories-editor/components/contexts/snapping.js b/assets/src/stories-editor/components/contexts/snapping.js index 03bb676e659..da614fa2b34 100644 --- a/assets/src/stories-editor/components/contexts/snapping.js +++ b/assets/src/stories-editor/components/contexts/snapping.js @@ -46,13 +46,13 @@ const Snapping = ( { children } ) => { pointerEvents: 'none', } } > - { snapLines.map( ( [ start, end ], index ) => ( + { snapLines.map( ( [ [ x1, y1 ], [ x2, y2 ] ], index ) => ( @@ -73,24 +73,9 @@ export const withSnapContext = createHigherOrderComponent( ( WrappedComponent ) => ( props ) => ( { - ( { - snapLines, - setSnapLines, - showSnapLines, - hideSnapLines, - clearSnapLines, - } ) => { - const fullProps = { - ...props, - snapLines, - setSnapLines, - showSnapLines, - hideSnapLines, - clearSnapLines, - }; - - return ; - } + ( snappingProps ) => ( + + ) } ), diff --git a/assets/src/stories-editor/components/contexts/test/__snapshots__/snapping.js.snap b/assets/src/stories-editor/components/contexts/test/__snapshots__/snapping.js.snap new file mode 100644 index 00000000000..ffdd6f5219a --- /dev/null +++ b/assets/src/stories-editor/components/contexts/test/__snapshots__/snapping.js.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Snapping should render a single snap line correctly when shown and set 1`] = ` + +`; diff --git a/assets/src/stories-editor/components/contexts/test/snapping.js b/assets/src/stories-editor/components/contexts/test/snapping.js new file mode 100644 index 00000000000..7139e050d68 --- /dev/null +++ b/assets/src/stories-editor/components/contexts/test/snapping.js @@ -0,0 +1,160 @@ +/** + * External dependencies + */ +import { mount } from 'enzyme'; +import { act } from 'react-dom/test-utils'; + +/** + * Internal dependencies + */ +import { default as Snapping, withSnapContext } from '../snapping'; + +const setup = () => { + const Dummy = () => null; + // Disable reason: I have no idea why eslint thinks this variable is unused? + // eslint-disable-next-line @wordpress/no-unused-vars-before-return + const SnappyDummy = withSnapContext( Dummy ); + const container = ( + + + + ); + const wrapper = mount( container ); + const snapProps = wrapper.find( Dummy ).props(); + const callbacks = [ + 'setSnapLines', + 'showSnapLines', + 'hideSnapLines', + 'clearSnapLines', + ]; + + // Wrap each callback in act() and follow it up with wrapper.update() + const wrappedSnapCallbacks = callbacks.reduce( + ( obj, cb ) => ( { + ...obj, + [ cb ]: ( ...args ) => { + act( () => snapProps[ cb ]( ...args ) ); + wrapper.update(); + }, + } ), + {} + ); + + const getDisplayedSnapLines = () => wrapper.find( 'line' ); + + return { + container, + wrapper, + getDisplayedSnapLines, + ...wrappedSnapCallbacks, + }; +}; + +describe( 'Snapping', () => { + const HORIZONTAL_SNAP_LINE = [ [ 0, 100 ], [ 100, 100 ] ]; + const VERTICAL_SNAP_LINE = [ [ 100, 0 ], [ 100, 100 ] ]; + + it( 'should not display any snap lines by default', () => { + const { getDisplayedSnapLines } = setup(); + + const displayedSnapLines = getDisplayedSnapLines(); + + expect( displayedSnapLines ).toHaveLength( 0 ); + } ); + + it( 'should not display any snap lines when set but not shown', () => { + const { setSnapLines, getDisplayedSnapLines } = setup(); + + setSnapLines( [ VERTICAL_SNAP_LINE, HORIZONTAL_SNAP_LINE ] ); + + const displayedSnapLines = getDisplayedSnapLines(); + + expect( displayedSnapLines ).toHaveLength( 0 ); + } ); + + it( 'should display snap lines when shown and set', () => { + const { setSnapLines, showSnapLines, getDisplayedSnapLines } = setup(); + + showSnapLines(); + setSnapLines( [ VERTICAL_SNAP_LINE, HORIZONTAL_SNAP_LINE ] ); + + const displayedSnapLines = getDisplayedSnapLines(); + + expect( displayedSnapLines ).toHaveLength( 2 ); + } ); + + it( 'should render a single snap line correctly when shown and set', () => { + const { setSnapLines, showSnapLines, getDisplayedSnapLines } = setup(); + + showSnapLines(); + setSnapLines( [ HORIZONTAL_SNAP_LINE ] ); + + const displayedSnapLines = getDisplayedSnapLines(); + + expect( displayedSnapLines ).toMatchSnapshot(); + } ); + + it( 'should display only new snap lines when shown, set and set again', () => { + const { setSnapLines, showSnapLines, getDisplayedSnapLines } = setup(); + + showSnapLines(); + setSnapLines( [ VERTICAL_SNAP_LINE, HORIZONTAL_SNAP_LINE ] ); + setSnapLines( [ VERTICAL_SNAP_LINE ] ); + + const displayedSnapLines = getDisplayedSnapLines(); + + expect( displayedSnapLines ).toHaveLength( 1 ); + } ); + + it( 'should not display any snap lines when set, shown and then hidden', () => { + const { + setSnapLines, + showSnapLines, + hideSnapLines, + getDisplayedSnapLines, + } = setup(); + + setSnapLines( [ VERTICAL_SNAP_LINE, HORIZONTAL_SNAP_LINE ] ); + showSnapLines(); + hideSnapLines(); + + const displayedSnapLines = getDisplayedSnapLines(); + + expect( displayedSnapLines ).toHaveLength( 0 ); + } ); + + it( 'should display snap lines when set, shown, hidden and then shown', () => { + const { + setSnapLines, + showSnapLines, + hideSnapLines, + getDisplayedSnapLines, + } = setup(); + + setSnapLines( [ VERTICAL_SNAP_LINE, HORIZONTAL_SNAP_LINE ] ); + showSnapLines(); + hideSnapLines(); + showSnapLines(); + + const displayedSnapLines = getDisplayedSnapLines(); + + expect( displayedSnapLines ).toHaveLength( 2 ); + } ); + + it( 'should not display any snap lines when set, shown and cleared', () => { + const { + setSnapLines, + showSnapLines, + clearSnapLines, + getDisplayedSnapLines, + } = setup(); + + setSnapLines( [ VERTICAL_SNAP_LINE, HORIZONTAL_SNAP_LINE ] ); + showSnapLines(); + clearSnapLines(); + + const displayedSnapLines = getDisplayedSnapLines(); + + expect( displayedSnapLines ).toHaveLength( 0 ); + } ); +} ); diff --git a/tests/js/jest.config.js b/tests/js/jest.config.js index a350f00e8a8..a49fb95b667 100644 --- a/tests/js/jest.config.js +++ b/tests/js/jest.config.js @@ -4,6 +4,9 @@ module.exports = { transform: { '^.+\\.[jt]sx?$': '/node_modules/@wordpress/scripts/config/babel-transform', }, + moduleNameMapper: { + '^@wordpress\\/element$': '/tests/shared/test-utils/wp-element-mock', + }, setupFiles: [ '/tests/js/setup-globals', ], diff --git a/tests/shared/test-utils/wp-element-mock.js b/tests/shared/test-utils/wp-element-mock.js new file mode 100644 index 00000000000..2f23114493f --- /dev/null +++ b/tests/shared/test-utils/wp-element-mock.js @@ -0,0 +1,112 @@ +/** + * External dependencies + */ +import { + Children, + cloneElement, + Component, + createContext, + createElement, + createRef, + forwardRef, + Fragment, + isValidElement, + memo, + StrictMode, + useState, + useEffect, + useContext, + useReducer, + useCallback, + useMemo, + useRef, + useImperativeHandle, + useLayoutEffect, + useDebugValue, + lazy, + Suspense, +} from 'react'; +import { isString } from 'lodash'; + +export { + createPortal, + findDOMNode, + render, + unmountComponentAtNode, +} from 'react-dom'; + +// Just pass these through from React. +export { + Children, + cloneElement, + Component, + createContext, + createElement, + createRef, + forwardRef, + Fragment, + isValidElement, + memo, + StrictMode, + useState, + useEffect, + useContext, + useReducer, + useCallback, + useMemo, + useRef, + useImperativeHandle, + useLayoutEffect, + useDebugValue, + lazy, + Suspense, +}; + +// These 3 functions below have to be defined here to use the correct React functions imported above. +// The first 2 are copied straight from @wordpress/element:src/react.js +export function concatChildren( ...childrenArguments ) { + return childrenArguments.reduce( ( result, children, i ) => { + Children.forEach( children, ( child, j ) => { + if ( child && 'string' !== typeof child ) { + child = cloneElement( child, { + key: [ i, j ].join(), + } ); + } + + result.push( child ); + } ); + + return result; + }, [] ); +} + +export function switchChildrenNodeName( children, nodeName ) { + return children && Children.map( children, ( elt, index ) => { + if ( isString( elt ) ) { + return createElement( nodeName, { key: index }, elt ); + } + const { children: childrenProp, ...props } = elt.props; + return createElement( nodeName, { key: index, ...props }, childrenProp ); + } ); +} + +// This function is copied straight from @wordpress/element:src/raw-html.js +export const RawHTML = ( { children, ...props } ) => { + // The DIV wrapper will be stripped by serializer, unless there are + // non-children props present. + return createElement( 'div', { + dangerouslySetInnerHTML: { __html: children }, + ...props, + } ); +}; + +// This can be re-exported after being imported from the actual package. +const { isEmptyElement } = require.requireActual( '@wordpress/element' ); +export { + isEmptyElement, +}; + +/* This mock module is explicitly *not* copying `renderToString`. + * It's possible to simple copy the entire complex function, but most likely not needed at + * all for any tests this repo will be doing. + */ From 7ec938893de61b39bf38b5ea2de7d9db04e2b333 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 23 Sep 2019 16:25:55 +0200 Subject: [PATCH 44/80] Update package lock file --- package-lock.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/package-lock.json b/package-lock.json index 8b8d5f06992..adb832fbd2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3145,6 +3145,15 @@ "@babel/runtime": "^7.4.4", "@wordpress/hooks": "^2.6.0" } + }, + "@wordpress/is-shallow-equal": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-1.6.0.tgz", + "integrity": "sha512-Yi3JvowB1+bpiKnx9OYv5Ni2zA/j8zJa5dTA5IuchOgdODRGlg9XAuC1Kl0Bd9nLmQsoU50TJYiAlffv0lUhzQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.4.4" + } } } }, From 398e4f240d402ee3d3d35f8068f9cf7e3fb3c407 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 23 Sep 2019 16:56:39 +0200 Subject: [PATCH 45/80] Do not pass hasSnapLines to consuming component in SnapContext.Provider --- assets/src/stories-editor/components/contexts/snapping.js | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/src/stories-editor/components/contexts/snapping.js b/assets/src/stories-editor/components/contexts/snapping.js index da614fa2b34..d106277cff0 100644 --- a/assets/src/stories-editor/components/contexts/snapping.js +++ b/assets/src/stories-editor/components/contexts/snapping.js @@ -30,7 +30,6 @@ const Snapping = ( { children } ) => { hideSnapLines, setSnapLines, snapLines, - hasSnapLines, clearSnapLines, }; From 7208db191173d32a783a9c331b8b685f02f5af3f Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 23 Sep 2019 17:15:24 +0200 Subject: [PATCH 46/80] Update position values for resizing e2e test --- tests/e2e/specs/stories-editor/resizing.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/e2e/specs/stories-editor/resizing.js b/tests/e2e/specs/stories-editor/resizing.js index fd372099b3a..60c0abb36f4 100644 --- a/tests/e2e/specs/stories-editor/resizing.js +++ b/tests/e2e/specs/stories-editor/resizing.js @@ -149,7 +149,7 @@ describe( 'Resizing', () => { await dragAndResize( handle, { x: -100, y: 100 } ); const { positionLeft, positionTop } = await getSelectedBlockPosition(); - expect( positionLeft ).toStrictEqual( '5%' ); + expect( positionLeft ).toStrictEqual( '4.88%' ); expect( positionTop ).toStrictEqual( '9.95%' ); } ); @@ -162,8 +162,8 @@ describe( 'Resizing', () => { await dragAndResize( handle, { x: -100, y: 0 } ); const { positionLeft, positionTop } = await getSelectedBlockPosition(); - expect( positionLeft ).toStrictEqual( '5%' ); - expect( positionTop ).toStrictEqual( '10%' ); + expect( positionLeft ).toStrictEqual( '4.88%' ); + expect( positionTop ).toStrictEqual( '9.95%' ); } ); it( 'should not change the top and left position when resizing: bottomRight', async () => { @@ -175,8 +175,8 @@ describe( 'Resizing', () => { await dragAndResize( handle, { x: 100, y: 100 } ); const { positionLeft, positionTop } = await getSelectedBlockPosition(); - expect( positionLeft ).toStrictEqual( '5%' ); - expect( positionTop ).toStrictEqual( '10%' ); + expect( positionLeft ).toStrictEqual( '4.88%' ); + expect( positionTop ).toStrictEqual( '9.95%' ); } ); it( 'should not change the top and left position when resizing: bottom', async () => { @@ -188,8 +188,8 @@ describe( 'Resizing', () => { await dragAndResize( handle, { x: 0, y: -100 } ); const { positionLeft, positionTop } = await getSelectedBlockPosition(); - expect( positionLeft ).toStrictEqual( '5%' ); - expect( positionTop ).toStrictEqual( '10%' ); + expect( positionLeft ).toStrictEqual( '4.88%' ); + expect( positionTop ).toStrictEqual( '9.95%' ); } ); it( 'should change the left position correctly when resizing: bottomLeft', async () => { @@ -202,7 +202,7 @@ describe( 'Resizing', () => { const { positionLeft, positionTop } = await getSelectedBlockPosition(); expect( positionLeft ).toStrictEqual( '-25.61%' ); - expect( positionTop ).toStrictEqual( '10%' ); + expect( positionTop ).toStrictEqual( '9.95%' ); } ); it( 'should keep text content height when resizing when max font size', async () => { From a606d91b08de758b04a634932971e63ad874b636 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 30 Sep 2019 14:54:19 +0200 Subject: [PATCH 47/80] Use helper function to make snapping code more DRY --- .../components/block-mover/draggable.js | 40 ++++++------------ .../components/resizable-box/index.js | 42 +++++++------------ 2 files changed, 27 insertions(+), 55 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 032fbbbd5f0..177881b8be2 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -105,8 +105,6 @@ class Draggable extends Component { return; } - const snappingEnabled = ! event.getModifierState( 'Alt' ); - // Get the correct dimensions in case the block is rotated, as rotation is only applied to the clone's inner element(s). const dimensions = this.cloneWrapper.querySelector( '.wp-block' ).getBoundingClientRect(); @@ -128,36 +126,24 @@ class Draggable extends Component { const newSnapLines = []; - if ( snappingEnabled ) { - const horizontalLeftSnap = findClosestSnap( actualLeft, this.horizontalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); - const horizontalRightSnap = findClosestSnap( actualRight, this.horizontalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); - const horizontalCenterSnap = findClosestSnap( horizontalCenter, this.horizontalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); - const verticalTopSnap = findClosestSnap( actualTop, this.verticalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); - const verticalBottomSnap = findClosestSnap( actualBottom, this.verticalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); - const verticalCenterSnap = findClosestSnap( verticalCenter, this.verticalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); - - if ( horizontalLeftSnap !== null ) { - newSnapLines.push( ...this.horizontalSnaps[ horizontalLeftSnap ] ); - } - - if ( horizontalRightSnap !== null ) { - newSnapLines.push( ...this.horizontalSnaps[ horizontalRightSnap ] ); - } + const snappingEnabled = ! event.getModifierState( 'Alt' ); - if ( horizontalCenterSnap !== null ) { - newSnapLines.push( ...this.horizontalSnaps[ horizontalCenterSnap ] ); - } + if ( snappingEnabled ) { + const findSnaps = ( snapKeys, ...values ) => { + return values + .map( ( value ) => findClosestSnap( value, snapKeys, BLOCK_DRAGGING_SNAP_GAP ) ) + .filter( ( value ) => value !== null ); + }; - if ( verticalTopSnap !== null ) { - newSnapLines.push( ...this.verticalSnaps[ verticalTopSnap ] ); - } + const _horizontalSnaps = findSnaps( this.horizontalSnapKeys, actualLeft, actualRight, horizontalCenter ); + const _verticalSnaps = findSnaps( this.verticalSnapKeys, actualTop, actualBottom, verticalCenter ); - if ( verticalBottomSnap !== null ) { - newSnapLines.push( ...this.verticalSnaps[ verticalBottomSnap ] ); + for ( const snap of _horizontalSnaps ) { + newSnapLines.push( ...this.horizontalSnaps[ snap ] ); } - if ( verticalCenterSnap !== null ) { - newSnapLines.push( ...this.verticalSnaps[ verticalCenterSnap ] ); + for ( const snap of _verticalSnaps ) { + newSnapLines.push( ...this.verticalSnaps[ snap ] ); } } diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index dd5f8f871f8..9dc451fe9e5 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -29,7 +29,7 @@ import { } from './helpers'; import { TEXT_BLOCK_PADDING, - BLOCK_DRAGGING_SNAP_GAP, + BLOCK_RESIZING_SNAP_GAP, } from '../../constants'; let lastSeenX = 0, @@ -185,8 +185,6 @@ class EnhancedResizableBox extends Component { onResizeStart(); } } onResize={ ( event, direction, element ) => { // eslint-disable-line complexity - const newSnapLines = []; - const { deltaW, deltaH } = getResizedWidthAndHeight( event, angle, lastSeenX, lastSeenY, direction ); // Handle case where media is inserted from URL. @@ -267,38 +265,26 @@ class EnhancedResizableBox extends Component { const horizontalCenter = actualLeft + ( ( actualRight - actualLeft ) / 2 ); const verticalCenter = actualTop + ( ( actualBottom - actualTop ) / 2 ); + const newSnapLines = []; + const snappingEnabled = ! event.getModifierState( 'Alt' ); if ( snappingEnabled ) { - const horizontalLeftSnap = findClosestSnap( actualLeft, this.horizontalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); - const horizontalRightSnap = findClosestSnap( actualRight, this.horizontalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); - const horizontalCenterSnap = findClosestSnap( horizontalCenter, this.horizontalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); - const verticalTopSnap = findClosestSnap( actualTop, this.verticalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); - const verticalBottomSnap = findClosestSnap( actualBottom, this.verticalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); - const verticalCenterSnap = findClosestSnap( verticalCenter, this.verticalSnapKeys, BLOCK_DRAGGING_SNAP_GAP ); - - if ( horizontalLeftSnap !== null ) { - newSnapLines.push( ...this.horizontalSnaps[ horizontalLeftSnap ] ); - } + const findSnaps = ( snapKeys, ...values ) => { + return values + .map( ( value ) => findClosestSnap( value, snapKeys, BLOCK_RESIZING_SNAP_GAP ) ) + .filter( ( value ) => value !== null ); + }; - if ( horizontalRightSnap !== null ) { - newSnapLines.push( ...this.horizontalSnaps[ horizontalRightSnap ] ); - } - - if ( horizontalCenterSnap !== null ) { - newSnapLines.push( ...this.horizontalSnaps[ horizontalCenterSnap ] ); - } - - if ( verticalTopSnap !== null ) { - newSnapLines.push( ...this.verticalSnaps[ verticalTopSnap ] ); - } + const _horizontalSnaps = findSnaps( this.horizontalSnapKeys, actualLeft, actualRight, horizontalCenter ); + const _verticalSnaps = findSnaps( this.verticalSnapKeys, actualTop, actualBottom, verticalCenter ); - if ( verticalBottomSnap !== null ) { - newSnapLines.push( ...this.verticalSnaps[ verticalBottomSnap ] ); + for ( const snap of _horizontalSnaps ) { + newSnapLines.push( ...this.horizontalSnaps[ snap ] ); } - if ( verticalCenterSnap !== null ) { - newSnapLines.push( ...this.verticalSnaps[ verticalCenterSnap ] ); + for ( const snap of _verticalSnaps ) { + newSnapLines.push( ...this.verticalSnaps[ snap ] ); } } From 8e1ebbdb3c57027e8bbc8a52bb067b5c47d2e7e1 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 30 Sep 2019 15:11:56 +0200 Subject: [PATCH 48/80] Leverage getRelativeElementPosition helper --- .../components/block-mover/draggable.js | 19 ++++++------------- .../higher-order/with-snap-targets.js | 19 ++++++++----------- .../components/resizable-box/index.js | 19 +++++-------------- assets/src/stories-editor/helpers/index.js | 6 ++++-- 4 files changed, 23 insertions(+), 40 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 177881b8be2..68de2ce1ba0 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -20,7 +20,7 @@ import { withSafeTimeout, compose } from '@wordpress/compose'; import withSnapTargets from '../higher-order/with-snap-targets'; import { getPixelsFromPercentage, - findClosestSnap, + findClosestSnap, getRelativeElementPosition, } from '../../helpers'; import { STORY_PAGE_INNER_WIDTH, @@ -94,8 +94,7 @@ class Draggable extends Component { snapLines, clearSnapLines, setSnapLines, - parentBlockOffsetTop, - parentBlockOffsetLeft, + parentBlockElement, } = this.props; const top = parseInt( this.cloneWrapper.style.top ) + event.clientY - this.cursorTop; @@ -106,20 +105,15 @@ class Draggable extends Component { } // Get the correct dimensions in case the block is rotated, as rotation is only applied to the clone's inner element(s). - const dimensions = this.cloneWrapper.querySelector( '.wp-block' ).getBoundingClientRect(); + const blockElement = this.cloneWrapper.querySelector( '.wp-block' ); // We calculate with the block's actual dimensions relative to the page it's on. - let { + const { top: actualTop, right: actualRight, bottom: actualBottom, left: actualLeft, - } = dimensions; - - actualTop -= parentBlockOffsetTop; - actualRight -= parentBlockOffsetLeft; - actualBottom -= parentBlockOffsetTop; - actualLeft -= parentBlockOffsetLeft; + } = getRelativeElementPosition( blockElement, parentBlockElement ); const horizontalCenter = actualLeft + ( ( actualRight - actualLeft ) / 2 ); const verticalCenter = actualTop + ( ( actualBottom - actualTop ) / 2 ); @@ -340,8 +334,7 @@ Draggable.propTypes = { hideSnapLines: PropTypes.func.isRequired, setSnapLines: PropTypes.func.isRequired, clearSnapLines: PropTypes.func.isRequired, - parentBlockOffsetTop: PropTypes.number.isRequired, - parentBlockOffsetLeft: PropTypes.number.isRequired, + parentBlockElement: PropTypes.object.isRequired, }; const enhance = compose( diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index 6b897fe7362..a4fe3dfe6f8 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -9,7 +9,7 @@ import isShallowEqual from '@wordpress/is-shallow-equal'; * Internal dependencies */ import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; -import { getBlockInnerElement } from '../../helpers'; +import { getBlockInnerElement, getRelativeElementPosition } from '../../helpers'; import { withSnapContext } from '../contexts/snapping'; /** @@ -50,8 +50,6 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { return defaultData; } - const { left: parentBlockOffsetLeft, top: parentBlockOffsetTop } = parentBlockElement.getBoundingClientRect(); - const siblings = getBlocksByClientId( getBlockOrder( parentBlock ) ).filter( ( { clientId: blockId } ) => blockId !== clientId ); const getVerticalLine = ( offsetX ) => [ [ offsetX, 0 ], [ offsetX, STORY_PAGE_INNER_HEIGHT ] ]; @@ -99,10 +97,10 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { continue; } - const { left, right } = blockElement.getBoundingClientRect(); + const { left, right } = getRelativeElementPosition( blockElement, parentBlockElement ); - snaps[ left - parentBlockOffsetLeft ] = getVerticalLine( left - parentBlockOffsetLeft ); - snaps[ right - parentBlockOffsetLeft ] = getVerticalLine( right - parentBlockOffsetLeft ); + snaps[ left ] = getVerticalLine( left ); + snaps[ right ] = getVerticalLine( right ); } return snaps; @@ -127,16 +125,15 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { continue; } - const { top, bottom } = blockElement.getBoundingClientRect(); + const { top, bottom } = getRelativeElementPosition( blockElement, parentBlockElement ); - snaps[ top - parentBlockOffsetTop ] = getHorizontalLine( top - parentBlockOffsetTop ); - snaps[ bottom - parentBlockOffsetTop ] = getHorizontalLine( bottom - parentBlockOffsetTop ); + snaps[ top ] = getHorizontalLine( top ); + snaps[ bottom ] = getHorizontalLine( bottom ); } return snaps; }, - parentBlockOffsetTop, - parentBlockOffsetLeft, + parentBlockElement, }; } ); diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 9dc451fe9e5..c763bfe1926 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -17,7 +17,7 @@ import withSnapTargets from '../higher-order/with-snap-targets'; import './edit.css'; import { getPercentageFromPixels, - findClosestSnap, + findClosestSnap, getRelativeElementPosition, } from '../../helpers'; import { getBlockPositioning, @@ -87,8 +87,7 @@ class EnhancedResizableBox extends Component { hideSnapLines, setSnapLines, clearSnapLines, - parentBlockOffsetTop, - parentBlockOffsetLeft, + parentBlockElement, ...childProps } = otherProps; @@ -247,20 +246,13 @@ class EnhancedResizableBox extends Component { lastDeltaW = deltaW; } - const dimensions = blockElement.getBoundingClientRect(); - // We calculate with the block's actual dimensions relative to the page it's on. - let { + const { top: actualTop, right: actualRight, bottom: actualBottom, left: actualLeft, - } = dimensions; - - actualTop -= parentBlockOffsetTop; - actualRight -= parentBlockOffsetLeft; - actualBottom -= parentBlockOffsetTop; - actualLeft -= parentBlockOffsetLeft; + } = getRelativeElementPosition( blockElement, parentBlockElement ); const horizontalCenter = actualLeft + ( ( actualRight - actualLeft ) / 2 ); const verticalCenter = actualTop + ( ( actualBottom - actualTop ) / 2 ); @@ -377,8 +369,7 @@ EnhancedResizableBox.propTypes = { hideSnapLines: PropTypes.func.isRequired, setSnapLines: PropTypes.func.isRequired, clearSnapLines: PropTypes.func.isRequired, - parentBlockOffsetTop: PropTypes.number.isRequired, - parentBlockOffsetLeft: PropTypes.number.isRequired, + parentBlockElement: PropTypes.object.isRequired, }; export default withSnapTargets( EnhancedResizableBox ); diff --git a/assets/src/stories-editor/helpers/index.js b/assets/src/stories-editor/helpers/index.js index e74da5136c1..129c479ed23 100644 --- a/assets/src/stories-editor/helpers/index.js +++ b/assets/src/stories-editor/helpers/index.js @@ -1966,12 +1966,14 @@ export const calculateTargetScalingFactor = ( width, height ) => { * * @return {{top: number, left: number}} Relative position of the block. */ -const getRelativeElementPosition = ( blockElement, parentElement ) => { +export const getRelativeElementPosition = ( blockElement, parentElement ) => { const { left: parentLeft, top: parentTop } = parentElement.getBoundingClientRect(); - const { top, left } = blockElement.getBoundingClientRect(); + const { top, right, bottom, left } = blockElement.getBoundingClientRect(); return { top: top - parentTop, + right: right - parentLeft, + bottom: bottom - parentTop, left: left - parentLeft, }; }; From 36d46a6391ae8df9d5d02ce0ba2915a2c66c3956 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 30 Sep 2019 15:27:04 +0200 Subject: [PATCH 49/80] prop types fixes --- assets/src/stories-editor/components/block-mover/draggable.js | 2 +- .../components/higher-order/with-snap-targets.js | 3 +-- assets/src/stories-editor/components/resizable-box/index.js | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 68de2ce1ba0..e863ddf392b 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -334,7 +334,7 @@ Draggable.propTypes = { hideSnapLines: PropTypes.func.isRequired, setSnapLines: PropTypes.func.isRequired, clearSnapLines: PropTypes.func.isRequired, - parentBlockElement: PropTypes.object.isRequired, + parentBlockElement: PropTypes.object, }; const enhance = compose( diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index a4fe3dfe6f8..db3d0b7a79d 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -36,8 +36,7 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { const defaultData = { horizontalSnaps: [], verticalSnaps: [], - parentBlockOffsetTop: 0, - parentBlockOffsetLeft: 0, + parentBlockElement: null, }; if ( getCurrentPage() !== parentBlock ) { diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index c763bfe1926..d926158c5d0 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -369,7 +369,7 @@ EnhancedResizableBox.propTypes = { hideSnapLines: PropTypes.func.isRequired, setSnapLines: PropTypes.func.isRequired, clearSnapLines: PropTypes.func.isRequired, - parentBlockElement: PropTypes.object.isRequired, + parentBlockElement: PropTypes.object, }; export default withSnapTargets( EnhancedResizableBox ); From 9e42965816ec3a77e4b1469983b2290f2104ccef Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 30 Sep 2019 15:27:32 +0200 Subject: [PATCH 50/80] Take rotation into account for snapping during resizing --- assets/src/stories-editor/components/resizable-box/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index d926158c5d0..d8b3673359e 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -238,7 +238,7 @@ class EnhancedResizableBox extends Component { } } - // Is it's not min width / height yet, assign lastDeltaH and lastDeltaW for position calculation. + // If it's not min width / height yet, assign lastDeltaH and lastDeltaW for position calculation. if ( minHeight < appliedHeight ) { lastDeltaH = deltaH; } @@ -246,13 +246,14 @@ class EnhancedResizableBox extends Component { lastDeltaW = deltaW; } + // Get the correct dimensions in case the block is rotated, as rotation is only applied to the clone's inner element(s). // We calculate with the block's actual dimensions relative to the page it's on. const { top: actualTop, right: actualRight, bottom: actualBottom, left: actualLeft, - } = getRelativeElementPosition( blockElement, parentBlockElement ); + } = getRelativeElementPosition( blockElement.querySelector( '.wp-block' ), parentBlockElement ); const horizontalCenter = actualLeft + ( ( actualRight - actualLeft ) / 2 ); const verticalCenter = actualTop + ( ( actualBottom - actualTop ) / 2 ); From ff01f934ff836a8bf7778f6f8598340a012b3703 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 2 Oct 2019 14:05:09 +0200 Subject: [PATCH 51/80] Lint fixes --- assets/src/stories-editor/components/block-mover/draggable.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index f581f4f04f6..d7b385d91f0 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -312,7 +312,6 @@ class Draggable extends Component { } Draggable.propTypes = { - clientId: PropTypes.string.isRequired, blockName: PropTypes.string, elementId: PropTypes.string, transferData: PropTypes.shape( { @@ -323,7 +322,6 @@ Draggable.propTypes = { } ), onDragStart: PropTypes.func, onDragEnd: PropTypes.func, - clearTimeout: PropTypes.func.isRequired, setTimeout: PropTypes.func.isRequired, children: PropTypes.node.isRequired, horizontalSnaps: PropTypes.oneOfType( [ From c3c196b6a9d4005d8e3fdb46e1c8714c126a8746 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 2 Oct 2019 15:41:19 +0200 Subject: [PATCH 52/80] Fix prop type for draggable --- assets/src/stories-editor/components/block-mover/draggable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index d7b385d91f0..83d501fb001 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -323,7 +323,7 @@ Draggable.propTypes = { onDragStart: PropTypes.func, onDragEnd: PropTypes.func, setTimeout: PropTypes.func.isRequired, - children: PropTypes.node.isRequired, + children: PropTypes.func.isRequired, horizontalSnaps: PropTypes.oneOfType( [ PropTypes.arrayOf( PropTypes.number ), PropTypes.func, From 0a7b350b0d1269b58a482b1a0173bace5ee7783c Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 2 Oct 2019 15:44:16 +0200 Subject: [PATCH 53/80] Fix prop type for BlockDraggable --- .../stories-editor/components/block-mover/block-draggable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/src/stories-editor/components/block-mover/block-draggable.js b/assets/src/stories-editor/components/block-mover/block-draggable.js index 31aff5da4e5..a5d71e35dfc 100644 --- a/assets/src/stories-editor/components/block-mover/block-draggable.js +++ b/assets/src/stories-editor/components/block-mover/block-draggable.js @@ -52,7 +52,7 @@ BlockDraggable.propTypes = { clientId: PropTypes.string, blockElementId: PropTypes.string, blockName: PropTypes.string, - children: PropTypes.node.isRequired, + children: PropTypes.func.isRequired, onDragStart: PropTypes.func, onDragEnd: PropTypes.func, }; From b66aa3bee38fdb5c553c0ec31ec77f33b5d4287d Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 2 Oct 2019 15:45:20 +0200 Subject: [PATCH 54/80] Use correct blockElement cor CTA block --- .../stories-editor/components/block-mover/draggable.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 83d501fb001..5c4c5f8c85a 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -20,7 +20,8 @@ import { withSafeTimeout, compose } from '@wordpress/compose'; import withSnapTargets from '../higher-order/with-snap-targets'; import { getPixelsFromPercentage, - findClosestSnap, getRelativeElementPosition, + findClosestSnap, + getRelativeElementPosition, } from '../../helpers'; import { STORY_PAGE_INNER_WIDTH, @@ -91,6 +92,7 @@ class Draggable extends Component { */ onDragOver = ( event ) => { // eslint-disable-line complexity const { + blockName, snapLines, clearSnapLines, setSnapLines, @@ -104,8 +106,11 @@ class Draggable extends Component { return; } + const isCTABlock = 'amp/amp-story-cta' === blockName; + // Get the correct dimensions in case the block is rotated, as rotation is only applied to the clone's inner element(s). - const blockElement = this.cloneWrapper.querySelector( '.wp-block' ); + // For CTA blocks, not the whole block is draggable, but only the button within. + const blockElement = isCTABlock ? this.cloneWrapper.querySelector( '.amp-story-cta-button' ) : this.cloneWrapper.querySelector( '.wp-block' ); // We calculate with the block's actual dimensions relative to the page it's on. const { From 81a41d8159d4a7cf0748b8cf48960ef88be0afeb Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 2 Oct 2019 16:17:52 +0200 Subject: [PATCH 55/80] Resizing: Prevent flickering by doing snapping _after_ correcting the position --- .../components/resizable-box/index.js | 57 +++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index d16154a1c70..375fbb9257c 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -241,6 +241,34 @@ class EnhancedResizableBox extends Component { lastDeltaW = deltaW; } + const radianAngle = getRadianFromDeg( angle ); + + // Compare position between the initial and after resizing. + let initialPosition, resizedPosition; + + // If it's a text block, we shouldn't consider the added padding for measuring. + if ( isText ) { + initialPosition = getBlockPositioning( width - ( TEXT_BLOCK_PADDING * 2 ), height - ( TEXT_BLOCK_PADDING * 2 ), radianAngle, direction ); + resizedPosition = getBlockPositioning( appliedWidth - ( TEXT_BLOCK_PADDING * 2 ), appliedHeight - ( TEXT_BLOCK_PADDING * 2 ), radianAngle, direction ); + } else { + initialPosition = getBlockPositioning( width, height, radianAngle, direction ); + resizedPosition = getBlockPositioning( appliedWidth, appliedHeight, radianAngle, direction ); + } + const diff = { + left: resizedPosition.left - initialPosition.left, + top: resizedPosition.top - initialPosition.top, + }; + + const originalPos = getResizedBlockPosition( direction, blockElementLeft, blockElementTop, lastDeltaW, lastDeltaH ); + const updatedPos = getUpdatedBlockPosition( direction, originalPos, diff ); + + + blockElement.style.left = getPercentageFromPixels( 'x', updatedPos.left ) + '%'; + blockElement.style.top = getPercentageFromPixels( 'y', updatedPos.top ) + '%'; + + element.style.width = appliedWidth + 'px'; + element.style.height = appliedHeight + 'px'; + // Get the correct dimensions in case the block is rotated, as rotation is only applied to the clone's inner element(s). // We calculate with the block's actual dimensions relative to the page it's on. const { @@ -276,35 +304,6 @@ class EnhancedResizableBox extends Component { } } - const radianAngle = getRadianFromDeg( angle ); - - // Compare position between the initial and after resizing. - let initialPosition, resizedPosition; - - // If it's a text block, we shouldn't consider the added padding for measuring. - if ( isText ) { - initialPosition = getBlockPositioning( width - ( TEXT_BLOCK_PADDING * 2 ), height - ( TEXT_BLOCK_PADDING * 2 ), radianAngle, direction ); - resizedPosition = getBlockPositioning( appliedWidth - ( TEXT_BLOCK_PADDING * 2 ), appliedHeight - ( TEXT_BLOCK_PADDING * 2 ), radianAngle, direction ); - } else { - initialPosition = getBlockPositioning( width, height, radianAngle, direction ); - resizedPosition = getBlockPositioning( appliedWidth, appliedHeight, radianAngle, direction ); - } - const diff = { - left: resizedPosition.left - initialPosition.left, - top: resizedPosition.top - initialPosition.top, - }; - - const originalPos = getResizedBlockPosition( direction, blockElementLeft, blockElementTop, lastDeltaW, lastDeltaH ); - const updatedPos = getUpdatedBlockPosition( direction, originalPos, diff ); - - // @todo: Do actual snapping. - - blockElement.style.left = getPercentageFromPixels( 'x', updatedPos.left ) + '%'; - blockElement.style.top = getPercentageFromPixels( 'y', updatedPos.top ) + '%'; - - element.style.width = appliedWidth + 'px'; - element.style.height = appliedHeight + 'px'; - lastWidth = appliedWidth; lastHeight = appliedHeight; From 75edd444331336e418c1e053028b45389848aa4a Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 2 Oct 2019 17:14:05 +0200 Subject: [PATCH 56/80] Lint fix --- assets/src/stories-editor/components/resizable-box/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 375fbb9257c..1e7efbf1610 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -262,7 +262,6 @@ class EnhancedResizableBox extends Component { const originalPos = getResizedBlockPosition( direction, blockElementLeft, blockElementTop, lastDeltaW, lastDeltaH ); const updatedPos = getUpdatedBlockPosition( direction, originalPos, diff ); - blockElement.style.left = getPercentageFromPixels( 'x', updatedPos.left ) + '%'; blockElement.style.top = getPercentageFromPixels( 'y', updatedPos.top ) + '%'; From 4fd6193d7ee4a91d573e042d08bdd256667cb7ea Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 3 Oct 2019 10:35:36 +0200 Subject: [PATCH 57/80] Move import to new line Co-Authored-By: Morten Barklund --- assets/src/stories-editor/components/resizable-box/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 1e7efbf1610..1eefb12f044 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -17,7 +17,8 @@ import withSnapTargets from '../higher-order/with-snap-targets'; import './edit.css'; import { getPercentageFromPixels, - findClosestSnap, getRelativeElementPosition, + findClosestSnap, + getRelativeElementPosition, } from '../../helpers'; import { getBlockPositioning, From eaef3d4a826dd4f822bb5a56a6d9f8f0fce5fb2f Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 3 Oct 2019 11:19:46 +0200 Subject: [PATCH 58/80] Fix selector in `getBlockInnerElement` This fixes an issue where snap lines in relation to CTA buttons were not shown. --- assets/src/stories-editor/helpers/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/src/stories-editor/helpers/index.js b/assets/src/stories-editor/helpers/index.js index 3c5d6556a70..de2c4605d7b 100644 --- a/assets/src/stories-editor/helpers/index.js +++ b/assets/src/stories-editor/helpers/index.js @@ -1200,7 +1200,7 @@ export const getBlockInnerElement = ( block ) => { if ( isCTABlock ) { // Not the block itself is movable, only the button within. - return document.querySelector( `amp-story-cta-button-${ clientId }` ); + return document.querySelector( `#amp-story-cta-button-${ clientId }` ); } return document.querySelector( `#block-${ clientId }` ); From 1198cbb3b4de8b08961d240379e1857fe5ea89c1 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 3 Oct 2019 11:47:18 +0200 Subject: [PATCH 59/80] UX improvements for snap targets * Show at most one snap guideline per axis to reduce visual noise * Show snap guidelines for the horizontal and vertical center of other blocks too * When snapping to another block, only draw the guideline from the current block to that block, not across the whole page --- .../components/block-mover/draggable.js | 39 ++++++++----- .../higher-order/with-snap-targets.js | 58 ++++++++++++------- .../components/resizable-box/index.js | 35 ++++++----- 3 files changed, 82 insertions(+), 50 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 5c4c5f8c85a..a74ff2bb83d 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -97,6 +97,8 @@ class Draggable extends Component { clearSnapLines, setSnapLines, parentBlockElement, + horizontalSnaps, + verticalSnaps, } = this.props; const top = parseInt( this.cloneWrapper.style.top ) + event.clientY - this.cursorTop; @@ -128,21 +130,33 @@ class Draggable extends Component { const snappingEnabled = ! event.getModifierState( 'Alt' ); if ( snappingEnabled ) { - const findSnaps = ( snapKeys, ...values ) => { + // Go through all snap targets and find the one that is closest. + const findSnap = ( snapKeys, ...values ) => { return values - .map( ( value ) => findClosestSnap( value, snapKeys, BLOCK_DRAGGING_SNAP_GAP ) ) - .filter( ( value ) => value !== null ); + .map( ( value ) => { + const snap = findClosestSnap( value, snapKeys, BLOCK_DRAGGING_SNAP_GAP ); + return [ value, snap ]; + } ) + .filter( ( arr ) => arr[ 1 ] !== null ) + .sort( ( a, b ) => a[ 1 ] - b[ 1 ] ) + .map( ( arr ) => arr[ 1 ] ) + .shift(); }; - const _horizontalSnaps = findSnaps( this.horizontalSnapKeys, actualLeft, actualRight, horizontalCenter ); - const _verticalSnaps = findSnaps( this.verticalSnapKeys, actualTop, actualBottom, verticalCenter ); + const _horizontalSnaps = horizontalSnaps( actualTop, actualBottom ); + const _horizontalSnapKeys = Object.keys( _horizontalSnaps ); + const _verticalSnaps = verticalSnaps( actualLeft, actualRight ); + const _verticalSnapKeys = Object.keys( _verticalSnaps ); + + const horizontalSnap = findSnap( _horizontalSnapKeys, actualLeft, actualRight, horizontalCenter ); + const verticalSnap = findSnap( _verticalSnapKeys, actualTop, actualBottom, verticalCenter ); - for ( const snap of _horizontalSnaps ) { - newSnapLines.push( ...this.horizontalSnaps[ snap ] ); + if ( horizontalSnap !== undefined ) { + newSnapLines.push( ..._horizontalSnaps[ horizontalSnap ] ); } - for ( const snap of _verticalSnaps ) { - newSnapLines.push( ...this.verticalSnaps[ snap ] ); + if ( verticalSnap !== undefined ) { + newSnapLines.push( ..._verticalSnaps[ verticalSnap ] ); } } @@ -191,8 +205,6 @@ class Draggable extends Component { snapLines, showSnapLines, clearSnapLines, - horizontalSnaps, - verticalSnaps, } = this.props; const isCTABlock = 'amp/amp-story-cta' === blockName; // In the CTA block only the inner element (the button) is draggable, not the whole block. @@ -277,11 +289,6 @@ class Draggable extends Component { clearSnapLines(); } - this.horizontalSnaps = horizontalSnaps(); - this.horizontalSnapKeys = Object.keys( this.horizontalSnaps ); - this.verticalSnaps = verticalSnaps(); - this.verticalSnapKeys = Object.keys( this.verticalSnaps ); - this.props.setTimeout( onDragStart ); } diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index db3d0b7a79d..e02f211bef9 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -49,12 +49,15 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { return defaultData; } - const siblings = getBlocksByClientId( getBlockOrder( parentBlock ) ).filter( ( { clientId: blockId } ) => blockId !== clientId ); + const siblings = getBlocksByClientId( getBlockOrder( parentBlock ) ) + .filter( ( { clientId: blockId } ) => blockId !== clientId ) + .filter( getBlockInnerElement ); - const getVerticalLine = ( offsetX ) => [ [ offsetX, 0 ], [ offsetX, STORY_PAGE_INNER_HEIGHT ] ]; - const getHorizontalLine = ( offsetY ) => [ [ 0, offsetY ], [ STORY_PAGE_INNER_WIDTH, offsetY ] ]; + const getVerticalLine = ( offsetX, start = 0, end = STORY_PAGE_INNER_HEIGHT ) => [ [ offsetX, start ], [ offsetX, end ] ]; + const getHorizontalLine = ( offsetY, start = 0, end = STORY_PAGE_INNER_WIDTH ) => [ [ start, offsetY ], [ end, offsetY ] ]; - // Setter used for the proxied objects. + // Setter used for the proxied snap target objects. + // Prevents duplicates and sets upper and lower boundaries. const proxySet = ( obj, prop, value ) => { prop = Math.round( prop ); @@ -76,7 +79,14 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { }; return { - horizontalSnaps: () => { + /** + * Horizontal snap function. + * + * @param {number} targetTop The top position of the currently dragged/resized block. + * @param {number} targetBottom The bottom position of the currently dragged/resized block. + * @return {Object.>>} Dictionary with horizontal snap targets. + */ + horizontalSnaps: ( targetTop, targetBottom ) => { const snaps = new Proxy( { // Left page border. 0: [ getVerticalLine( 0 ) ], @@ -91,20 +101,28 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { for ( const block of siblings ) { const blockElement = getBlockInnerElement( block ); + const { top, right, bottom, left } = getRelativeElementPosition( blockElement, parentBlockElement ); + const center = left + ( ( right - left ) / 2 ); - if ( ! blockElement ) { - continue; - } + const start = targetTop < top ? targetTop : top; + const end = targetBottom > bottom ? targetBottom : bottom; - const { left, right } = getRelativeElementPosition( blockElement, parentBlockElement ); - - snaps[ left ] = getVerticalLine( left ); - snaps[ right ] = getVerticalLine( right ); + snaps[ left ] = getVerticalLine( left, start, end ); + snaps[ right ] = getVerticalLine( right, start, end ); + snaps[ center ] = getVerticalLine( center, start, end ); } return snaps; }, - verticalSnaps: () => { + + /** + * Vertical snap function. + * + * @param {number} targetLeft The left position of the currently dragged/resized block. + * @param {number} targetRight The right position of the currently dragged/resized block. + * @return {Object.>>} Dictionary with vertical snap targets. + */ + verticalSnaps: ( targetLeft, targetRight ) => { const snaps = new Proxy( { // Top page border. 0: [ getHorizontalLine( 0 ) ], @@ -119,15 +137,15 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { for ( const block of siblings ) { const blockElement = getBlockInnerElement( block ); + const { top, right, bottom, left } = getRelativeElementPosition( blockElement, parentBlockElement ); + const center = top + ( ( bottom - top ) / 2 ); - if ( ! blockElement ) { - continue; - } - - const { top, bottom } = getRelativeElementPosition( blockElement, parentBlockElement ); + const start = targetLeft < left ? targetLeft : left; + const end = targetRight > right ? targetRight : right; - snaps[ top ] = getHorizontalLine( top ); - snaps[ bottom ] = getHorizontalLine( bottom ); + snaps[ top ] = getHorizontalLine( top, start, end ); + snaps[ bottom ] = getHorizontalLine( bottom, start, end ); + snaps[ center ] = getHorizontalLine( center, start, end ); } return snaps; diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 1eefb12f044..3b9b73b6104 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -177,11 +177,6 @@ class EnhancedResizableBox extends Component { clearSnapLines(); } - this.horizontalSnaps = horizontalSnaps(); - this.horizontalSnapKeys = Object.keys( this.horizontalSnaps ); - this.verticalSnaps = verticalSnaps(); - this.verticalSnapKeys = Object.keys( this.verticalSnaps ); - onResizeStart(); } } onResize={ ( event, direction, element ) => { // eslint-disable-line complexity @@ -286,21 +281,33 @@ class EnhancedResizableBox extends Component { const snappingEnabled = ! event.getModifierState( 'Alt' ); if ( snappingEnabled ) { - const findSnaps = ( snapKeys, ...values ) => { + // Go through all snap targets and find the one that is closest. + const findSnap = ( snapKeys, ...values ) => { return values - .map( ( value ) => findClosestSnap( value, snapKeys, BLOCK_RESIZING_SNAP_GAP ) ) - .filter( ( value ) => value !== null ); + .map( ( value ) => { + const snap = findClosestSnap( value, snapKeys, BLOCK_RESIZING_SNAP_GAP ); + return [ value, snap ]; + } ) + .filter( ( arr ) => arr[ 1 ] !== null ) + .sort( ( a, b ) => a[ 1 ] - b[ 1 ] ) + .map( ( arr ) => arr[ 1 ] ) + .shift(); }; - const _horizontalSnaps = findSnaps( this.horizontalSnapKeys, actualLeft, actualRight, horizontalCenter ); - const _verticalSnaps = findSnaps( this.verticalSnapKeys, actualTop, actualBottom, verticalCenter ); + const _horizontalSnaps = horizontalSnaps( actualTop, actualBottom ); + const _horizontalSnapKeys = Object.keys( _horizontalSnaps ); + const _verticalSnaps = verticalSnaps( actualLeft, actualRight ); + const _verticalSnapKeys = Object.keys( _verticalSnaps ); + + const horizontalSnap = findSnap( _horizontalSnapKeys, actualLeft, actualRight, horizontalCenter ); + const verticalSnap = findSnap( _verticalSnapKeys, actualTop, actualBottom, verticalCenter ); - for ( const snap of _horizontalSnaps ) { - newSnapLines.push( ...this.horizontalSnaps[ snap ] ); + if ( horizontalSnap !== undefined ) { + newSnapLines.push( ..._horizontalSnaps[ horizontalSnap ] ); } - for ( const snap of _verticalSnaps ) { - newSnapLines.push( ...this.verticalSnaps[ snap ] ); + if ( verticalSnap !== undefined ) { + newSnapLines.push( ..._verticalSnaps[ verticalSnap ] ); } } From e9938c8aceb42567477f25aa7770a7d777b7ee54 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 3 Oct 2019 14:44:26 +0200 Subject: [PATCH 60/80] Use correct lower and upper bounds for horizontal/vertical snap lines --- .../higher-order/with-snap-targets.js | 49 ++++++++++++------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index e02f211bef9..fabad918ea2 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -56,26 +56,39 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { const getVerticalLine = ( offsetX, start = 0, end = STORY_PAGE_INNER_HEIGHT ) => [ [ offsetX, start ], [ offsetX, end ] ]; const getHorizontalLine = ( offsetY, start = 0, end = STORY_PAGE_INNER_WIDTH ) => [ [ start, offsetY ], [ end, offsetY ] ]; - // Setter used for the proxied snap target objects. - // Prevents duplicates and sets upper and lower boundaries. - const proxySet = ( obj, prop, value ) => { - prop = Math.round( prop ); - - if ( prop < 0 || prop > STORY_PAGE_INNER_WIDTH ) { - return true; - } + /** + * Returns the setter used for the proxied snap target objects. + * + * The setter is a small helper that: + * + * - Prevents duplicates + * - Keeps snap targets within lower and upper bounds. + * - Combines snap lines if there are multiple for a given target + * + * @param {number} lowerBound Lower limit for snap targets, i.e. the page dimensions. + * @param {number} upperBound Upper limit for snap targets, i.e. the page dimensions. + * @return {Function} Proxy setter. + */ + const getSetter = ( lowerBound, upperBound ) => { + return ( obj, prop, value ) => { + prop = Math.round( prop ); + + if ( prop < lowerBound || prop > upperBound ) { + return true; + } - const hasSnapLine = ( item ) => obj[ prop ].find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); + const hasSnapLine = ( item ) => obj[ prop ].find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); - if ( obj.hasOwnProperty( prop ) && ! hasSnapLine( value ) ) { - obj[ prop ].push( value ); - } else { - obj[ prop ] = [ value ]; - } + if ( obj.hasOwnProperty( prop ) && ! hasSnapLine( value ) ) { + obj[ prop ].push( value ); + } else { + obj[ prop ] = [ value ]; + } - obj[ prop ] = obj[ prop ].sort(); + obj[ prop ] = obj[ prop ].sort(); - return true; + return true; + }; }; return { @@ -96,7 +109,7 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { [ STORY_PAGE_INNER_WIDTH ]: [ getVerticalLine( STORY_PAGE_INNER_WIDTH ) ], }, { - set: proxySet, + set: getSetter( 0, STORY_PAGE_INNER_WIDTH ), } ); for ( const block of siblings ) { @@ -132,7 +145,7 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { [ STORY_PAGE_INNER_HEIGHT ]: [ getHorizontalLine( STORY_PAGE_INNER_HEIGHT ) ], }, { - set: proxySet, + set: getSetter( 0, STORY_PAGE_INNER_HEIGHT ), } ); for ( const block of siblings ) { From cb3ab61c28946d009b1b9aabf8541446c666b4c8 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 3 Oct 2019 16:26:21 +0200 Subject: [PATCH 61/80] Make sure snap props are always functions --- .../stories-editor/components/block-mover/draggable.js | 10 ++-------- .../components/higher-order/with-snap-targets.js | 4 ++-- .../stories-editor/components/resizable-box/index.js | 10 ++-------- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index a74ff2bb83d..e6a3af61aab 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -336,14 +336,8 @@ Draggable.propTypes = { onDragEnd: PropTypes.func, setTimeout: PropTypes.func.isRequired, children: PropTypes.func.isRequired, - horizontalSnaps: PropTypes.oneOfType( [ - PropTypes.arrayOf( PropTypes.number ), - PropTypes.func, - ] ).isRequired, - verticalSnaps: PropTypes.oneOfType( [ - PropTypes.arrayOf( PropTypes.number ), - PropTypes.func, - ] ).isRequired, + horizontalSnaps: PropTypes.func.isRequired, + verticalSnaps: PropTypes.func.isRequired, snapLines: PropTypes.array.isRequired, showSnapLines: PropTypes.func.isRequired, hideSnapLines: PropTypes.func.isRequired, diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index fabad918ea2..b93c9a00282 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -34,8 +34,8 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { const parentBlock = getBlockRootClientId( clientId ); const defaultData = { - horizontalSnaps: [], - verticalSnaps: [], + horizontalSnaps: () => [], + verticalSnaps: () => [], parentBlockElement: null, }; diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index b675d740d11..859c34f60b3 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -357,14 +357,8 @@ EnhancedResizableBox.propTypes = { children: PropTypes.node.isRequired, width: PropTypes.number, height: PropTypes.number, - horizontalSnaps: PropTypes.oneOfType( [ - PropTypes.arrayOf( PropTypes.number ), - PropTypes.func, - ] ).isRequired, - verticalSnaps: PropTypes.oneOfType( [ - PropTypes.arrayOf( PropTypes.number ), - PropTypes.func, - ] ).isRequired, + horizontalSnaps: PropTypes.func.isRequired, + verticalSnaps: PropTypes.func.isRequired, snapGap: PropTypes.number.isRequired, snapLines: PropTypes.array.isRequired, showSnapLines: PropTypes.func.isRequired, From dc8f7d18c58e887881a0cb405b14a48a7928771c Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 3 Oct 2019 16:26:58 +0200 Subject: [PATCH 62/80] Reduce duplicated code --- .../components/higher-order/with-snap-targets.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index b93c9a00282..d07c73649f5 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -51,7 +51,8 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { const siblings = getBlocksByClientId( getBlockOrder( parentBlock ) ) .filter( ( { clientId: blockId } ) => blockId !== clientId ) - .filter( getBlockInnerElement ); + .map( getBlockInnerElement ) + .filter( Boolean ); const getVerticalLine = ( offsetX, start = 0, end = STORY_PAGE_INNER_HEIGHT ) => [ [ offsetX, start ], [ offsetX, end ] ]; const getHorizontalLine = ( offsetY, start = 0, end = STORY_PAGE_INNER_WIDTH ) => [ [ start, offsetY ], [ end, offsetY ] ]; @@ -112,8 +113,7 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { set: getSetter( 0, STORY_PAGE_INNER_WIDTH ), } ); - for ( const block of siblings ) { - const blockElement = getBlockInnerElement( block ); + for ( const blockElement of siblings ) { const { top, right, bottom, left } = getRelativeElementPosition( blockElement, parentBlockElement ); const center = left + ( ( right - left ) / 2 ); @@ -148,8 +148,7 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { set: getSetter( 0, STORY_PAGE_INNER_HEIGHT ), } ); - for ( const block of siblings ) { - const blockElement = getBlockInnerElement( block ); + for ( const blockElement of siblings ) { const { top, right, bottom, left } = getRelativeElementPosition( blockElement, parentBlockElement ); const center = top + ( ( bottom - top ) / 2 ); From 0170cd8aec074d54dd7035f0307c1626006cc831 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 3 Oct 2019 16:54:23 +0200 Subject: [PATCH 63/80] Simplify center and start/end calculations --- .../components/block-mover/draggable.js | 4 ++-- .../components/higher-order/with-snap-targets.js | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index e6a3af61aab..71a96e64203 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -122,8 +122,8 @@ class Draggable extends Component { left: actualLeft, } = getRelativeElementPosition( blockElement, parentBlockElement ); - const horizontalCenter = actualLeft + ( ( actualRight - actualLeft ) / 2 ); - const verticalCenter = actualTop + ( ( actualBottom - actualTop ) / 2 ); + const horizontalCenter = ( actualRight + actualLeft ) / 2; + const verticalCenter = ( actualTop + actualBottom ) / 2; const newSnapLines = []; diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index d07c73649f5..cbe13488766 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -115,10 +115,10 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { for ( const blockElement of siblings ) { const { top, right, bottom, left } = getRelativeElementPosition( blockElement, parentBlockElement ); - const center = left + ( ( right - left ) / 2 ); + const center = ( top + bottom ) / 2; - const start = targetTop < top ? targetTop : top; - const end = targetBottom > bottom ? targetBottom : bottom; + const start = Math.min( targetTop, top ); + const end = Math.max( targetBottom, bottom ); snaps[ left ] = getVerticalLine( left, start, end ); snaps[ right ] = getVerticalLine( right, start, end ); @@ -150,10 +150,10 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { for ( const blockElement of siblings ) { const { top, right, bottom, left } = getRelativeElementPosition( blockElement, parentBlockElement ); - const center = top + ( ( bottom - top ) / 2 ); + const center = ( top + bottom ) / 2; - const start = targetLeft < left ? targetLeft : left; - const end = targetRight > right ? targetRight : right; + const start = Math.min( targetLeft, left ); + const end = Math.max( targetRight, right ); snaps[ top ] = getHorizontalLine( top, start, end ); snaps[ bottom ] = getHorizontalLine( bottom, start, end ); From 2b1f9778387ea0a30864452e7f77fff44697d5ec Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 3 Oct 2019 17:04:21 +0200 Subject: [PATCH 64/80] Get rid of showSnapLines / hideSnapLines logic --- .../components/block-mover/draggable.js | 7 +- .../components/contexts/snapping.js | 7 +- .../test/__snapshots__/snapping.js.snap | 2 +- .../components/contexts/test/snapping.js | 64 ++----------------- .../components/resizable-box/index.js | 6 -- 5 files changed, 9 insertions(+), 77 deletions(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 71a96e64203..315c1071499 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -70,14 +70,13 @@ class Draggable extends Component { * @param {Object} event The non-custom DragEvent. */ onDragEnd = ( event ) => { - const { onDragEnd = noop, snapLines, hideSnapLines, clearSnapLines } = this.props; + const { onDragEnd = noop, snapLines, clearSnapLines } = this.props; if ( event ) { event.preventDefault(); } this.resetDragState(); - hideSnapLines(); if ( snapLines.length ) { clearSnapLines(); } @@ -203,7 +202,6 @@ class Draggable extends Component { transferData, onDragStart = noop, snapLines, - showSnapLines, clearSnapLines, } = this.props; const isCTABlock = 'amp/amp-story-cta' === blockName; @@ -284,7 +282,6 @@ class Draggable extends Component { document.addEventListener( 'drop', this.onDrop ); } - showSnapLines(); if ( snapLines.length ) { clearSnapLines(); } @@ -339,8 +336,6 @@ Draggable.propTypes = { horizontalSnaps: PropTypes.func.isRequired, verticalSnaps: PropTypes.func.isRequired, snapLines: PropTypes.array.isRequired, - showSnapLines: PropTypes.func.isRequired, - hideSnapLines: PropTypes.func.isRequired, setSnapLines: PropTypes.func.isRequired, clearSnapLines: PropTypes.func.isRequired, parentBlockElement: PropTypes.object, diff --git a/assets/src/stories-editor/components/contexts/snapping.js b/assets/src/stories-editor/components/contexts/snapping.js index d106277cff0..033308f58e9 100644 --- a/assets/src/stories-editor/components/contexts/snapping.js +++ b/assets/src/stories-editor/components/contexts/snapping.js @@ -19,15 +19,10 @@ const SnapContext = createContext(); const Snapping = ( { children } ) => { const [ snapLines, setSnapLines ] = useState( [] ); - const [ hasSnapLines, setHasSnapLines ] = useState( false ); - const showSnapLines = () => setHasSnapLines( true ); - const hideSnapLines = () => setHasSnapLines( false ); const clearSnapLines = () => setSnapLines( [] ); const context = { - showSnapLines, - hideSnapLines, setSnapLines, snapLines, clearSnapLines, @@ -36,7 +31,7 @@ const Snapping = ( { children } ) => { return ( { children } - { Boolean( hasSnapLines && snapLines.length ) && ( + { Boolean( snapLines.length ) && ( { const snapProps = wrapper.find( Dummy ).props(); const callbacks = [ 'setSnapLines', - 'showSnapLines', - 'hideSnapLines', 'clearSnapLines', ]; @@ -62,31 +60,19 @@ describe( 'Snapping', () => { expect( displayedSnapLines ).toHaveLength( 0 ); } ); - it( 'should not display any snap lines when set but not shown', () => { + it( 'should display snap lines when set', () => { const { setSnapLines, getDisplayedSnapLines } = setup(); setSnapLines( [ VERTICAL_SNAP_LINE, HORIZONTAL_SNAP_LINE ] ); const displayedSnapLines = getDisplayedSnapLines(); - expect( displayedSnapLines ).toHaveLength( 0 ); - } ); - - it( 'should display snap lines when shown and set', () => { - const { setSnapLines, showSnapLines, getDisplayedSnapLines } = setup(); - - showSnapLines(); - setSnapLines( [ VERTICAL_SNAP_LINE, HORIZONTAL_SNAP_LINE ] ); - - const displayedSnapLines = getDisplayedSnapLines(); - expect( displayedSnapLines ).toHaveLength( 2 ); } ); - it( 'should render a single snap line correctly when shown and set', () => { - const { setSnapLines, showSnapLines, getDisplayedSnapLines } = setup(); + it( 'should render a single snap line correctly when set', () => { + const { setSnapLines, getDisplayedSnapLines } = setup(); - showSnapLines(); setSnapLines( [ HORIZONTAL_SNAP_LINE ] ); const displayedSnapLines = getDisplayedSnapLines(); @@ -94,10 +80,9 @@ describe( 'Snapping', () => { expect( displayedSnapLines ).toMatchSnapshot(); } ); - it( 'should display only new snap lines when shown, set and set again', () => { - const { setSnapLines, showSnapLines, getDisplayedSnapLines } = setup(); + it( 'should display only new snap lines when set and set again', () => { + const { setSnapLines, getDisplayedSnapLines } = setup(); - showSnapLines(); setSnapLines( [ VERTICAL_SNAP_LINE, HORIZONTAL_SNAP_LINE ] ); setSnapLines( [ VERTICAL_SNAP_LINE ] ); @@ -106,51 +91,14 @@ describe( 'Snapping', () => { expect( displayedSnapLines ).toHaveLength( 1 ); } ); - it( 'should not display any snap lines when set, shown and then hidden', () => { - const { - setSnapLines, - showSnapLines, - hideSnapLines, - getDisplayedSnapLines, - } = setup(); - - setSnapLines( [ VERTICAL_SNAP_LINE, HORIZONTAL_SNAP_LINE ] ); - showSnapLines(); - hideSnapLines(); - - const displayedSnapLines = getDisplayedSnapLines(); - - expect( displayedSnapLines ).toHaveLength( 0 ); - } ); - - it( 'should display snap lines when set, shown, hidden and then shown', () => { - const { - setSnapLines, - showSnapLines, - hideSnapLines, - getDisplayedSnapLines, - } = setup(); - - setSnapLines( [ VERTICAL_SNAP_LINE, HORIZONTAL_SNAP_LINE ] ); - showSnapLines(); - hideSnapLines(); - showSnapLines(); - - const displayedSnapLines = getDisplayedSnapLines(); - - expect( displayedSnapLines ).toHaveLength( 2 ); - } ); - - it( 'should not display any snap lines when set, shown and cleared', () => { + it( 'should not display any snap lines when set and cleared', () => { const { setSnapLines, - showSnapLines, clearSnapLines, getDisplayedSnapLines, } = setup(); setSnapLines( [ VERTICAL_SNAP_LINE, HORIZONTAL_SNAP_LINE ] ); - showSnapLines(); clearSnapLines(); const displayedSnapLines = getDisplayedSnapLines(); diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 859c34f60b3..0c83422f9b8 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -84,8 +84,6 @@ class EnhancedResizableBox extends Component { horizontalSnaps, verticalSnaps, snapLines, - showSnapLines, - hideSnapLines, setSnapLines, clearSnapLines, parentBlockElement, @@ -137,7 +135,6 @@ class EnhancedResizableBox extends Component { this.setState( { isResizing: false } ); - hideSnapLines(); if ( snapLines.length ) { clearSnapLines(); } @@ -172,7 +169,6 @@ class EnhancedResizableBox extends Component { this.setState( { isResizing: true } ); - showSnapLines(); if ( snapLines.length ) { clearSnapLines(); } @@ -361,8 +357,6 @@ EnhancedResizableBox.propTypes = { verticalSnaps: PropTypes.func.isRequired, snapGap: PropTypes.number.isRequired, snapLines: PropTypes.array.isRequired, - showSnapLines: PropTypes.func.isRequired, - hideSnapLines: PropTypes.func.isRequired, setSnapLines: PropTypes.func.isRequired, clearSnapLines: PropTypes.func.isRequired, parentBlockElement: PropTypes.object, From 5ae88e72f7db5f9598f2567db3a77e0af198ed43 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 3 Oct 2019 18:19:33 +0200 Subject: [PATCH 65/80] Make sure selector is mocked when importing function --- .../helpers/test/calculateTargetScalingFactor.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/assets/src/stories-editor/helpers/test/calculateTargetScalingFactor.js b/assets/src/stories-editor/helpers/test/calculateTargetScalingFactor.js index 876bdc37ac6..899e26bda38 100644 --- a/assets/src/stories-editor/helpers/test/calculateTargetScalingFactor.js +++ b/assets/src/stories-editor/helpers/test/calculateTargetScalingFactor.js @@ -4,6 +4,16 @@ import { calculateTargetScalingFactor } from '../setAnimationTransformProperties'; import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; +const mockGetBlockRootClientId = jest.fn( () => [] ); + +jest.mock( '@wordpress/data', () => { + return { + select: () => ( { + getBlockRootClientId: ( ...args ) => mockGetBlockRootClientId( ...args ), + } ), + }; +} ); + describe( 'calculateTargetScalingFactor', () => { it.each( [ [ STORY_PAGE_INNER_WIDTH, STORY_PAGE_INNER_HEIGHT, 1.25 ], From 9b4eb81f77df6c05baff061bf2fd31313bf43fb4 Mon Sep 17 00:00:00 2001 From: Morten Barklund Date: Thu, 3 Oct 2019 18:27:31 -0400 Subject: [PATCH 66/80] Moved snap line calculation to helper functions This moved all the snap line calculations to a set of external helper functions. It's split in the way it is to enable unit testing of all relevant components separately. Unit tests haven't been added yet though. Unit testing `getSnapCalculatorByDimension` is going to be slightly mind-blowing. --- .../higher-order/with-snap-targets.js | 124 ++---------------- .../stories-editor/helpers/createSnapList.js | 75 +++++++++++ .../helpers/getHorizontalSnaps.js | 20 +++ .../helpers/getSnapCalculatorByDimension.js | 49 +++++++ .../helpers/getVerticalSnaps.js | 20 +++ assets/src/stories-editor/helpers/index.js | 2 + 6 files changed, 179 insertions(+), 111 deletions(-) create mode 100644 assets/src/stories-editor/helpers/createSnapList.js create mode 100644 assets/src/stories-editor/helpers/getHorizontalSnaps.js create mode 100644 assets/src/stories-editor/helpers/getSnapCalculatorByDimension.js create mode 100644 assets/src/stories-editor/helpers/getVerticalSnaps.js diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index cbe13488766..92c56d7d02e 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -3,13 +3,16 @@ */ import { withSelect } from '@wordpress/data'; import { compose, createHigherOrderComponent } from '@wordpress/compose'; -import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies */ -import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; -import { getBlockInnerElement, getRelativeElementPosition } from '../../helpers'; +import { + getBlockInnerElement, + getRelativeElementPosition, + getHorizontalSnaps, + getVerticalSnaps, +} from '../../helpers'; import { withSnapContext } from '../contexts/snapping'; /** @@ -49,119 +52,18 @@ const applyWithSelect = withSelect( ( select, { clientId } ) => { return defaultData; } - const siblings = getBlocksByClientId( getBlockOrder( parentBlock ) ) + const siblingPositions = getBlocksByClientId( getBlockOrder( parentBlock ) ) .filter( ( { clientId: blockId } ) => blockId !== clientId ) .map( getBlockInnerElement ) - .filter( Boolean ); + .filter( Boolean ) + .map( ( el ) => getRelativeElementPosition( el, parentBlockElement ) ); - const getVerticalLine = ( offsetX, start = 0, end = STORY_PAGE_INNER_HEIGHT ) => [ [ offsetX, start ], [ offsetX, end ] ]; - const getHorizontalLine = ( offsetY, start = 0, end = STORY_PAGE_INNER_WIDTH ) => [ [ start, offsetY ], [ end, offsetY ] ]; - - /** - * Returns the setter used for the proxied snap target objects. - * - * The setter is a small helper that: - * - * - Prevents duplicates - * - Keeps snap targets within lower and upper bounds. - * - Combines snap lines if there are multiple for a given target - * - * @param {number} lowerBound Lower limit for snap targets, i.e. the page dimensions. - * @param {number} upperBound Upper limit for snap targets, i.e. the page dimensions. - * @return {Function} Proxy setter. - */ - const getSetter = ( lowerBound, upperBound ) => { - return ( obj, prop, value ) => { - prop = Math.round( prop ); - - if ( prop < lowerBound || prop > upperBound ) { - return true; - } - - const hasSnapLine = ( item ) => obj[ prop ].find( ( snapLine ) => isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && isShallowEqual( item[ 1 ], snapLine[ 1 ] ) ); - - if ( obj.hasOwnProperty( prop ) && ! hasSnapLine( value ) ) { - obj[ prop ].push( value ); - } else { - obj[ prop ] = [ value ]; - } - - obj[ prop ] = obj[ prop ].sort(); - - return true; - }; - }; + const horizontalSnaps = getHorizontalSnaps( siblingPositions ); + const verticalSnaps = getVerticalSnaps( siblingPositions ); return { - /** - * Horizontal snap function. - * - * @param {number} targetTop The top position of the currently dragged/resized block. - * @param {number} targetBottom The bottom position of the currently dragged/resized block. - * @return {Object.>>} Dictionary with horizontal snap targets. - */ - horizontalSnaps: ( targetTop, targetBottom ) => { - const snaps = new Proxy( { - // Left page border. - 0: [ getVerticalLine( 0 ) ], - // Center of the page. - [ STORY_PAGE_INNER_WIDTH / 2 ]: [ getVerticalLine( STORY_PAGE_INNER_WIDTH / 2 ) ], - // Right page border. - [ STORY_PAGE_INNER_WIDTH ]: [ getVerticalLine( STORY_PAGE_INNER_WIDTH ) ], - }, - { - set: getSetter( 0, STORY_PAGE_INNER_WIDTH ), - } ); - - for ( const blockElement of siblings ) { - const { top, right, bottom, left } = getRelativeElementPosition( blockElement, parentBlockElement ); - const center = ( top + bottom ) / 2; - - const start = Math.min( targetTop, top ); - const end = Math.max( targetBottom, bottom ); - - snaps[ left ] = getVerticalLine( left, start, end ); - snaps[ right ] = getVerticalLine( right, start, end ); - snaps[ center ] = getVerticalLine( center, start, end ); - } - - return snaps; - }, - - /** - * Vertical snap function. - * - * @param {number} targetLeft The left position of the currently dragged/resized block. - * @param {number} targetRight The right position of the currently dragged/resized block. - * @return {Object.>>} Dictionary with vertical snap targets. - */ - verticalSnaps: ( targetLeft, targetRight ) => { - const snaps = new Proxy( { - // Top page border. - 0: [ getHorizontalLine( 0 ) ], - // Center of the page. - [ STORY_PAGE_INNER_HEIGHT / 2 ]: [ getHorizontalLine( STORY_PAGE_INNER_HEIGHT / 2 ) ], - // Bottom page border. - [ STORY_PAGE_INNER_HEIGHT ]: [ getHorizontalLine( STORY_PAGE_INNER_HEIGHT ) ], - }, - { - set: getSetter( 0, STORY_PAGE_INNER_HEIGHT ), - } ); - - for ( const blockElement of siblings ) { - const { top, right, bottom, left } = getRelativeElementPosition( blockElement, parentBlockElement ); - const center = ( top + bottom ) / 2; - - const start = Math.min( targetLeft, left ); - const end = Math.max( targetRight, right ); - - snaps[ top ] = getHorizontalLine( top, start, end ); - snaps[ bottom ] = getHorizontalLine( bottom, start, end ); - snaps[ center ] = getHorizontalLine( center, start, end ); - } - - return snaps; - }, + horizontalSnaps, + verticalSnaps, parentBlockElement, }; } ); diff --git a/assets/src/stories-editor/helpers/createSnapList.js b/assets/src/stories-editor/helpers/createSnapList.js new file mode 100644 index 00000000000..38e0b2bf6bd --- /dev/null +++ b/assets/src/stories-editor/helpers/createSnapList.js @@ -0,0 +1,75 @@ +/** + * WordPress dependencies + */ +import isShallowEqual from '@wordpress/is-shallow-equal'; + +/** + * Returns the setter used for the proxied snap target objects. + * + * The setter is a small helper that: + * + * - Prevents duplicates + * - Keeps snap targets within lower and upper bounds. + * - Combines snap lines if there are multiple for a given target + * + * @param {number} lowerBound Lower limit for snap targets, i.e. the page dimensions. + * @param {number} upperBound Upper limit for snap targets, i.e. the page dimensions. + * @return {Function} Proxy setter. + */ +const getSetter = ( lowerBound, upperBound ) => ( obj, prop, value ) => { + prop = Math.round( prop ); + + if ( prop < lowerBound || prop > upperBound ) { + // Discard any snap lines outside the page. + return true; + } + + const hasSnapLine = ( item ) => + obj[ prop ] + .find( ( snapLine ) => + isShallowEqual( item[ 0 ], snapLine[ 0 ] ) && + isShallowEqual( item[ 1 ], snapLine[ 1 ] ) + ); + + if ( obj.hasOwnProperty( prop ) ) { + if ( ! hasSnapLine( value ) ) { + // We already have a completely identical snap line. + return true; + } + + // We have at least one snap line at this position, add new to the list. + obj[ prop ].push( value ); + } else { + // First snap lines at this position, create new list. + obj[ prop ] = [ value ]; + } + + // Always keep the list sorted. + obj[ prop ] = obj[ prop ].sort(); + + return true; +}; + +/** + * Create snap list via proxy that contains initial snap lines for edges and center of page. + * + * @param {Function} getLine Function to create lines based off maximum value. + * @param {number} maxValue Maximum value of page in this direction. + * @return {Function} Function mapping an actual object's position to all possible snap targets. + */ +const createSnapList = ( getLine, maxValue ) => new Proxy( + // Create initial list of snap lines. + { + // Start page border. + 0: [ getLine( 0 ) ], + // Center of the page. + [ maxValue / 2 ]: [ getLine( maxValue / 2 ) ], + // End page border. + [ maxValue ]: [ getLine( maxValue ) ], + }, + { + set: getSetter( 0, maxValue ), + }, +); + +export default createSnapList; diff --git a/assets/src/stories-editor/helpers/getHorizontalSnaps.js b/assets/src/stories-editor/helpers/getHorizontalSnaps.js new file mode 100644 index 00000000000..53edcc16ad7 --- /dev/null +++ b/assets/src/stories-editor/helpers/getHorizontalSnaps.js @@ -0,0 +1,20 @@ +/** + * Internal dependencies + */ +import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../constants'; +import getSnapCalculatorByDimension from './getSnapCalculatorByDimension'; + +const getVerticalLine = ( + offsetX, + start = 0, + end = STORY_PAGE_INNER_HEIGHT, +) => [ [ offsetX, start ], [ offsetX, end ] ]; + +const getHorizontalSnaps = getSnapCalculatorByDimension( + getVerticalLine, + STORY_PAGE_INNER_WIDTH, + [ 'left', 'right' ], + [ 'top', 'bottom' ], +); + +export default getHorizontalSnaps; diff --git a/assets/src/stories-editor/helpers/getSnapCalculatorByDimension.js b/assets/src/stories-editor/helpers/getSnapCalculatorByDimension.js new file mode 100644 index 00000000000..6fc254a3552 --- /dev/null +++ b/assets/src/stories-editor/helpers/getSnapCalculatorByDimension.js @@ -0,0 +1,49 @@ +/** + * Internal dependencies + */ +import createSnapList from './createSnapList'; + +/** + * Higher-higher order function that in the end creates a list of snap targets. + * + * First it is invoked with the "direction" of the snaps to calculate, creating the two + * functions below, `getHorizontalSnaps` and `getVerticalSnaps`. + * + * These functions in turn take a list of sibling element positions used to create the + * snap calculator function. + * + * This final snap calculator function takes the current coordinates of the dragged + * element (in the relevant dimension) and returns a list of all available snap lines + * relative to the snap target. + * + * @param {Function} getLine Function to create lines based. + * @param {number} maxValue Maximum value of page in this direction. + * @param {Array.} primary Coordinate property names in primary direction. + * @param {Array.} secondary Coordinate property names in secondary direction. + * @return {Function} Function mapping an actual object's position to all possible snap targets. + */ +const getSnapCalculatorByDimension = ( + getLine, + maxValue, + [ startProp, endProp ], + [ minProp, maxProp ], +) => ( siblingPositions ) => ( targetMin, targetMax ) => { + const snaps = createSnapList( getLine, maxValue ); + + for ( const coords of siblingPositions ) { + const start = coords[ startProp ]; + const end = coords[ endProp ]; + const center = ( start + end ) / 2; + + const min = Math.min( targetMin, coords[ minProp ] ); + const max = Math.max( targetMax, coords[ maxProp ] ); + + snaps[ start ] = getLine( start, min, max ); + snaps[ end ] = getLine( end, min, max ); + snaps[ center ] = getLine( center, min, max ); + } + + return snaps; +}; + +export default getSnapCalculatorByDimension; diff --git a/assets/src/stories-editor/helpers/getVerticalSnaps.js b/assets/src/stories-editor/helpers/getVerticalSnaps.js new file mode 100644 index 00000000000..09adf171b92 --- /dev/null +++ b/assets/src/stories-editor/helpers/getVerticalSnaps.js @@ -0,0 +1,20 @@ +/** + * Internal dependencies + */ +import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../constants'; +import getSnapCalculatorByDimension from './getSnapCalculatorByDimension'; + +const getHorizontalLine = ( + offsetY, + start = 0, + end = STORY_PAGE_INNER_WIDTH, +) => [ [ start, offsetY ], [ end, offsetY ] ]; + +const getVerticalSnaps = getSnapCalculatorByDimension( + getHorizontalLine, + STORY_PAGE_INNER_HEIGHT, + [ 'top', 'bottom' ], + [ 'left', 'right' ], +); + +export default getVerticalSnaps; diff --git a/assets/src/stories-editor/helpers/index.js b/assets/src/stories-editor/helpers/index.js index 5261f577b7f..6e1c6660df6 100644 --- a/assets/src/stories-editor/helpers/index.js +++ b/assets/src/stories-editor/helpers/index.js @@ -59,3 +59,5 @@ export { default as metaToAttributeNames } from './metaToAttributeNames'; export { default as parseDropEvent } from './parseDropEvent'; export { default as getBlockInnerElement } from './getBlockInnerElement'; export { default as getRelativeElementPosition } from './getRelativeElementPosition'; +export { default as getHorizontalSnaps } from './getHorizontalSnaps'; +export { default as getVerticalSnaps } from './getVerticalSnaps'; From 9bc8873f47ed02166c15ee5cd3361a3dbdcd6c85 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 4 Oct 2019 12:29:55 +0200 Subject: [PATCH 67/80] Add some tests for findClosestSnap --- .../helpers/test/findClosestSnap.js | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 assets/src/stories-editor/helpers/test/findClosestSnap.js diff --git a/assets/src/stories-editor/helpers/test/findClosestSnap.js b/assets/src/stories-editor/helpers/test/findClosestSnap.js new file mode 100644 index 00000000000..7a8892748a8 --- /dev/null +++ b/assets/src/stories-editor/helpers/test/findClosestSnap.js @@ -0,0 +1,42 @@ +/** + * Internal dependencies + */ +import findClosestSnap from '../findClosestSnap'; + +describe( 'findClosestSnap', () => { + it( 'should return the first match', () => { + const number = 15; + const snap = [ 10, 20, 30 ]; + const snapGap = 6; + + expect( findClosestSnap( number, snap, snapGap ) ).toStrictEqual( 10 ); + } ); + + it( 'should return null if there is no match', () => { + const number = 36; + const snap = [ 10, 20, 30 ]; + const snapGap = 5; + + expect( findClosestSnap( number, snap, snapGap ) ).toBeNull(); + } ); + + it( 'should accept a snap function', () => { + const number = 15; + const snap = jest.fn( () => [ 10, 20, 30 ] ); + const snapGap = 6; + + expect( findClosestSnap( number, snap, snapGap ) ).toStrictEqual( 10 ); + expect( snap.mock.calls ).toHaveLength( 1 ); + } ); + + it( 'should be memoized', () => { + const number = 25; + const snap = jest.fn( () => [ 20, 30, 40 ] ); + const snapGap = 10; + + findClosestSnap( number, snap, snapGap ); + findClosestSnap( number, snap, snapGap ); + expect( findClosestSnap( number, snap, snapGap ) ).toStrictEqual( 20 ); + expect( snap.mock.calls ).toHaveLength( 1 ); + } ); +} ); From 9ab9c5c60671a02b9f768933538d4703b901d712 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 4 Oct 2019 12:39:17 +0200 Subject: [PATCH 68/80] Add test for getRelativeElementPosition --- .../test/getRelativeElementPosition.js | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 assets/src/stories-editor/helpers/test/getRelativeElementPosition.js diff --git a/assets/src/stories-editor/helpers/test/getRelativeElementPosition.js b/assets/src/stories-editor/helpers/test/getRelativeElementPosition.js new file mode 100644 index 00000000000..a5a813e80d9 --- /dev/null +++ b/assets/src/stories-editor/helpers/test/getRelativeElementPosition.js @@ -0,0 +1,39 @@ +/** + * Internal dependencies + */ +import getRelativeElementPosition from '../getRelativeElementPosition'; + +describe( 'getRelativeElementPosition', () => { + it( 'should return the relative element position', () => { + const blockElement = { + getBoundingClientRect: jest.fn( () => { + return { + top: 400, + right: 800, + bottom: 800, + left: 400, + }; + } ), + }; + + const parentElement = { + getBoundingClientRect: jest.fn( () => { + return { + top: 250, + right: 1000, + bottom: 1000, + left: 250, + }; + } ), + }; + + const expected = { + top: 150, + right: 550, + bottom: 550, + left: 150, + }; + + expect( getRelativeElementPosition( blockElement, parentElement ) ).toStrictEqual( expected ); + } ); +} ); From 5251aaa6e1b97844855df3cdfa34e6b2b150f3ef Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 4 Oct 2019 14:24:34 +0200 Subject: [PATCH 69/80] Fix inverted condition to address duplicated snap line issue --- assets/src/stories-editor/helpers/createSnapList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/src/stories-editor/helpers/createSnapList.js b/assets/src/stories-editor/helpers/createSnapList.js index 38e0b2bf6bd..9e5628d6698 100644 --- a/assets/src/stories-editor/helpers/createSnapList.js +++ b/assets/src/stories-editor/helpers/createSnapList.js @@ -32,7 +32,7 @@ const getSetter = ( lowerBound, upperBound ) => ( obj, prop, value ) => { ); if ( obj.hasOwnProperty( prop ) ) { - if ( ! hasSnapLine( value ) ) { + if ( hasSnapLine( value ) ) { // We already have a completely identical snap line. return true; } From 8b7a2aa5df34f38216448f350d1cf188509466d4 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 4 Oct 2019 14:28:54 +0200 Subject: [PATCH 70/80] Add tests for createSnapList --- .../stories-editor/helpers/createSnapList.js | 18 +++- .../helpers/test/createSnapList.js | 91 +++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 assets/src/stories-editor/helpers/test/createSnapList.js diff --git a/assets/src/stories-editor/helpers/createSnapList.js b/assets/src/stories-editor/helpers/createSnapList.js index 9e5628d6698..83bc998c73e 100644 --- a/assets/src/stories-editor/helpers/createSnapList.js +++ b/assets/src/stories-editor/helpers/createSnapList.js @@ -50,10 +50,26 @@ const getSetter = ( lowerBound, upperBound ) => ( obj, prop, value ) => { return true; }; +/** + * Straight line between two coordinates. + * + * @typedef {Array., Array.>} Line + */ + +/** + * Function that draws a line from A to B. + * + * @typedef {LineCreator} LineCreator + * @param {number} offset Offset on the axis. + * @param {number} [start] Starting point. + * @param {number} [end] End point. + * @return Line + */ + /** * Create snap list via proxy that contains initial snap lines for edges and center of page. * - * @param {Function} getLine Function to create lines based off maximum value. + * @param {LineCreator} getLine Function to create lines based off maximum value. * @param {number} maxValue Maximum value of page in this direction. * @return {Function} Function mapping an actual object's position to all possible snap targets. */ diff --git a/assets/src/stories-editor/helpers/test/createSnapList.js b/assets/src/stories-editor/helpers/test/createSnapList.js new file mode 100644 index 00000000000..09ff6028608 --- /dev/null +++ b/assets/src/stories-editor/helpers/test/createSnapList.js @@ -0,0 +1,91 @@ +/** + * Internal dependencies + */ +import createSnapList from '../createSnapList'; + +const getLine = jest.fn( ( offset ) => [ [ offset, 0 ], [ offset, 1000 ] ] ); + +describe( 'createSnapList', () => { + it( 'should return an initial list of snap lines', () => { + const maxValue = 1000; + const snapLines = createSnapList( getLine, maxValue ); + + const expected = { + 0: [ [ [ 0, 0 ], [ 0, 1000 ] ] ], + 500: [ [ [ 500, 0 ], [ 500, 1000 ] ] ], + 1000: [ [ [ 1000, 0 ], [ 1000, 1000 ] ] ], + }; + + expect( snapLines ).toMatchObject( expected ); + } ); + + it( 'should allow adding new snap targets', () => { + const maxValue = 1000; + const snapLines = createSnapList( getLine, maxValue ); + + const expected = { + 0: [ [ [ 0, 0 ], [ 0, 1000 ] ] ], + 100: [ [ [ 100, 0 ], [ 100, 1000 ] ] ], + 500: [ [ [ 500, 0 ], [ 500, 1000 ] ] ], + 750: [ [ [ 750, 0 ], [ 750, 1000 ] ] ], + 1000: [ [ [ 1000, 0 ], [ 1000, 1000 ] ] ], + }; + + snapLines[ 100 ] = [ [ 100, 0 ], [ 100, 1000 ] ]; + snapLines[ 750 ] = [ [ 750, 0 ], [ 750, 1000 ] ]; + + expect( snapLines ).toMatchObject( expected ); + } ); + + it( 'should append new snap lines to the existing targets', () => { + const maxValue = 1000; + const snapLines = createSnapList( getLine, maxValue ); + + const expected = { + 0: [ [ [ 0, 0 ], [ 0, 1000 ] ], [ [ 0, 500 ], [ 100, 1000 ] ] ], + 500: [ [ [ 10, 500 ], [ 10, 1000 ] ], [ [ 500, 0 ], [ 500, 1000 ] ] ], + 1000: [ [ [ 1000, 0 ], [ 1000, 1000 ] ] ], + }; + + snapLines[ 0 ] = [ [ 0, 500 ], [ 100, 1000 ] ]; + snapLines[ 500 ] = [ [ 10, 500 ], [ 10, 1000 ] ]; + + expect( snapLines ).toMatchObject( expected ); + } ); + + it( 'should prevent duplicate snap lines', () => { + const maxValue = 1000; + const snapLines = createSnapList( getLine, maxValue ); + + const expected = { + 0: [ [ [ 0, 0 ], [ 0, 1000 ] ] ], + 500: [ [ [ 500, 0 ], [ 500, 1000 ] ] ], + 1000: [ [ [ 1000, 0 ], [ 1000, 1000 ] ] ], + }; + + snapLines[ 0 ] = [ [ 0, 0 ], [ 0, 1000 ] ]; + snapLines[ 0 ] = [ [ 0, 0 ], [ 0, 1000 ] ]; + snapLines[ 500 ] = [ [ 500, 0 ], [ 500, 1000 ] ]; + snapLines[ 500 ] = [ [ 500, 0 ], [ 500, 1000 ] ]; + snapLines[ 1000 ] = [ [ 1000, 0 ], [ 1000, 1000 ] ]; + snapLines[ 1000 ] = [ [ 1000, 0 ], [ 1000, 1000 ] ]; + + expect( snapLines ).toMatchObject( expected ); + } ); + + it( 'should prevent out of bound snap lines', () => { + const maxValue = 1000; + const snapLines = createSnapList( getLine, maxValue ); + + const expected = { + 0: [ [ [ 0, 0 ], [ 0, 1000 ] ] ], + 500: [ [ [ 500, 0 ], [ 500, 1000 ] ] ], + 1000: [ [ [ 1000, 0 ], [ 1000, 1000 ] ] ], + }; + + snapLines[ -1 ] = [ [ -1, 0 ], [ -1, 1000 ] ]; + snapLines[ 1001 ] = [ [ 1001, 0 ], [ 1001, 1000 ] ]; + + expect( snapLines ).toMatchObject( expected ); + } ); +} ); From 6815295ae8554f3127f8b03b33a572f8a35257ab Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 4 Oct 2019 14:54:48 +0200 Subject: [PATCH 71/80] Add a simple test for getSnapCalculatorByDimension --- .../helpers/getSnapCalculatorByDimension.js | 36 +++++++++++++++- .../test/getSnapCalculatorByDimension.js | 41 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 assets/src/stories-editor/helpers/test/getSnapCalculatorByDimension.js diff --git a/assets/src/stories-editor/helpers/getSnapCalculatorByDimension.js b/assets/src/stories-editor/helpers/getSnapCalculatorByDimension.js index 6fc254a3552..1b8f0f7e963 100644 --- a/assets/src/stories-editor/helpers/getSnapCalculatorByDimension.js +++ b/assets/src/stories-editor/helpers/getSnapCalculatorByDimension.js @@ -3,6 +3,40 @@ */ import createSnapList from './createSnapList'; +/** + * Function mapping an actual object's position to all possible snap targets. + * + * @typedef {BlockPosition} BlockPosition + * @property {number} top The block's relative top position. + * @property {number} right The block's relative right position. + * @property {number} bottom The block's relative bottom position. + * @property {number} left The block's relative left position. + */ + +/** + * Function returning an enhanced list of snap targets based on the current element's siblings' dimensions. + * + * @typedef {SnapTargetsEnhancer} SnapTargetsEnhancer + * @param {Array} blockPositions List of relative block dimensions. + * @return {SnapTargetsProvider} + */ + +/** + * A map of snap targets and their respective snapping guidelines. + * + * @typedef {SnapLines} SnapLines + * @type {Object., Array.>>>} List of snap lines + */ + +/** + * Returns a list of snap targets based on the current element's dimensions. + * + * @typedef {SnapTargetsProvider} SnapTargetsProvider + * @param {number} targetMin The current element's minimum value. + * @param {number} targetMax The current element's maximum value. + * @return {SnapLines} The resulting list of snap lines. + */ + /** * Higher-higher order function that in the end creates a list of snap targets. * @@ -20,7 +54,7 @@ import createSnapList from './createSnapList'; * @param {number} maxValue Maximum value of page in this direction. * @param {Array.} primary Coordinate property names in primary direction. * @param {Array.} secondary Coordinate property names in secondary direction. - * @return {Function} Function mapping an actual object's position to all possible snap targets. + * @return {SnapTargetsEnhancer} Function mapping an actual object's position to all possible snap targets. */ const getSnapCalculatorByDimension = ( getLine, diff --git a/assets/src/stories-editor/helpers/test/getSnapCalculatorByDimension.js b/assets/src/stories-editor/helpers/test/getSnapCalculatorByDimension.js new file mode 100644 index 00000000000..ff722bc288e --- /dev/null +++ b/assets/src/stories-editor/helpers/test/getSnapCalculatorByDimension.js @@ -0,0 +1,41 @@ +/** + * Internal dependencies + */ +import getSnapCalculatorByDimension from '../getSnapCalculatorByDimension'; + +const getLine = jest.fn( ( offset ) => [ [ offset, 0 ], [ offset, 1000 ] ] ); +const maxValue = 1000; + +describe( 'getSnapCalculatorByDimension', () => { + it( 'should ultimately return a list of snap targets based on block positions', () => { + const getSnaps = getSnapCalculatorByDimension( + getLine, + maxValue, + [ 'top', 'bottom' ], + [ 'left', 'right' ], + ); + + const siblingPositions = [ + { + top: 100, + right: 200, + bottom: 200, + left: 100, + }, + ]; + + const getSnapsWithSiblingPositions = getSnaps( siblingPositions ); + const actual = getSnapsWithSiblingPositions( 100, 100 ); + + const expected = { + 0: [ [ [ 0, 0 ], [ 0, 1000 ] ] ], + 100: [ [ [ 100, 0 ], [ 100, 1000 ] ] ], + 1000: [ [ [ 1000, 0 ], [ 1000, 1000 ] ] ], + 150: [ [ [ 150, 0 ], [ 150, 1000 ] ] ], + 200: [ [ [ 200, 0 ], [ 200, 1000 ] ] ], + 500: [ [ [ 500, 0 ], [ 500, 1000 ] ] ], + }; + + expect( actual ).toMatchObject( expected ); + } ); +} ); From cb5c3c3fb82eea3d62d6f040d5cb09536a9e32a3 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 4 Oct 2019 18:24:30 +0200 Subject: [PATCH 72/80] Fix eported name for isCTABlock --- assets/src/stories-editor/helpers/isCTABlock.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/src/stories-editor/helpers/isCTABlock.js b/assets/src/stories-editor/helpers/isCTABlock.js index 223d007a492..5a907daa8fa 100644 --- a/assets/src/stories-editor/helpers/isCTABlock.js +++ b/assets/src/stories-editor/helpers/isCTABlock.js @@ -4,8 +4,8 @@ * @param {string} blockName Block name. * @return {boolean} Boolean if block is / is not a CTA block. */ -export const isPCTABlock = ( blockName ) => { +const isCTABlock = ( blockName ) => { return 'amp/amp-story-cta' === blockName; }; -export default isPCTABlock; +export default isCTABlock; From afc7ef74924403c705ac3a46313b1749711e516a Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 4 Oct 2019 18:24:42 +0200 Subject: [PATCH 73/80] Use isCTABlock helper in more places --- assets/src/stories-editor/helpers/addAMPAttributes.js | 4 ++-- assets/src/stories-editor/helpers/getBlockInnerElement.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/src/stories-editor/helpers/addAMPAttributes.js b/assets/src/stories-editor/helpers/addAMPAttributes.js index 398277a7eb1..844f31712ee 100644 --- a/assets/src/stories-editor/helpers/addAMPAttributes.js +++ b/assets/src/stories-editor/helpers/addAMPAttributes.js @@ -5,6 +5,7 @@ import { ALLOWED_CHILD_BLOCKS, BLOCKS_WITH_RESIZING, BLOCKS_WITH_TEXT_SETTINGS } import { addAMPAttributesDeprecations } from '../deprecations/filters'; import getDefaultMinimumBlockHeight from './getDefaultMinimumBlockHeight'; import isMovableBlock from './isMovableBlock'; +import isCTABlock from './isCTABlock'; /** * Adds AMP attributes to every allowed AMP Story block. @@ -30,7 +31,6 @@ const addAMPAttributes = ( settings, name ) => { const isImageBlock = 'core/image' === name; const isVideoBlock = 'core/video' === name; - const isCTABlock = 'amp/amp-story-cta' === name; const needsTextSettings = BLOCKS_WITH_TEXT_SETTINGS.includes( name ); @@ -80,7 +80,7 @@ const addAMPAttributes = ( settings, name ) => { }; } - if ( isCTABlock ) { + if ( isCTABlock( name ) ) { addedAttributes.anchor = { type: 'string', source: 'attribute', diff --git a/assets/src/stories-editor/helpers/getBlockInnerElement.js b/assets/src/stories-editor/helpers/getBlockInnerElement.js index 61ab0025a31..df6b60ab746 100644 --- a/assets/src/stories-editor/helpers/getBlockInnerElement.js +++ b/assets/src/stories-editor/helpers/getBlockInnerElement.js @@ -2,6 +2,7 @@ * Internal dependencies */ import getBlockDOMNode from './getBlockDOMNode'; +import isCTABlock from './isCTABlock'; /** * Returns a movable block's inner element. @@ -13,13 +14,12 @@ import getBlockDOMNode from './getBlockDOMNode'; const getBlockInnerElement = ( block ) => { const { name, clientId } = block; const isPage = 'amp/amp-story-page' === name; - const isCTABlock = 'amp/amp-story-cta' === name; if ( isPage ) { return getBlockDOMNode( clientId ); } - if ( isCTABlock ) { + if ( isCTABlock( name ) ) { // Not the block itself is movable, only the button within. return document.querySelector( `amp-story-cta-button-${ clientId }` ); } From 9515bed26b23f58ec21b8fe278f2afe994809be9 Mon Sep 17 00:00:00 2001 From: Morten Barklund Date: Fri, 4 Oct 2019 14:02:50 -0400 Subject: [PATCH 74/80] Reorganised snap-related helpers --- .../components/block-mover/draggable.js | 2 +- .../components/higher-order/with-snap-targets.js | 8 ++------ .../components/resizable-box/index.js | 12 +++--------- .../components/rotatable-box/index.js | 2 +- assets/src/stories-editor/helpers/index.js | 3 --- .../helpers/{ => snapping}/createSnapList.js | 0 .../helpers/{ => snapping}/findClosestSnap.js | 0 .../helpers/{ => snapping}/getHorizontalSnaps.js | 2 +- .../{ => snapping}/getSnapCalculatorByDimension.js | 0 .../helpers/{ => snapping}/getVerticalSnaps.js | 2 +- assets/src/stories-editor/helpers/snapping/index.js | 13 +++++++++++++ .../helpers/{ => snapping}/test/createSnapList.js | 0 .../helpers/{ => snapping}/test/findClosestSnap.js | 0 .../test/getSnapCalculatorByDimension.js | 0 14 files changed, 22 insertions(+), 22 deletions(-) rename assets/src/stories-editor/helpers/{ => snapping}/createSnapList.js (100%) rename assets/src/stories-editor/helpers/{ => snapping}/findClosestSnap.js (100%) rename assets/src/stories-editor/helpers/{ => snapping}/getHorizontalSnaps.js (96%) rename assets/src/stories-editor/helpers/{ => snapping}/getSnapCalculatorByDimension.js (100%) rename assets/src/stories-editor/helpers/{ => snapping}/getVerticalSnaps.js (96%) create mode 100644 assets/src/stories-editor/helpers/snapping/index.js rename assets/src/stories-editor/helpers/{ => snapping}/test/createSnapList.js (100%) rename assets/src/stories-editor/helpers/{ => snapping}/test/findClosestSnap.js (100%) rename assets/src/stories-editor/helpers/{ => snapping}/test/getSnapCalculatorByDimension.js (100%) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 3323836ff0f..4cd3d909c03 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -20,11 +20,11 @@ import { withSafeTimeout, compose } from '@wordpress/compose'; import withSnapTargets from '../higher-order/with-snap-targets'; import { getPixelsFromPercentage, - findClosestSnap, getPercentageFromPixels, getRelativeElementPosition, isCTABlock, } from '../../helpers'; +import { findClosestSnap } from '../../helpers/snapping'; import { STORY_PAGE_INNER_WIDTH, STORY_PAGE_INNER_HEIGHT, diff --git a/assets/src/stories-editor/components/higher-order/with-snap-targets.js b/assets/src/stories-editor/components/higher-order/with-snap-targets.js index 92c56d7d02e..7c5a5ee5dc6 100644 --- a/assets/src/stories-editor/components/higher-order/with-snap-targets.js +++ b/assets/src/stories-editor/components/higher-order/with-snap-targets.js @@ -7,12 +7,8 @@ import { compose, createHigherOrderComponent } from '@wordpress/compose'; /** * Internal dependencies */ -import { - getBlockInnerElement, - getRelativeElementPosition, - getHorizontalSnaps, - getVerticalSnaps, -} from '../../helpers'; +import { getBlockInnerElement, getRelativeElementPosition } from '../../helpers'; +import { getHorizontalSnaps, getVerticalSnaps } from '../../helpers/snapping'; import { withSnapContext } from '../contexts/snapping'; /** diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 0c83422f9b8..3b17c35c97e 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -15,15 +15,9 @@ import { ResizableBox } from '@wordpress/components'; */ import withSnapTargets from '../higher-order/with-snap-targets'; import './edit.css'; -import { - getPercentageFromPixels, - getRelativeElementPosition, - findClosestSnap, -} from '../../helpers'; -import { - TEXT_BLOCK_PADDING, - BLOCK_RESIZING_SNAP_GAP, -} from '../../constants'; +import { getPercentageFromPixels, getRelativeElementPosition } from '../../helpers'; +import { findClosestSnap } from '../../helpers/snapping'; +import { TEXT_BLOCK_PADDING, BLOCK_RESIZING_SNAP_GAP } from '../../constants'; import { getBlockPositioning, getResizedBlockPosition, diff --git a/assets/src/stories-editor/components/rotatable-box/index.js b/assets/src/stories-editor/components/rotatable-box/index.js index 76fe9aaee73..db067626d8f 100644 --- a/assets/src/stories-editor/components/rotatable-box/index.js +++ b/assets/src/stories-editor/components/rotatable-box/index.js @@ -15,7 +15,7 @@ import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ -import { findClosestSnap } from '../../helpers'; +import { findClosestSnap } from '../../helpers/snapping'; import './edit.css'; const RotatableBox = ( { angle, initialAngle, blockElementId, className, speak, onRotateStart, onRotate, onRotateStop, snap, snapGap, children } ) => { diff --git a/assets/src/stories-editor/helpers/index.js b/assets/src/stories-editor/helpers/index.js index a3f049a131c..11e589fdc24 100644 --- a/assets/src/stories-editor/helpers/index.js +++ b/assets/src/stories-editor/helpers/index.js @@ -51,15 +51,12 @@ export { default as isPageBlock } from './isPageBlock'; export { default as copyTextToClipBoard } from './copyTextToClipBoard'; export { default as getPosterImageFromFileObj } from './getPosterImageFromFileObj'; export { default as getUniqueId } from './getUniqueId'; -export { default as findClosestSnap } from './findClosestSnap'; export { default as setInputSelectionToEnd } from './setInputSelectionToEnd'; export { default as getBlockDOMNode } from './getBlockDOMNode'; export { default as isMovableBlock } from './isMovableBlock'; export { default as metaToAttributeNames } from './metaToAttributeNames'; export { default as parseDropEvent } from './parseDropEvent'; export { default as getBlockInnerElement } from './getBlockInnerElement'; -export { default as getHorizontalSnaps } from './getHorizontalSnaps'; -export { default as getVerticalSnaps } from './getVerticalSnaps'; export { default as getRelativeElementPosition } from './getRelativeElementPosition'; export { default as isCTABlock } from './isCTABlock'; export { default as useMoveBlockToPage } from './useMoveBlockToPage'; diff --git a/assets/src/stories-editor/helpers/createSnapList.js b/assets/src/stories-editor/helpers/snapping/createSnapList.js similarity index 100% rename from assets/src/stories-editor/helpers/createSnapList.js rename to assets/src/stories-editor/helpers/snapping/createSnapList.js diff --git a/assets/src/stories-editor/helpers/findClosestSnap.js b/assets/src/stories-editor/helpers/snapping/findClosestSnap.js similarity index 100% rename from assets/src/stories-editor/helpers/findClosestSnap.js rename to assets/src/stories-editor/helpers/snapping/findClosestSnap.js diff --git a/assets/src/stories-editor/helpers/getHorizontalSnaps.js b/assets/src/stories-editor/helpers/snapping/getHorizontalSnaps.js similarity index 96% rename from assets/src/stories-editor/helpers/getHorizontalSnaps.js rename to assets/src/stories-editor/helpers/snapping/getHorizontalSnaps.js index 53edcc16ad7..0b7df3fa15e 100644 --- a/assets/src/stories-editor/helpers/getHorizontalSnaps.js +++ b/assets/src/stories-editor/helpers/snapping/getHorizontalSnaps.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../constants'; +import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; import getSnapCalculatorByDimension from './getSnapCalculatorByDimension'; const getVerticalLine = ( diff --git a/assets/src/stories-editor/helpers/getSnapCalculatorByDimension.js b/assets/src/stories-editor/helpers/snapping/getSnapCalculatorByDimension.js similarity index 100% rename from assets/src/stories-editor/helpers/getSnapCalculatorByDimension.js rename to assets/src/stories-editor/helpers/snapping/getSnapCalculatorByDimension.js diff --git a/assets/src/stories-editor/helpers/getVerticalSnaps.js b/assets/src/stories-editor/helpers/snapping/getVerticalSnaps.js similarity index 96% rename from assets/src/stories-editor/helpers/getVerticalSnaps.js rename to assets/src/stories-editor/helpers/snapping/getVerticalSnaps.js index 09adf171b92..f667bcaf89e 100644 --- a/assets/src/stories-editor/helpers/getVerticalSnaps.js +++ b/assets/src/stories-editor/helpers/snapping/getVerticalSnaps.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../constants'; +import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; import getSnapCalculatorByDimension from './getSnapCalculatorByDimension'; const getHorizontalLine = ( diff --git a/assets/src/stories-editor/helpers/snapping/index.js b/assets/src/stories-editor/helpers/snapping/index.js new file mode 100644 index 00000000000..61b0685a6e8 --- /dev/null +++ b/assets/src/stories-editor/helpers/snapping/index.js @@ -0,0 +1,13 @@ +/** + * WordPress dependencies + */ +import '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import '../../store'; + +export { default as findClosestSnap } from './findClosestSnap'; +export { default as getHorizontalSnaps } from './getHorizontalSnaps'; +export { default as getVerticalSnaps } from './getVerticalSnaps'; diff --git a/assets/src/stories-editor/helpers/test/createSnapList.js b/assets/src/stories-editor/helpers/snapping/test/createSnapList.js similarity index 100% rename from assets/src/stories-editor/helpers/test/createSnapList.js rename to assets/src/stories-editor/helpers/snapping/test/createSnapList.js diff --git a/assets/src/stories-editor/helpers/test/findClosestSnap.js b/assets/src/stories-editor/helpers/snapping/test/findClosestSnap.js similarity index 100% rename from assets/src/stories-editor/helpers/test/findClosestSnap.js rename to assets/src/stories-editor/helpers/snapping/test/findClosestSnap.js diff --git a/assets/src/stories-editor/helpers/test/getSnapCalculatorByDimension.js b/assets/src/stories-editor/helpers/snapping/test/getSnapCalculatorByDimension.js similarity index 100% rename from assets/src/stories-editor/helpers/test/getSnapCalculatorByDimension.js rename to assets/src/stories-editor/helpers/snapping/test/getSnapCalculatorByDimension.js From ebff740fabd7e0ba30f436eea103eb51c5b06a2b Mon Sep 17 00:00:00 2001 From: Morten Barklund Date: Fri, 4 Oct 2019 16:56:31 -0400 Subject: [PATCH 75/80] Extracted best snap finder to helper function --- .../components/block-mover/draggable.js | 61 ++++--------------- .../components/resizable-box/index.js | 49 +++------------ .../helpers/snapping/createSnapList.js | 20 +----- .../helpers/snapping/findBestMatch.js | 23 +++++++ .../helpers/snapping/findClosestSnap.js | 2 +- .../helpers/snapping/getBestSnapLines.js | 33 ++++++++++ .../snapping/getSnapCalculatorByDimension.js | 42 ++----------- .../stories-editor/helpers/snapping/index.js | 11 +--- .../stories-editor/helpers/snapping/types.js | 57 +++++++++++++++++ 9 files changed, 142 insertions(+), 156 deletions(-) create mode 100644 assets/src/stories-editor/helpers/snapping/findBestMatch.js create mode 100644 assets/src/stories-editor/helpers/snapping/getBestSnapLines.js create mode 100644 assets/src/stories-editor/helpers/snapping/types.js diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index 4cd3d909c03..d3324fad2a3 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -24,7 +24,7 @@ import { getRelativeElementPosition, isCTABlock, } from '../../helpers'; -import { findClosestSnap } from '../../helpers/snapping'; +import { getBestSnapLines } from '../../helpers/snapping'; import { STORY_PAGE_INNER_WIDTH, STORY_PAGE_INNER_HEIGHT, @@ -76,7 +76,7 @@ class Draggable extends Component { * @param {Object} event The non-custom DragEvent. */ onDragEnd = ( event ) => { - const { clearHighlight, dropElementByOffset, blockName, setTimeout, snapLines, clearSnapLines, onDragEnd = noop } = this.props; + const { clearHighlight, dropElementByOffset, blockName, setTimeout, clearSnapLines, onDragEnd = noop } = this.props; if ( event ) { event.preventDefault(); } @@ -111,9 +111,7 @@ class Draggable extends Component { this.resetDragState(); - if ( snapLines.length ) { - clearSnapLines(); - } + clearSnapLines(); setTimeout( onDragEnd ); } @@ -126,9 +124,8 @@ class Draggable extends Component { onDragOver = ( event ) => { // eslint-disable-line complexity const { blockName, - snapLines, - clearSnapLines, setSnapLines, + clearSnapLines, parentBlockElement, horizontalSnaps, verticalSnaps, @@ -154,47 +151,16 @@ class Draggable extends Component { left: actualLeft, } = getRelativeElementPosition( blockElement, parentBlockElement ); - const horizontalCenter = ( actualRight + actualLeft ) / 2; - const verticalCenter = ( actualTop + actualBottom ) / 2; - - const newSnapLines = []; - const snappingEnabled = ! event.getModifierState( 'Alt' ); if ( snappingEnabled ) { - // Go through all snap targets and find the one that is closest. - const findSnap = ( snapKeys, ...values ) => { - return values - .map( ( value ) => { - const snap = findClosestSnap( value, snapKeys, BLOCK_DRAGGING_SNAP_GAP ); - return [ value, snap ]; - } ) - .filter( ( arr ) => arr[ 1 ] !== null ) - .sort( ( a, b ) => a[ 1 ] - b[ 1 ] ) - .map( ( arr ) => arr[ 1 ] ) - .shift(); - }; - - const _horizontalSnaps = horizontalSnaps( actualTop, actualBottom ); - const _horizontalSnapKeys = Object.keys( _horizontalSnaps ); - const _verticalSnaps = verticalSnaps( actualLeft, actualRight ); - const _verticalSnapKeys = Object.keys( _verticalSnaps ); - - const horizontalSnap = findSnap( _horizontalSnapKeys, actualLeft, actualRight, horizontalCenter ); - const verticalSnap = findSnap( _verticalSnapKeys, actualTop, actualBottom, verticalCenter ); - - if ( horizontalSnap !== undefined ) { - newSnapLines.push( ..._horizontalSnaps[ horizontalSnap ] ); - } - - if ( verticalSnap !== undefined ) { - newSnapLines.push( ..._verticalSnaps[ verticalSnap ] ); - } - } - - if ( newSnapLines.length ) { - setSnapLines( newSnapLines ); - } else if ( snapLines.length ) { + const horizontalSnapsForPosition = horizontalSnaps( actualTop, actualBottom ); + const verticalSnapsForPosition = verticalSnaps( actualLeft, actualRight ); + setSnapLines( [ + ...getBestSnapLines( horizontalSnapsForPosition, actualLeft, actualRight, BLOCK_DRAGGING_SNAP_GAP ), + ...getBestSnapLines( verticalSnapsForPosition, actualTop, actualBottom, BLOCK_DRAGGING_SNAP_GAP ), + ] ); + } else { clearSnapLines(); } @@ -250,7 +216,6 @@ class Draggable extends Component { elementId, transferData, onDragStart = noop, - snapLines, clearSnapLines, } = this.props; const blockIsCTA = isCTABlock( blockName ); @@ -338,9 +303,7 @@ class Draggable extends Component { document.addEventListener( 'drop', this.onDrop ); } - if ( snapLines.length ) { - clearSnapLines(); - } + clearSnapLines(); this.props.setTimeout( onDragStart ); } diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 3b17c35c97e..28f5cdb00b9 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -16,7 +16,7 @@ import { ResizableBox } from '@wordpress/components'; import withSnapTargets from '../higher-order/with-snap-targets'; import './edit.css'; import { getPercentageFromPixels, getRelativeElementPosition } from '../../helpers'; -import { findClosestSnap } from '../../helpers/snapping'; +import { getBestSnapLines } from '../../helpers/snapping'; import { TEXT_BLOCK_PADDING, BLOCK_RESIZING_SNAP_GAP } from '../../constants'; import { getBlockPositioning, @@ -263,42 +263,17 @@ class EnhancedResizableBox extends Component { left: actualLeft, } = getRelativeElementPosition( blockElement.querySelector( '.wp-block' ), parentBlockElement ); - const horizontalCenter = actualLeft + ( ( actualRight - actualLeft ) / 2 ); - const verticalCenter = actualTop + ( ( actualBottom - actualTop ) / 2 ); - - const newSnapLines = []; - const snappingEnabled = ! event.getModifierState( 'Alt' ); if ( snappingEnabled ) { - // Go through all snap targets and find the one that is closest. - const findSnap = ( snapKeys, ...values ) => { - return values - .map( ( value ) => { - const snap = findClosestSnap( value, snapKeys, BLOCK_RESIZING_SNAP_GAP ); - return [ value, snap ]; - } ) - .filter( ( arr ) => arr[ 1 ] !== null ) - .sort( ( a, b ) => a[ 1 ] - b[ 1 ] ) - .map( ( arr ) => arr[ 1 ] ) - .shift(); - }; - - const _horizontalSnaps = horizontalSnaps( actualTop, actualBottom ); - const _horizontalSnapKeys = Object.keys( _horizontalSnaps ); - const _verticalSnaps = verticalSnaps( actualLeft, actualRight ); - const _verticalSnapKeys = Object.keys( _verticalSnaps ); - - const horizontalSnap = findSnap( _horizontalSnapKeys, actualLeft, actualRight, horizontalCenter ); - const verticalSnap = findSnap( _verticalSnapKeys, actualTop, actualBottom, verticalCenter ); - - if ( horizontalSnap !== undefined ) { - newSnapLines.push( ..._horizontalSnaps[ horizontalSnap ] ); - } - - if ( verticalSnap !== undefined ) { - newSnapLines.push( ..._verticalSnaps[ verticalSnap ] ); - } + const horizontalSnapsForPosition = horizontalSnaps( actualTop, actualBottom ); + const verticalSnapsForPosition = verticalSnaps( actualLeft, actualRight ); + setSnapLines( [ + ...getBestSnapLines( horizontalSnapsForPosition, actualLeft, actualRight, BLOCK_RESIZING_SNAP_GAP ), + ...getBestSnapLines( verticalSnapsForPosition, actualTop, actualBottom, BLOCK_RESIZING_SNAP_GAP ), + ] ); + } else { + clearSnapLines(); } lastWidth = appliedWidth; @@ -317,12 +292,6 @@ class EnhancedResizableBox extends Component { imageWrapper.style.width = appliedWidth + 'px'; imageWrapper.style.height = appliedHeight + 'px'; } - - if ( newSnapLines.length ) { - setSnapLines( newSnapLines ); - } else if ( snapLines.length ) { - clearSnapLines(); - } } } > { children } diff --git a/assets/src/stories-editor/helpers/snapping/createSnapList.js b/assets/src/stories-editor/helpers/snapping/createSnapList.js index 83bc998c73e..12d69922876 100644 --- a/assets/src/stories-editor/helpers/snapping/createSnapList.js +++ b/assets/src/stories-editor/helpers/snapping/createSnapList.js @@ -50,28 +50,12 @@ const getSetter = ( lowerBound, upperBound ) => ( obj, prop, value ) => { return true; }; -/** - * Straight line between two coordinates. - * - * @typedef {Array., Array.>} Line - */ - -/** - * Function that draws a line from A to B. - * - * @typedef {LineCreator} LineCreator - * @param {number} offset Offset on the axis. - * @param {number} [start] Starting point. - * @param {number} [end] End point. - * @return Line - */ - /** * Create snap list via proxy that contains initial snap lines for edges and center of page. * - * @param {LineCreator} getLine Function to create lines based off maximum value. + * @param {import('./types').LineCreator} getLine Function to create lines based off maximum value. * @param {number} maxValue Maximum value of page in this direction. - * @return {Function} Function mapping an actual object's position to all possible snap targets. + * @return {import('./types').SnapLines} An initial list of snap lines ready to be extended. */ const createSnapList = ( getLine, maxValue ) => new Proxy( // Create initial list of snap lines. diff --git a/assets/src/stories-editor/helpers/snapping/findBestMatch.js b/assets/src/stories-editor/helpers/snapping/findBestMatch.js new file mode 100644 index 00000000000..c11de201189 --- /dev/null +++ b/assets/src/stories-editor/helpers/snapping/findBestMatch.js @@ -0,0 +1,23 @@ +/** + * Get the best match for a list of values each creating a match + * + * @param {Function.} matcher Function finding closest match for a single value or null + * @param {Array.} values List of values to find a best match between. + * @return {number} The single best matching value or undefined. + */ +const findBestMatch = ( matcher, ...values ) => values + .map( ( value ) => { + const best = matcher( value ); + if ( ! best ) { + return null; + } + return { value: best, distance: Math.abs( best - value ) }; + } ) + .filter( Boolean ) + .reduce( + ( best, candidate ) => candidate.distance < best.distance ? candidate : best, + { distance: Number.MAX_VALUE, value: null }, + ) + .value; + +export default findBestMatch; diff --git a/assets/src/stories-editor/helpers/snapping/findClosestSnap.js b/assets/src/stories-editor/helpers/snapping/findClosestSnap.js index 4df21fe1bdf..d5bffcb5034 100644 --- a/assets/src/stories-editor/helpers/snapping/findClosestSnap.js +++ b/assets/src/stories-editor/helpers/snapping/findClosestSnap.js @@ -9,7 +9,7 @@ import memize from 'memize'; * @see https://github.com/bokuweb/re-resizable * * @param {number} number Given number. - * @param {Array|Function} snap List of snap targets or function that provides them. + * @param {Array. | Function.} snap List of snap targets or function that provides them. * @param {number} snapGap Minimum gap required in order to move to the next snapping target * @return {?number} Snap target if found. */ diff --git a/assets/src/stories-editor/helpers/snapping/getBestSnapLines.js b/assets/src/stories-editor/helpers/snapping/getBestSnapLines.js new file mode 100644 index 00000000000..36ab511d303 --- /dev/null +++ b/assets/src/stories-editor/helpers/snapping/getBestSnapLines.js @@ -0,0 +1,33 @@ +/** + * Internal dependencies + */ +import findClosestSnap from './findClosestSnap'; +import findBestMatch from './findBestMatch'; + +/** + * Get the best match(es) from a list of snap lines based on actual position. + * + * @param {import('./types').SnapLines} snapLines Function to create lines based off maximum value. + * @param {number} start Value of near edge of current object + * @param {number} end Value of far edge of current object + * @param {number} gap Maximum gap from edge to snap line for line to be considered. + * @return {Array.} The lines that correspond to the single best match for any edge. + */ +const getBestSnapLines = ( snapLines, start, end, gap ) => { + // Go through all snap targets and find the one that is closest. + const snapTargets = Object.keys( snapLines ); + + const center = ( start + end ) / 2; + + const matcher = ( value ) => findClosestSnap( value, snapTargets, gap ); + + const bestMatch = findBestMatch( matcher, start, end, center ); + + if ( ! bestMatch ) { + return []; + } + + return snapLines[ bestMatch ]; +}; + +export default getBestSnapLines; diff --git a/assets/src/stories-editor/helpers/snapping/getSnapCalculatorByDimension.js b/assets/src/stories-editor/helpers/snapping/getSnapCalculatorByDimension.js index 1b8f0f7e963..ffa2280b143 100644 --- a/assets/src/stories-editor/helpers/snapping/getSnapCalculatorByDimension.js +++ b/assets/src/stories-editor/helpers/snapping/getSnapCalculatorByDimension.js @@ -3,40 +3,6 @@ */ import createSnapList from './createSnapList'; -/** - * Function mapping an actual object's position to all possible snap targets. - * - * @typedef {BlockPosition} BlockPosition - * @property {number} top The block's relative top position. - * @property {number} right The block's relative right position. - * @property {number} bottom The block's relative bottom position. - * @property {number} left The block's relative left position. - */ - -/** - * Function returning an enhanced list of snap targets based on the current element's siblings' dimensions. - * - * @typedef {SnapTargetsEnhancer} SnapTargetsEnhancer - * @param {Array} blockPositions List of relative block dimensions. - * @return {SnapTargetsProvider} - */ - -/** - * A map of snap targets and their respective snapping guidelines. - * - * @typedef {SnapLines} SnapLines - * @type {Object., Array.>>>} List of snap lines - */ - -/** - * Returns a list of snap targets based on the current element's dimensions. - * - * @typedef {SnapTargetsProvider} SnapTargetsProvider - * @param {number} targetMin The current element's minimum value. - * @param {number} targetMax The current element's maximum value. - * @return {SnapLines} The resulting list of snap lines. - */ - /** * Higher-higher order function that in the end creates a list of snap targets. * @@ -50,11 +16,11 @@ import createSnapList from './createSnapList'; * element (in the relevant dimension) and returns a list of all available snap lines * relative to the snap target. * - * @param {Function} getLine Function to create lines based. + * @param {import('./types').LineCreator} getLine Function to create lines based. * @param {number} maxValue Maximum value of page in this direction. - * @param {Array.} primary Coordinate property names in primary direction. - * @param {Array.} secondary Coordinate property names in secondary direction. - * @return {SnapTargetsEnhancer} Function mapping an actual object's position to all possible snap targets. + * @param {Array.} primary Coordinate property names in primary direction. + * @param {Array.} secondary Coordinate property names in secondary direction. + * @return {import('./types').SnapTargetsEnhancer} Function mapping an actual object's position to all possible snap targets. */ const getSnapCalculatorByDimension = ( getLine, diff --git a/assets/src/stories-editor/helpers/snapping/index.js b/assets/src/stories-editor/helpers/snapping/index.js index 61b0685a6e8..72690d3fe27 100644 --- a/assets/src/stories-editor/helpers/snapping/index.js +++ b/assets/src/stories-editor/helpers/snapping/index.js @@ -1,13 +1,4 @@ -/** - * WordPress dependencies - */ -import '@wordpress/core-data'; - -/** - * Internal dependencies - */ -import '../../store'; - export { default as findClosestSnap } from './findClosestSnap'; export { default as getHorizontalSnaps } from './getHorizontalSnaps'; export { default as getVerticalSnaps } from './getVerticalSnaps'; +export { default as getBestSnapLines } from './getBestSnapLines'; diff --git a/assets/src/stories-editor/helpers/snapping/types.js b/assets/src/stories-editor/helpers/snapping/types.js new file mode 100644 index 00000000000..a37c8152e89 --- /dev/null +++ b/assets/src/stories-editor/helpers/snapping/types.js @@ -0,0 +1,57 @@ +/** + * Straight line between two coordinates. + * + * @typedef {Array., Array.>} Line + */ + +/** + * Function that draws a line from A to B. + * + * @typedef {LineCreator} LineCreator + * @param {number} offset Offset on the axis. + * @param {number} [start] Starting point. + * @param {number} [end] End point. + * @return Line + */ + +/** + * Object position e.g. as returned by `getBoundingClientRect()`. + * + * @typedef {BlockPosition} BlockPosition + * @property {number} top The block's relative top position. + * @property {number} right The block's relative right position. + * @property {number} bottom The block's relative bottom position. + * @property {number} left The block's relative left position. + */ + +/** + * Function returning an enhanced list of snap targets based on the current element's siblings' dimensions. + * + * @typedef {SnapTargetsEnhancer} SnapTargetsEnhancer + * @param {Array} blockPositions List of relative block dimensions. + * @return {SnapTargetsProvider} + */ + +/** + * A map of snap targets and their respective snapping guidelines. + * + * @typedef {SnapLines} SnapLines + * @type {Object.>} List of snap lines + */ + +/** + * A function creating a map of snap targets and their respective snapping guidelines from a single number. + * + * @typedef {SnapLinesCreator} SnapLinesCreator + * @param {number} number The number to generate snap targets for + * @return {SnapLines} The resulting list of snap lines. + */ + +/** + * Returns a list of snap targets based on the current element's dimensions. + * + * @typedef {SnapTargetsProvider} SnapTargetsProvider + * @param {number} targetMin The current element's minimum value. + * @param {number} targetMax The current element's maximum value. + * @return {SnapLines} The resulting list of snap lines. + */ From aca726864b6629e565668c5d7b51ded53eab8bce Mon Sep 17 00:00:00 2001 From: Morten Barklund Date: Fri, 4 Oct 2019 17:16:24 -0400 Subject: [PATCH 76/80] Added unit tests for `getBestMatch` and `getBestSnapLines` --- .../helpers/snapping/getBestSnapLines.js | 2 +- .../helpers/snapping/test/findBestMatch.js | 36 +++++++++++ .../helpers/snapping/test/getBestSnapLines.js | 59 +++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 assets/src/stories-editor/helpers/snapping/test/findBestMatch.js create mode 100644 assets/src/stories-editor/helpers/snapping/test/getBestSnapLines.js diff --git a/assets/src/stories-editor/helpers/snapping/getBestSnapLines.js b/assets/src/stories-editor/helpers/snapping/getBestSnapLines.js index 36ab511d303..a2700445695 100644 --- a/assets/src/stories-editor/helpers/snapping/getBestSnapLines.js +++ b/assets/src/stories-editor/helpers/snapping/getBestSnapLines.js @@ -7,7 +7,7 @@ import findBestMatch from './findBestMatch'; /** * Get the best match(es) from a list of snap lines based on actual position. * - * @param {import('./types').SnapLines} snapLines Function to create lines based off maximum value. + * @param {import('./types').SnapLines} snapLines Object with lists of snap lines indexes by coordinate. * @param {number} start Value of near edge of current object * @param {number} end Value of far edge of current object * @param {number} gap Maximum gap from edge to snap line for line to be considered. diff --git a/assets/src/stories-editor/helpers/snapping/test/findBestMatch.js b/assets/src/stories-editor/helpers/snapping/test/findBestMatch.js new file mode 100644 index 00000000000..03d249f47f8 --- /dev/null +++ b/assets/src/stories-editor/helpers/snapping/test/findBestMatch.js @@ -0,0 +1,36 @@ +/** + * Internal dependencies + */ +import findBestMatch from '../findBestMatch'; + +describe( 'findBestMatch', () => { + // Use a matcher that rounds to nearest 10, but returns null if nearest 10 is 50 + const matcher = ( n ) => { + const tens = Math.round( n / 10 ) * 10; + return tens === 50 ? null : tens; + }; + + it( 'should return the best match', () => { + const values = [ 33, 22, 11 ]; + + expect( findBestMatch( matcher, ...values ) ).toStrictEqual( 10 ); + } ); + + it( 'should return ignore non-matches', () => { + const values = [ 46, 22, 51 ]; + + expect( findBestMatch( matcher, ...values ) ).toStrictEqual( 20 ); + } ); + + it( 'should return null if none match', () => { + const values = [ 46, 51 ]; + + expect( findBestMatch( matcher, ...values ) ).toBeNull(); + } ); + + it( 'should return the first match if several match equally well', () => { + const values = [ 33, 22, 12 ]; + + expect( findBestMatch( matcher, ...values ) ).toStrictEqual( 20 ); + } ); +} ); diff --git a/assets/src/stories-editor/helpers/snapping/test/getBestSnapLines.js b/assets/src/stories-editor/helpers/snapping/test/getBestSnapLines.js new file mode 100644 index 00000000000..aaa34a65270 --- /dev/null +++ b/assets/src/stories-editor/helpers/snapping/test/getBestSnapLines.js @@ -0,0 +1,59 @@ +/** + * Internal dependencies + */ +import getBestSnapLines from '../getBestSnapLines'; + +describe( 'getBestSnapLines', () => { + const snapLines = { + 0: Array( 1 ), + 100: Array( 2 ), + 300: Array( 3 ), + 305: Array( 4 ), + }; + + const gap = 10; + + it( 'should return nothing if none match', () => { + const start = 40; + const end = 60; + + expect( getBestSnapLines( snapLines, start, end, gap ) ).toHaveLength( 0 ); + } ); + + it( 'should return match if start matches within gap', () => { + const start = 5; + const end = 60; + + expect( getBestSnapLines( snapLines, start, end, gap ) ).toHaveLength( 1 ); + } ); + + it( 'should return match if end matches within gap', () => { + const start = 40; + const end = 91; + + expect( getBestSnapLines( snapLines, start, end, gap ) ).toHaveLength( 2 ); + } ); + + it( 'should return match if center matches within gap', () => { + const start = 60; + const end = 135; + + expect( getBestSnapLines( snapLines, start, end, gap ) ).toHaveLength( 2 ); + } ); + + it( 'should return best if multiple edges match within gap', () => { + const start = 5; + const end = 190; + + // Center coordinate will match 100 better than start coordinate will match 0. + expect( getBestSnapLines( snapLines, start, end, gap ) ).toHaveLength( 2 ); + } ); + + it( 'should return best if multiple lines match the same edge within gap', () => { + const start = 200; + const end = 302; + + // End will match 300 better than 305, and no other edge will match. + expect( getBestSnapLines( snapLines, start, end, gap ) ).toHaveLength( 3 ); + } ); +} ); From 3cb256545e294c2c81fc6f0663ef3500aa9fa76a Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 7 Oct 2019 09:40:52 -0400 Subject: [PATCH 77/80] Lint fixes --- assets/src/stories-editor/components/block-mover/draggable.js | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/src/stories-editor/components/block-mover/draggable.js b/assets/src/stories-editor/components/block-mover/draggable.js index d3324fad2a3..24b191b7b63 100644 --- a/assets/src/stories-editor/components/block-mover/draggable.js +++ b/assets/src/stories-editor/components/block-mover/draggable.js @@ -357,7 +357,6 @@ Draggable.propTypes = { children: PropTypes.func.isRequired, horizontalSnaps: PropTypes.func.isRequired, verticalSnaps: PropTypes.func.isRequired, - snapLines: PropTypes.array.isRequired, setSnapLines: PropTypes.func.isRequired, clearSnapLines: PropTypes.func.isRequired, parentBlockElement: PropTypes.object, From a661493b94fde40d40f12193302aeaa2ccb877be Mon Sep 17 00:00:00 2001 From: Morten Barklund Date: Mon, 7 Oct 2019 12:02:52 -0400 Subject: [PATCH 78/80] Optimize resizable snapping references --- .../stories-editor/components/resizable-box/index.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/assets/src/stories-editor/components/resizable-box/index.js b/assets/src/stories-editor/components/resizable-box/index.js index 62155358f72..813125b1c76 100644 --- a/assets/src/stories-editor/components/resizable-box/index.js +++ b/assets/src/stories-editor/components/resizable-box/index.js @@ -78,7 +78,6 @@ class EnhancedResizableBox extends Component { snapGap, horizontalSnaps, verticalSnaps, - snapLines, setSnapLines, clearSnapLines, parentBlockElement, @@ -130,9 +129,7 @@ class EnhancedResizableBox extends Component { this.setState( { isResizing: false } ); - if ( snapLines.length ) { - clearSnapLines(); - } + clearSnapLines(); onResizeStop( { width: parseInt( appliedWidth ), @@ -168,9 +165,7 @@ class EnhancedResizableBox extends Component { this.setState( { isResizing: true } ); - if ( snapLines.length ) { - clearSnapLines(); - } + clearSnapLines(); onResizeStart(); } } @@ -337,7 +332,6 @@ EnhancedResizableBox.propTypes = { horizontalSnaps: PropTypes.func.isRequired, verticalSnaps: PropTypes.func.isRequired, snapGap: PropTypes.number.isRequired, - snapLines: PropTypes.array.isRequired, setSnapLines: PropTypes.func.isRequired, clearSnapLines: PropTypes.func.isRequired, parentBlockElement: PropTypes.object, From 1852207970b0181189cb3bc3b0ba63fd7c91b5b5 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 7 Oct 2019 16:11:34 -0400 Subject: [PATCH 79/80] Make sure snap lines are above draggable overlay --- .../components/contexts/snapping/edit.css | 13 +++++++++++++ .../contexts/{snapping.js => snapping/index.js} | 11 +++-------- 2 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 assets/src/stories-editor/components/contexts/snapping/edit.css rename assets/src/stories-editor/components/contexts/{snapping.js => snapping/index.js} (89%) diff --git a/assets/src/stories-editor/components/contexts/snapping/edit.css b/assets/src/stories-editor/components/contexts/snapping/edit.css new file mode 100644 index 00000000000..93cfca6935b --- /dev/null +++ b/assets/src/stories-editor/components/contexts/snapping/edit.css @@ -0,0 +1,13 @@ +.amp-story-page-snap-lines { + position: absolute; + top: 0; + pointer-events: none; + + /* higher than draggable overlay */ + z-index: 20; +} + +.amp-story-page-snap-lines line { + stroke: #f00; + pointer-events: none; +} diff --git a/assets/src/stories-editor/components/contexts/snapping.js b/assets/src/stories-editor/components/contexts/snapping/index.js similarity index 89% rename from assets/src/stories-editor/components/contexts/snapping.js rename to assets/src/stories-editor/components/contexts/snapping/index.js index 033308f58e9..a94192f73e2 100644 --- a/assets/src/stories-editor/components/contexts/snapping.js +++ b/assets/src/stories-editor/components/contexts/snapping/index.js @@ -13,7 +13,8 @@ import { createContext, useState } from '@wordpress/element'; /** * Internal dependencies */ -import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../constants'; +import { STORY_PAGE_INNER_HEIGHT, STORY_PAGE_INNER_WIDTH } from '../../../constants'; +import './edit.css'; const SnapContext = createContext(); @@ -34,11 +35,7 @@ const Snapping = ( { children } ) => { { Boolean( snapLines.length ) && ( { snapLines.map( ( [ [ x1, y1 ], [ x2, y2 ] ], index ) => ( { y1={ y1 } x2={ x2 } y2={ y2 } - stroke="red" - pointerEvents="none" /> ) ) } From 2e2dc62ce9cfa186dccd53657068dc97df8f8e37 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 7 Oct 2019 16:35:05 -0400 Subject: [PATCH 80/80] Update snapshot --- .../components/contexts/test/__snapshots__/snapping.js.snap | 2 -- 1 file changed, 2 deletions(-) diff --git a/assets/src/stories-editor/components/contexts/test/__snapshots__/snapping.js.snap b/assets/src/stories-editor/components/contexts/test/__snapshots__/snapping.js.snap index d77d5ccfcdf..48bbf703881 100644 --- a/assets/src/stories-editor/components/contexts/test/__snapshots__/snapping.js.snap +++ b/assets/src/stories-editor/components/contexts/test/__snapshots__/snapping.js.snap @@ -3,8 +3,6 @@ exports[`Snapping should render a single snap line correctly when set 1`] = `