From eef7bc634a2d2f9fbcc3287abcbc342963265da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 7 Aug 2019 18:08:10 -0300 Subject: [PATCH] Try/even better previews (#16873) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * First stab at a proof of concept. * Attempt to programmatically determine dest dimensions * Add README stub. * Updates to use default args for width and height Addresses https://github.com/WordPress/gutenberg/pull/16113#discussion_r298130949 * Update class namespaces to match package Addresses https://github.com/WordPress/gutenberg/pull/16113#discussion_r298131236 * Add width and height as Effect Hook deps Addresses https://github.com/WordPress/gutenberg/pull/16113#discussion_r300334136 * Use correct `createRef` from @wordpress/element Was previously using React createRef. Addresses https://github.com/WordPress/gutenberg/pull/16113#discussion_r300325510 * Revert "Update class namespaces to match package" This reverts commit 24252fa887986548839e7d9a6fe2639807ed0bc6. * Migrate to useRef hook * Adds basic unit test Still a WIP * Refactor to avoid unecessary state and simplify implementation of scaling * Add some padding to the preview Avoids Blocks butting up against the edges of the preview, especially if the preview wrapper has rounded corners which can cause the Blocks to be clipped. * Refactor to utilise JS template strings * WIP: Adds zoom for small individual blocks With small individual Blocks showing the preview at “actual size” (even when scaled to fit) is a bit odd. Everything still looks very zoomed out and far away. Updates to check DOM for largest element within the Block and use that width to calculate a “zoomed” scale value so that the contents of the Block itself is shown zoomed in. Still WIP * Updates to target block contents more reliably and to calc entire box model in width * block-preview: render scaled preview in a shadow dom * try: setting styles to shadow dom elements * Revert "try: setting styles to shadow dom elements" This reverts commit ea573f9dd0401356ebab81b9df3e5079baf5de66. The reason being that there is no benefit of using shadow DOM and it introduces additional complexity. * Removes attempt at auto zoom on Block contents After much experimenting we’ve decided that attempting to reliably detect the visual size of the Block’s visual contents is impossible. This is due to inconsistencies in the DOM markup of each Block. See https://github.com/WordPress/gutenberg/pull/16113#issuecomment-516916262 * Updates to remove scale by default in favour of opt in via prop As discussed here https://github.com/WordPress/gutenberg/pull/16113#issuecomment-516917681, there is little point in having thumbnail preview and large popover preview showing the same thing. Update to introduce a prop to opt in to scaling the preview. By default the preview is no auto-scaled. Apply `true` value to `BlockPreview` to ensure that Block Styles popover preview is scaled to fit. * Adds scaleFactor prop to provide control for non dynamically scaled preview * Adjust scaleFactor for Block Styles preview * Fix to ensure hooks are called unconditionally to obey rules of Hooks See https://reactjs.org/docs/hooks-rules.html * Removes padding and border from preview on designer advice * Only apply dimensions to preview content when dynamically scaling * block-preview: a different approach attempt. * Correct typo in util function name * Seek the firstChild of the Block DOM node * Tidy up effect timers and improve comments * Dynamically calculate widths and offsets of preview container Removes hardcoded values. * Updates to account for widths of all Blocks passed into to preview Preivously we only considered a single Block scenario. However, the recent addition of multi block support for Previews means we need to ensure we’re measuring the widths of _all_ Blocks. * Revert class additions to Blocks * Adds optional scope to DOM utils. Use to limit scope in Block width comparison. * Update packages/block-editor/src/components/block-preview/index.js Co-Authored-By: Riad Benguella * Allow prop based opt out from scaling * Fix to ensure smallest Block is found even if smaller than container Preivously we were default to the container width being the largest item. In fact we always want to determine the largest Block contents element and use that to determine the scale. * Remove unecessary Math.min usage * core/button: adjust dims of button wrapper * apply vertical alignment * set scale factor to 0.9 * rollback testing purpose comment * cpre/button: keep adjusting styles for preview * rename vars and css class names * adjust rebase * restore isScaled factor scale * set the scale adjustment in the proper place * update README file * core/button: tidy edit preview styles * coer/button: tweak button preview * preview-block: ensure make the preview visible. * block-preview: hide dropzone * core/button: adjust preview for thumb and full sizes * core/button: set same width for preview * core/button: because it's !important * core/button: set nowrap button in preview * core/button: set nowrap onlu for button * core/quote: adjust quote size * Make previews overflow to the bottom. Currently still has a bug where the large `.block-editor-block-switcher__preview` pane behaves like it's previewing a taller block, when it's not * Revert "core/button: set nowrap onlu for button" It breaks previews of long buttons This reverts commit 758468c803a6abec1ad9aed00f240f3af44cf86d. * Fix unit test * core/button: apply nowrap only to buttons * Try a fixed canvas width * Fix blocks editor styles * core/button: centering preview * core/button: adjust only into teh styles preview * block-preview: remove scaleAdjustment property * block-preview: hide inserte element in preview * block-preview: just pick up the first block to scale * block-preview: fix set tall class. X position (wip) * popover: remove commented lines * Refactor the preview * Remove debug code * simplify preview resets * Fix the preview recomputing * Vertical alignining small blocks * block-editor: restore viewportWidth as a property * Update Readme docs Props @marekhrabe. * Remove tests for now We'll revisit when we have a better idea what specifically we should be testing * block-preview: simplify comparision * Remove readme updates that break tests * Update docs properly. See 6f29c27 --- packages/block-editor/README.md | 9 +- .../src/components/block-preview/README.md | 14 +- .../src/components/block-preview/index.js | 129 ++++++++++++++---- .../src/components/block-preview/style.scss | 77 +++++++++-- .../src/components/block-styles/index.js | 7 +- .../src/components/block-styles/style.scss | 24 ++-- packages/block-editor/src/utils/dom.js | 15 +- packages/block-library/src/button/edit.js | 4 + packages/block-library/src/button/editor.scss | 20 +-- 9 files changed, 217 insertions(+), 82 deletions(-) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 477c8657e40db..1d230d45fc3a2 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -124,11 +124,16 @@ Undocumented declaration. # **BlockPreview** -BlockPreview renders a preview given an array of blocks. +BlockPreview renders a preview of a block or array of blocks. + +_Related_ + +- _Parameters_ -- _props_ `Object`: Component props. +- _blocks_ `(Array|Object)`: A block instance (object) or an array of blocks to be previewed. +- _viewportWidth_ `number`: Width of the preview container in pixels. Controls at what size the blocks will be rendered inside the preview. Default: 700. _Returns_ diff --git a/packages/block-editor/src/components/block-preview/README.md b/packages/block-editor/src/components/block-preview/README.md index 10aaec2dd4001..b415f2bbc2237 100644 --- a/packages/block-editor/src/components/block-preview/README.md +++ b/packages/block-editor/src/components/block-preview/README.md @@ -10,20 +10,22 @@ Render the component passing in the required props: ```jsx ``` ## Props ### `blocks` -* **Type:** `array|object` +* **Type:** `Array|Object` * **Default:** `undefined` A block instance (object) or a blocks array you would like to render a preview. -### `isScaled` -* **Type:** `Boolean` -* **Default:** `false` +### `viewportWidth` +* **Type:** `Int` +* **Default:** `700` -Use this if you need to render previews in smaller areas, like block thumbnails. +Width of the preview container in pixels. Controls at what size the blocks will be rendered inside the preview. + +`viewportWidth` can be used to simulate how blocks look on different device sizes or to make sure make sure multiple previews will be rendered with the same scale, regardless of their content. diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index b63fd4abbf7dd..d8235eea0c1dd 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -9,46 +9,129 @@ import classnames from 'classnames'; */ import { Disabled } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; +import { useLayoutEffect, useState, useRef, useReducer, useMemo } from '@wordpress/element'; /** * Internal dependencies */ import BlockEditorProvider from '../provider'; import BlockList from '../block-list'; +import { getBlockPreviewContainerDOMNode } from '../../utils/dom'; -export function BlockPreview( { blocks, settings, className, isScaled } ) { - if ( ! blocks ) { +function ScaledBlockPreview( { blocks, viewportWidth } ) { + const previewRef = useRef( null ); + + const [ isTallPreview, setIsTallPreview ] = useState( false ); + const [ isReady, setIsReady ] = useState( false ); + const [ previewScale, setPreviewScale ] = useState( 1 ); + const [ { x, y }, setPosition ] = useState( { x: 0, y: 0 } ); + + // Dynamically calculate the scale factor + useLayoutEffect( () => { + // Timer - required to account for async render of `BlockEditorProvider` + const timerId = setTimeout( () => { + const containerElement = previewRef.current; + if ( ! containerElement ) { + return; + } + + // If we're previewing a single block, scale the preview to fit it. + if ( blocks.length === 1 ) { + const block = blocks[ 0 ]; + const previewElement = getBlockPreviewContainerDOMNode( block.clientId, containerElement ); + if ( ! previewElement ) { + return; + } + + const containerElementRect = containerElement.getBoundingClientRect(); + const scaledElementRect = previewElement.getBoundingClientRect(); + + const scale = containerElementRect.width / scaledElementRect.width || 1; + const offsetX = scaledElementRect.left - containerElementRect.left; + const offsetY = ( containerElementRect.height > scaledElementRect.height * scale ) ? + ( containerElementRect.height - ( scaledElementRect.height * scale ) ) / 2 : 0; + + setIsTallPreview( scaledElementRect.height * scale > containerElementRect.height ); + setPreviewScale( scale ); + setPosition( { x: offsetX * scale, y: offsetY } ); + + // Hack: we need to reset the scaled elements margins + previewElement.style.marginTop = '0'; + } else { + const containerElementRect = containerElement.getBoundingClientRect(); + setPreviewScale( containerElementRect.width / viewportWidth ); + setIsTallPreview( true ); + } + + setIsReady( true ); + }, 100 ); + + // Cleanup + return () => { + if ( timerId ) { + window.clearTimeout( timerId ); + } + }; + }, [] ); + + if ( ! blocks || blocks.length === 0 ) { return null; } + + const previewStyles = { + transform: `scale(${ previewScale })`, + visibility: isReady ? 'visible' : 'hidden', + left: -x, + top: y, + width: viewportWidth, + }; + + const contentClassNames = classnames( 'block-editor-block-preview__content editor-styles-wrapper', { + 'is-tall-preview': isTallPreview, + 'is-ready': isReady, + } ); + return ( - + + + + + ); +} + +export function BlockPreview( { blocks, viewportWidth = 700, settings } ) { + const renderedBlocks = useMemo( () => castArray( blocks ), [ blocks ] ); + const [ recompute, triggerRecompute ] = useReducer( ( state ) => state + 1, 0 ); + useLayoutEffect( triggerRecompute, [ blocks ] ); + return ( + - - - - + { + /* + * The key prop is used to force recomputing the preview + * by remounting the component, ScaledBlockPreview is not meant to + * be rerendered. + */ + } + + ); } /** - * BlockPreview renders a preview given an array of blocks. + * BlockPreview renders a preview of a block or array of blocks. * - * @param {Object} props Component props. + * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/block-preview/README.md * + * @param {Array|Object} blocks A block instance (object) or an array of blocks to be previewed. + * @param {number} viewportWidth Width of the preview container in pixels. Controls at what size the blocks will be rendered inside the preview. Default: 700. * @return {WPElement} Rendered element. */ export default withSelect( ( select ) => { diff --git a/packages/block-editor/src/components/block-preview/style.scss b/packages/block-editor/src/components/block-preview/style.scss index ff42e7b3b4dcc..76c2f4ccaf10a 100644 --- a/packages/block-editor/src/components/block-preview/style.scss +++ b/packages/block-editor/src/components/block-preview/style.scss @@ -1,34 +1,87 @@ -.block-editor-block-preview { +// This is the preview that shows up to the right of the thumbnail when hovering. +.block-editor-block-switcher__preview { padding: $block-padding; font-family: $editor-font; overflow: hidden; width: 100%; + pointer-events: none; + display: none; + + @include break-medium { + display: block; + } + + .block-editor-block-preview__content { + font-family: $editor-font; + + > div { + font-family: $editor-font; + } + + &:not(.is-tall-preview) { + // Vertical alignment. + margin-top: 50%; + } + } + + .block-editor-block-preview__title { + margin-bottom: 10px; + color: $dark-gray-300; + } +} + +// These rules ensure the preview scales smoothly regardless of the container size. +.block-editor-block-preview__container { + // In the component, a top padding is provided as an inline style to provid an aspect-ratio. + // This positioning enables the content to sit on top of that padding to fit. + position: relative; + + // The preview component measures the pixel width of this item, so as to calculate the scale factor. + // But without this baseline width, it collapses to 0. + width: 100%; +} + +.block-editor-block-preview__content { + // This element receives inline styles for width, height, and transform-scale. + // Those inline styles are calculated to fit a perfect thumbnail. + + // Position above the padding. + position: absolute; + + // Vertical alignment. It works with the transform: translate(-50%, -50%)`, + top: 0; + left: 0; + + // Important to set the origin. + transform-origin: top left; + + // Resetting paddings, margins, and other. + text-align: initial; + margin: 0; + overflow: visible; + min-height: auto; - // Resetting the block editor paddings and margins .block-editor-block-list__layout, .block-editor-block-list__block { padding: 0; } + .editor-block-list__block-edit [data-block] { - margin-top: 0; + margin: 0; } > div section { height: auto; } - > .reusable-block-indicator { - display: none; + &.is-tall-preview { + top: 4px; } + .block-editor-block-list__insertion-point, + .block-editor-block-drop-zone, + .reusable-block-indicator, .block-list-appender { display: none; } - - &.is-scaled { - > div { - transform: scale(0.9); - transform-origin: center top; - } - } } diff --git a/packages/block-editor/src/components/block-styles/index.js b/packages/block-editor/src/components/block-styles/index.js index f9270094e4708..c5f7797aa1db8 100644 --- a/packages/block-editor/src/components/block-styles/index.js +++ b/packages/block-editor/src/components/block-styles/index.js @@ -122,12 +122,7 @@ function BlockStyles( { aria-label={ style.label || style.name } >
- +
{ style.label || style.name } diff --git a/packages/block-editor/src/components/block-styles/style.scss b/packages/block-editor/src/components/block-styles/style.scss index 988e7563878fc..e5eb662d93b1f 100644 --- a/packages/block-editor/src/components/block-styles/style.scss +++ b/packages/block-editor/src/components/block-styles/style.scss @@ -12,6 +12,7 @@ overflow: hidden; border-radius: $radius-round-rectangle; padding: $grid-size-small * 1.5; + padding-top: calc(50% * 0.75 - #{ $grid-size-small } * 1.5); &:focus { @include block-style__focus(); @@ -30,28 +31,21 @@ } } +// Show a little preview thumbnail for style variations. .block-editor-block-styles__item-preview { outline: $border-width solid transparent; // Shown in Windows High Contrast mode. - border: 1px solid rgba($dark-gray-900, 0.2); - overflow: hidden; padding: 0; - text-align: initial; + border: $border-width solid rgba($dark-gray-900, 0.2); border-radius: $radius-round-rectangle; display: flex; - height: 60px; + overflow: hidden; background: $white; + padding-top: 75%; + margin-top: -75%; - // Actual preview contents. - .block-editor-block-preview__content { - transform: scale(0.7); - transform-origin: center center; - width: 100%; - - // Unset some of the styles that might be inherited from the editor style. - margin: 0; - padding: 0; - overflow: visible; - min-height: auto; + .block-editor-block-preview__container { + padding-top: 0; + margin-top: -75%; } } diff --git a/packages/block-editor/src/utils/dom.js b/packages/block-editor/src/utils/dom.js index f6d3328d2fd86..fd732b40c5318 100644 --- a/packages/block-editor/src/utils/dom.js +++ b/packages/block-editor/src/utils/dom.js @@ -4,11 +4,22 @@ * in cases where isolated behaviors need remote access to a block node. * * @param {string} clientId Block client ID. + * @param {Element} scope an optional DOM Element to which the selector should be scoped * * @return {Element} Block DOM node. */ -export function getBlockDOMNode( clientId ) { - return document.querySelector( '[data-block="' + clientId + '"]' ); +export function getBlockDOMNode( clientId, scope = document ) { + return scope.querySelector( '[data-block="' + clientId + '"]' ); +} + +export function getBlockPreviewContainerDOMNode( clientId, scope ) { + const domNode = getBlockDOMNode( clientId, scope ); + + if ( ! domNode ) { + return; + } + + return domNode.firstChild || domNode; } /** diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index b258486b98920..e70cc00b27854 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -33,6 +33,10 @@ import { const { getComputedStyle } = window; const applyFallbackStyles = withFallbackStyles( ( node, ownProps ) => { + if ( node ) { + node.classList.add( 'wp-block-button-wrapper' ); + } + const { textColor, backgroundColor } = ownProps; const backgroundColorValue = backgroundColor && backgroundColor.color; const textColorValue = textColor && textColor.color; diff --git a/packages/block-library/src/button/editor.scss b/packages/block-library/src/button/editor.scss index 9fe1a9afabd1b..da2116d2fc492 100644 --- a/packages/block-library/src/button/editor.scss +++ b/packages/block-library/src/button/editor.scss @@ -39,22 +39,6 @@ [data-rich-text-placeholder]::after { opacity: 0.8; } - - // Don't let the placeholder text wrap in the variation preview. - .block-editor-block-preview__content & { - max-width: 100%; - - .wp-block-button__link { - max-width: 100%; - overflow: hidden; - // Override is allowed here only because the rich text instance in - // a preview is not editable. - // To do: use the `save` function to preview a block transform, not - // the `edit` function. - white-space: nowrap !important; - text-overflow: ellipsis; - } - } } .wp-block-button__inline-link { @@ -91,3 +75,7 @@ margin-top: $grid-size-large; } } + +.wp-block-button-wrapper { + display: table; +}