Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[AMP Stories] Ability to resize text block. #1972

Merged
merged 15 commits into from
Mar 19, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 42 additions & 5 deletions assets/css/amp-editor-story-blocks.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
8. Block mover
9. General block editor components
10. Custom Components
11. Text Block
100. Shame
100.1 Gutenberg - Warning div not clickable

Expand Down Expand Up @@ -670,6 +671,7 @@ div[data-type="amp/amp-story-page"] .editor-inner-blocks .editor-block-list__lay
left: -100px;
top: -50px;
border: none;
background: transparent;
}

div[data-type="amp/amp-story-page"] .wp-block-image {
Expand Down Expand Up @@ -768,11 +770,6 @@ div[data-type="amp/amp-story-page"] .wp-block-image {
* Custom block mover.
*/

.post-type-amp_story .editor-block-list__layout .editor-block-mover.is-visible,
.post-type-amp_story .editor-block-list__layout .editor-block-mover {
display: none;
}

.amp-story-editor-block-mover {
position: absolute;
width: 30px;
Expand All @@ -793,6 +790,46 @@ div[data-type="amp/amp-story-page"] .wp-block:hover .amp-story-editor-block-move
opacity: 1;
}

/**
* 11. Text Block.
*/
.amp-story-text__resize-container .components-resizable-box__handle-right {
right: -26px;
height: 50px;
top: calc(50% - 25px);
}
.amp-story-text__resize-container .components-resizable-box__handle-right::before {
margin: 14px 0;
}
.amp-story-text__resize-container .components-resizable-box__handle-bottom {
bottom: -26px;
width: 50px;
left: calc(50% - 25px);
}
.amp-story-text__resize-container .components-resizable-box__handle-bottom::before {
margin: 0 auto;
}

.editor-styles-wrapper #amp-story-editor .wp-block .wp-block[data-type="amp/amp-story-text"] {
width: initial;
}

.wp-block[data-type="amp/amp-story-text"] .components-resizable-box__handle,
.wp-block.is-typing[data-type="amp/amp-story-text"] .components-resizable-box__handle{
display: block;
opacity: 0;
transition: opacity .3s;
}

.wp-block[data-type="amp/amp-story-text"]:hover .components-resizable-box__handle,
.wp-block[data-type="amp/amp-story-text"] .components-resizable-box__handle:hover {
opacity: 100;
}

.wp-block[data-type="amp/amp-story-text"] .block-editor-rich-text__editable {
display: inline-block;
}

/*
* 100. Shame
*/
Expand Down
4 changes: 0 additions & 4 deletions assets/css/amp-stories.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,3 @@ amp-story-grid-layer[template="fill"] .wp-block-image { margin: 0; }
.has-background {
padding: 10px !important;
}

amp-story-grid-layer > * {
width: auto !important;
swissspidy marked this conversation as resolved.
Show resolved Hide resolved
}
273 changes: 179 additions & 94 deletions assets/src/blocks/amp-story-text/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { Fragment } from '@wordpress/element';
import { PanelBody, SelectControl, withFallbackStyles } from '@wordpress/components';
import { Component, Fragment } from '@wordpress/element';
import { PanelBody, ResizableBox, SelectControl, withFallbackStyles } from '@wordpress/components';
import { compose } from '@wordpress/compose';
import {
RichText,
Expand All @@ -28,6 +28,9 @@ import { maybeEnqueueFontStyle } from '../../helpers';

const { getComputedStyle } = window;

const maxLimitFontSize = 54;
const minLimitFontSize = 14;

const applyFallbackStyles = withFallbackStyles( ( node, ownProps ) => {
const { textColor, backgroundColor, fontSize, customFontSize } = ownProps.attributes;
const editableNode = node.querySelector( '[contenteditable="true"]' );
Expand All @@ -40,108 +43,190 @@ const applyFallbackStyles = withFallbackStyles( ( node, ownProps ) => {
};
} );

function TextBlock( props ) {
const {
attributes,
setAttributes,
className,
fontSize,
setFontSize,
backgroundColor,
textColor,
setBackgroundColor,
setTextColor,
fallbackTextColor,
fallbackBackgroundColor,
} = props;
class TextBlockEdit extends Component {
componentDidUpdate() {
Copy link
Contributor Author

@miina miina Mar 18, 2019

Choose a reason for hiding this comment

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

@swissspidy I've added the amp-fit-text's replication logic here. Note that I didn't make any changes to fontSizePicker (yet), so I renamed the PR back to WIP.

Testing this it feels like maybe it would be good if the user could still assign a custom font size, too. Perhaps we could even have just only two values for this: Auto (amp-fit-text logic) and Custom. Thoughts?

Also, let me know if you know of a better place for adding the logic, note that it needs to check the element's changing width and height to calculate the font size so the element should be rendered.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Testing this it feels like maybe it would be good if the user could still assign a custom font size, too.
Note that I didn't make any changes to FontSizePicker (yet)

At the moment I see two reasonable options here:

  • Add a new "Auto" option to font size dropdown *
  • Add a "Automatically fit text to container" ToggleControl (like in the post editor) that is enabled by default.
    The font size picker would only be displayed when this toggle is disabled. Screenshot:

Screenshot 2019-03-18 at 22 18 30

* Worth noting that the font options come from the withFontSizes HOC (and thus editor settings), not sure how easy it is to override these.


Besides this, I'd probably extract calculateFontSize in its own class method or even move it to helper.js as it is static.

Also, I am not sure if this should use setFontSize() or use a new attribute, e.g. autoFontSize. But that probably makes more sense when using option 2 from above (toggle). That way you can set a custom font size, turn on the toggle, turn it off, and get back your custom font size from before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Add a "Automatically fit text to container" ToggleControl (like in the post editor) that is enabled by default.
The font size picker would only be displayed when this toggle is disabled.

Good idea, makes it consistent with other options too.

Besides this, I'd probably extract calculateFontSize in its own class method or even move it to helper.js as it is static.

That's also a good point, especially since it would potentially be good to use it in case of other blocks at some point as well and not just with AMP Stories.

That way you can set a custom font size, turn on the toggle, turn it off, and get back your custom font size from before.

Hmm. There are some cases where it might be unexpected to switch back to an old custom font size, however, there are more cases where it makes sense. 👍

const { attributes, setFontSize, fontSize, isSelected } = this.props;
if ( isSelected ) {
/**
* Calculates font size that fits to the text element based on the element's size.
* Replicates amp-fit-text's logic in the editor.
*
* @see https://github.com/ampproject/amphtml/blob/e7a1b3ff97645ec0ec482192205134bd0735943c/extensions/amp-fit-text/0.1/amp-fit-text.js
*
* @param {Object} measurer HTML element.
* @param {number} expectedHeight Maximum height.
* @param {number} expectedWidth Maximum width.
* @param {number} maxFontSize Maximum font size.
* @param {number} minFontSize Minimum font size.
* @return {number} Calculated font size.
*/
const calculateFontSize = ( measurer, expectedHeight, expectedWidth, maxFontSize, minFontSize ) => {
maxFontSize++;
// Binomial search for the best font size.
while ( maxFontSize - minFontSize > 1 ) {
const mid = Math.floor( ( minFontSize + maxFontSize ) / 2 );
measurer.style.fontSize = mid + 'px';
const currentHeight = measurer.offsetHeight;
const currentWidth = measurer.offsetWidth;
if ( currentHeight > expectedHeight || currentWidth > expectedWidth ) {
maxFontSize = mid;
} else {
minFontSize = mid;
}
}
return minFontSize;
};
// Check if the font size is OK, if not, update the font size if not.
const element = document.querySelector( `#block-${ this.props.clientId } .block-editor-rich-text__editable` );
if ( element ) {
const fitFontSize = calculateFontSize( element, attributes.height, attributes.width, maxLimitFontSize, minLimitFontSize );
if ( fontSize.size !== fitFontSize ) {
setFontSize( fitFontSize );
}
}
}
}

const {
placeholder,
content,
type,
ampFontFamily,
} = attributes;
render() {
const {
attributes,
setAttributes,
className,
fontSize,
isSelected,
setFontSize,
backgroundColor,
textColor,
setBackgroundColor,
setTextColor,
fallbackTextColor,
fallbackBackgroundColor,
toggleSelection,
} = this.props;

return (
<Fragment>
<InspectorControls>
<PanelBody title={ __( 'Text Settings', 'amp' ) }>
<FontFamilyPicker
name={ ampFontFamily }
onChange={ ( value ) => {
maybeEnqueueFontStyle( value );
setAttributes( { ampFontFamily: value } );
} }
/>
<FontSizePicker
value={ fontSize.size }
onChange={ setFontSize }
/>
<SelectControl
label={ __( 'Select text type', 'amp' ) }
value={ type }
onChange={ ( selected ) => setAttributes( { type: selected } ) }
options={ [
{ value: 'auto', label: __( 'Automatic', 'amp' ) },
{ value: 'p', label: __( 'Paragraph', 'amp' ) },
{ value: 'h1', label: __( 'Heading 1', 'amp' ) },
{ value: 'h2', label: __( 'Heading 2', 'amp' ) },
const {
placeholder,
content,
type,
ampFontFamily,
height,
width,
} = attributes;

const minTextHeight = 20;
const minTextWidth = 30;

return (
<Fragment>
<InspectorControls>
<PanelBody title={ __( 'Text Settings', 'amp' ) }>
<FontFamilyPicker
name={ ampFontFamily }
onChange={ ( value ) => {
maybeEnqueueFontStyle( value );
setAttributes( { ampFontFamily: value } );
} }
/>
<FontSizePicker
value={ fontSize.size }
onChange={ setFontSize }
/>
<SelectControl
label={ __( 'Select text type', 'amp' ) }
value={ type }
onChange={ ( selected ) => setAttributes( { type: selected } ) }
options={ [
{ value: 'auto', label: __( 'Automatic', 'amp' ) },
{ value: 'p', label: __( 'Paragraph', 'amp' ) },
{ value: 'h1', label: __( 'Heading 1', 'amp' ) },
{ value: 'h2', label: __( 'Heading 2', 'amp' ) },
] }
/>
</PanelBody>
<PanelColorSettings
title={ __( 'Color Settings', 'amp' ) }
initialOpen={ false }
colorSettings={ [
{
value: backgroundColor.color,
onChange: setBackgroundColor,
label: __( 'Background Color', 'amp' ),
},
{
value: textColor.color,
onChange: setTextColor,
label: __( 'Text Color', 'amp' ),
},
] }
/>
</PanelBody>
<PanelColorSettings
title={ __( 'Color Settings', 'amp' ) }
initialOpen={ false }
colorSettings={ [
{
value: backgroundColor.color,
onChange: setBackgroundColor,
label: __( 'Background Color', 'amp' ),
},
{
value: textColor.color,
onChange: setTextColor,
label: __( 'Text Color', 'amp' ),
},
] }
>
<ContrastChecker
{ ...{
textColor: textColor.color,
backgroundColor: backgroundColor.color,
fallbackTextColor,
fallbackBackgroundColor,
fontSize: fontSize.size,
} }
/>
</PanelColorSettings>
</InspectorControls>
<ResizableBox
className={ classnames(
'amp-story-text__resize-container',
{ 'is-selected': isSelected }
) }
size={ {
height,
width,
} }
minHeight={ minTextHeight }
minWidth={ minTextWidth }
// Adding only right and bottom since otherwise it needs to change the top and left position, too.
enable={ {
top: false,
right: true,
bottom: true,
left: false,
} }
onResizeStop={ ( event, direction, elt, delta ) => {
setAttributes( {
width: parseInt( width + delta.width, 10 ),
height: parseInt( height + delta.height, 10 ),
} );
toggleSelection( true );
} }
onResizeStart={ () => {
toggleSelection( false );
} }
>
<ContrastChecker
{ ...{
textColor: textColor.color,
<RichText
identifier="content"
wrapperClassName="wp-block-amp-story-text"
tagName="p"
value={ content }
onChange={ ( value ) => setAttributes( { content: value } ) }
style={ {
backgroundColor: backgroundColor.color,
fallbackTextColor,
fallbackBackgroundColor,
fontSize: fontSize.size,
color: textColor.color,
fontSize: fontSize.size ? fontSize.size + 'px' : undefined,
} }
className={ classnames( className, {
'has-text-color': textColor.color,
'has-background': backgroundColor.color,
[ backgroundColor.class ]: backgroundColor.class,
[ textColor.class ]: textColor.class,
[ fontSize.class ]: fontSize.class,
} ) }
placeholder={ placeholder || __( 'Write text…', 'amp' ) }
/>
</PanelColorSettings>
</InspectorControls>
<RichText
identifier="content"
wrapperClassName="wp-block-amp-story-text"
tagName="p"
value={ content }
onChange={ ( value ) => setAttributes( { content: value } ) }
style={ {
backgroundColor: backgroundColor.color,
color: textColor.color,
fontSize: fontSize.size ? fontSize.size + 'px' : undefined,
} }
className={ classnames( className, {
'has-text-color': textColor.color,
'has-background': backgroundColor.color,
[ backgroundColor.class ]: backgroundColor.class,
[ textColor.class ]: textColor.class,
[ fontSize.class ]: fontSize.class,
} ) }
placeholder={ placeholder || __( 'Write text…', 'amp' ) }
/>
</Fragment>
);
</ResizableBox>
</Fragment>
);
}
}

export default compose(
withColors( 'backgroundColor', { textColor: 'color' } ),
withFontSizes( 'fontSize' ),
applyFallbackStyles
)( TextBlock );
)( TextBlockEdit );
Loading