Skip to content

Commit

Permalink
Add "Text & Image" Block (#9416)
Browse files Browse the repository at this point in the history
This commit implements a block that contains two areas one for a media element (image/video) and one area for blocks (buttons, paragraphs etc). The elements of both areas are vertically aligned.
Here instead of middle blocks, the parent block will self-contain the media area (that allows images or videos), so we have only one simple nested area for the content.
  • Loading branch information
jorgefilipecosta authored Oct 19, 2018
1 parent fd64343 commit e50efbf
Show file tree
Hide file tree
Showing 24 changed files with 816 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/block-library/src/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
@import "./image/editor.scss";
@import "./latest-comments/editor.scss";
@import "./latest-posts/editor.scss";
@import "./media-text/editor.scss";
@import "./list/editor.scss";
@import "./more/editor.scss";
@import "./nextpage/editor.scss";
Expand Down
2 changes: 2 additions & 0 deletions packages/block-library/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import * as cover from './cover';
import * as embed from './embed';
import * as file from './file';
import * as html from './html';
import * as mediaText from './media-text';
import * as latestComments from './latest-comments';
import * as latestPosts from './latest-posts';
import * as list from './list';
Expand Down Expand Up @@ -76,6 +77,7 @@ export const registerCoreBlocks = () => {
file,
window.wp && window.wp.oldEditor ? classic : null, // Only add the classic block in WP Context
html,
mediaText,
latestComments,
latestPosts,
missing,
Expand Down
163 changes: 163 additions & 0 deletions packages/block-library/src/media-text/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import {
BlockControls,
InnerBlocks,
InspectorControls,
PanelColorSettings,
withColors,
} from '@wordpress/editor';
import { Component, Fragment } from '@wordpress/element';
import { Toolbar } from '@wordpress/components';

/**
* Internal dependencies
*/
import MediaContainer from './media-container';

/**
* Constants
*/
const ALLOWED_BLOCKS = [ 'core/button', 'core/paragraph', 'core/heading', 'core/list' ];
const TEMPLATE = [
[ 'core/paragraph', { fontSize: 'large', placeholder: 'Content…' } ],
];

class MediaTextEdit extends Component {
constructor() {
super( ...arguments );

this.onSelectMedia = this.onSelectMedia.bind( this );
this.onWidthChange = this.onWidthChange.bind( this );
this.commitWidthChange = this.commitWidthChange.bind( this );
this.state = {
mediaWidth: null,
};
}

onSelectMedia( media ) {
const { setAttributes } = this.props;

let mediaType;
// for media selections originated from a file upload.
if ( media.media_type ) {
if ( media.media_type === 'image' ) {
mediaType = 'image';
} else {
// only images and videos are accepted so if the media_type is not an image we can assume it is a video.
// video contain the media type of 'file' in the object returned from the rest api.
mediaType = 'video';
}
} else { // for media selections originated from existing files in the media library.
mediaType = media.type;
}

setAttributes( {
mediaAlt: media.alt,
mediaId: media.id,
mediaType,
mediaUrl: media.url,
} );
}

onWidthChange( width ) {
this.setState( {
mediaWidth: width,
} );
}

commitWidthChange( width ) {
const { setAttributes } = this.props;

setAttributes( {
mediaWidth: width,
} );
this.setState( {
mediaWidth: null,
} );
}

renderMediaArea() {
const { attributes } = this.props;
const { mediaAlt, mediaId, mediaPosition, mediaType, mediaUrl, mediaWidth } = attributes;

return (
<MediaContainer
className="block-library-media-text__media-container"
onSelectMedia={ this.onSelectMedia }
onWidthChange={ this.onWidthChange }
commitWidthChange={ this.commitWidthChange }
{ ...{ mediaAlt, mediaId, mediaType, mediaUrl, mediaPosition, mediaWidth } }
/>
);
}

render() {
const {
attributes,
className,
backgroundColor,
setAttributes,
setBackgroundColor,
} = this.props;
const { mediaPosition, mediaWidth } = attributes;
const temporaryMediaWidth = this.state.mediaWidth;
const classNames = classnames( className, {
'has-media-on-the-right': 'right' === mediaPosition,
[ backgroundColor.class ]: backgroundColor.class,
} );
const widthString = `${ temporaryMediaWidth || mediaWidth }%`;
const style = {
gridTemplateColumns: 'right' === mediaPosition ? `auto ${ widthString }` : `${ widthString } auto`,
backgroundColor: backgroundColor.color,
};
const colorSettings = [ {
value: backgroundColor.color,
onChange: setBackgroundColor,
label: __( 'Background Color' ),
} ];
const toolbarControls = [ {
icon: 'align-pull-left',
title: __( 'Show media on left' ),
isActive: mediaPosition === 'left',
onClick: () => setAttributes( { mediaPosition: 'left' } ),
}, {
icon: 'align-pull-right',
title: __( 'Show media on right' ),
isActive: mediaPosition === 'right',
onClick: () => setAttributes( { mediaPosition: 'right' } ),
} ];
return (
<Fragment>
<InspectorControls>
<PanelColorSettings
title={ __( 'Color Settings' ) }
initialOpen={ false }
colorSettings={ colorSettings }
/>
</InspectorControls>
<BlockControls>
<Toolbar
controls={ toolbarControls }
/>
</BlockControls>
<div className={ classNames } style={ style } >
{ this.renderMediaArea() }
<InnerBlocks
allowedBlocks={ ALLOWED_BLOCKS }
template={ TEMPLATE }
/>
</div>
</Fragment>
);
}
}

export default withColors( 'backgroundColor' )( MediaTextEdit );
59 changes: 59 additions & 0 deletions packages/block-library/src/media-text/editor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.wp-block-media-text,
.wp-block-media-text.aligncenter {
grid-template-areas:
"media-text-media media-text-content"
"resizer resizer";
}

.wp-block-media-text.has-media-on-the-right,
.wp-block-media-text.has-media-on-the-right.aligncenter {
grid-template-areas:
"media-text-content media-text-media"
"resizer resizer";
}

.wp-block-media-text .__resizable_base__ {
grid-area: resizer;
}

.wp-block-media-text .editor-media-container__resizer {
grid-area: media-text-media;
align-self: center;
// The resizer sets a inline width but as we are using a grid layout,
// we set the width on container.
width: 100% !important;
}

.wp-block-media-text .editor-inner-blocks {
word-break: break-word;
grid-area: media-text-content;
text-align: initial;
padding: 0 8% 0 8%;
}

.wp-block-media-text > .editor-inner-blocks > .editor-block-list__layout > .editor-block-list__block {
max-width: unset;
}

figure.block-library-media-text__media-container {
margin: 0;
height: 100%;
width: 100%;
}

.wp-block-media-text .block-library-media-text__media-container img,
.wp-block-media-text .block-library-media-text__media-container video {
margin-bottom: -8px;
width: 100%;
}

.editor-media-container__resizer .components-resizable-box__handle {
display: none;
}

.editor-block-list__block.is-selected,
.editor-block-list__block.is-focused {
.editor-media-container__resizer .components-resizable-box__handle {
display: block;
}
}
120 changes: 120 additions & 0 deletions packages/block-library/src/media-text/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* External dependencies
*/
import { noop } from 'lodash';
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import {
InnerBlocks,
getColorClassName,
} from '@wordpress/editor';

/**
* Internal dependencies
*/
import edit from './edit';

const DEFAULT_MEDIA_WIDTH = 50;

export const name = 'core/media-text';

export const settings = {
title: __( 'Media & Text' ),

icon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 17h8v-2h-8v2zM3 19h8V5H3v14zM13 9h8V7h-8v2zm0 4h8v-2h-8v2z" /></svg>,

category: 'layout',

keywords: [ __( 'image' ), __( 'video' ), __( 'half' ) ],

attributes: {
align: {
type: 'string',
default: 'wide',
},
backgroundColor: {
type: 'string',
},
customBackgroundColor: {
type: 'string',
},
mediaAlt: {
type: 'string',
source: 'attribute',
selector: 'figure img',
attribute: 'alt',
default: '',
},
mediaPosition: {
type: 'string',
default: 'left',
},
mediaId: {
type: 'number',
},
mediaUrl: {
type: 'string',
source: 'attribute',
selector: 'figure video,figure img',
attribute: 'src',
},
mediaType: {
type: 'string',
},
mediaWidth: {
type: 'number',
default: 50,
},
},

supports: {
align: [ 'wide', 'full' ],
},

edit,

save( { attributes } ) {
const {
backgroundColor,
customBackgroundColor,
mediaAlt,
mediaPosition,
mediaType,
mediaUrl,
mediaWidth,
} = attributes;
const mediaTypeRenders = {
image: () => <img src={ mediaUrl } alt={ mediaAlt } />,
video: () => <video controls src={ mediaUrl } />,
};

const backgroundClass = getColorClassName( 'background-color', backgroundColor );
const className = classnames( {
'has-media-on-the-right': 'right' === mediaPosition,
[ backgroundClass ]: backgroundClass,
} );

let gridTemplateColumns;
if ( mediaWidth !== DEFAULT_MEDIA_WIDTH ) {
gridTemplateColumns = 'right' === mediaPosition ? `auto ${ mediaWidth }%` : `${ mediaWidth }% auto`;
}
const style = {
backgroundColor: backgroundClass ? undefined : customBackgroundColor,
gridTemplateColumns,
};
return (
<div className={ className } style={ style }>
<figure className="wp-block-media-text__media" >
{ ( mediaTypeRenders[ mediaType ] || noop )() }
</figure>
<div className="wp-block-media-text__content">
<InnerBlocks.Content />
</div>
</div>
);
},
};
Loading

0 comments on commit e50efbf

Please sign in to comment.