-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Gallery: Add simple columns slider #1135
Changes from all commits
a3fc90d
d4c251b
6df86e3
b906822
6b7bf26
73fd306
c42cf12
d4eb05a
bf13196
b6b1093
4aa3e37
00ecd81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,20 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { __ } from 'i18n'; | ||
import './style.scss'; | ||
import { registerBlockType, query as hpq } from '../../api'; | ||
|
||
import Placeholder from 'components/placeholder'; | ||
import MediaUploadButton from '../../media-upload-button'; | ||
import InspectorControls from '../../inspector-controls'; | ||
|
||
import GalleryImage from './gallery-image'; | ||
|
||
const { query, attr } = hpq; | ||
|
||
const MAX_COLUMNS = 8; | ||
|
||
const editMediaLibrary = ( attributes, setAttributes ) => { | ||
const frameConfig = { | ||
frame: 'post', | ||
|
@@ -51,6 +55,11 @@ function toggleAlignment( align ) { | |
}; | ||
} | ||
|
||
function defaultColumnsNumber( attributes ) { | ||
attributes.images = attributes.images || []; | ||
return Math.min( 3, attributes.images.length ); | ||
} | ||
|
||
registerBlockType( 'core/gallery', { | ||
title: wp.i18n.__( 'Gallery' ), | ||
icon: 'format-gallery', | ||
|
@@ -61,15 +70,10 @@ registerBlockType( 'core/gallery', { | |
query( 'div.blocks-gallery figure.blocks-gallery-image img', { | ||
url: attr( 'src' ), | ||
alt: attr( 'alt' ), | ||
} ), | ||
} ) || [], | ||
}, | ||
|
||
controls: [ | ||
{ | ||
icon: 'format-image', | ||
title: wp.i18n.__( 'Edit Gallery' ), | ||
onClick: editMediaLibrary, | ||
}, | ||
{ | ||
icon: 'align-left', | ||
title: wp.i18n.__( 'Align left' ), | ||
|
@@ -89,16 +93,36 @@ registerBlockType( 'core/gallery', { | |
onClick: toggleAlignment( 'right' ), | ||
}, | ||
{ | ||
icon: 'align-full-width', | ||
title: wp.i18n.__( 'Wide width' ), | ||
icon: 'align-wide', | ||
title: __( 'Wide width' ), | ||
isActive: ( { align } ) => 'wide' === align, | ||
onClick: toggleAlignment( 'wide' ), | ||
}, | ||
{ | ||
icon: 'align-full-width', | ||
title: __( 'Full width' ), | ||
isActive: ( { align } ) => 'full' === align, | ||
onClick: toggleAlignment( 'full' ), | ||
}, | ||
{ | ||
icon: 'format-image', | ||
title: wp.i18n.__( 'Edit Gallery' ), | ||
onClick: editMediaLibrary, | ||
}, | ||
], | ||
|
||
edit( { attributes, setAttributes } ) { | ||
const { images, align = 'none' } = attributes; | ||
if ( ! images ) { | ||
getEditWrapperProps( attributes ) { | ||
const { align } = attributes; | ||
if ( 'left' === align || 'right' === align || 'wide' === align || 'full' === align ) { | ||
return { 'data-align': align }; | ||
} | ||
}, | ||
|
||
edit( { attributes, setAttributes, focus, id } ) { | ||
const { images = [], columns = defaultColumnsNumber( attributes ), align = 'none' } = attributes; | ||
const setColumnsNumber = ( event ) => setAttributes( { columns: event.target.value } ); | ||
const rangeId = `blocks-gallery-range-${ id }`; | ||
if ( images.length === 0 ) { | ||
const setMediaUrl = ( imgs ) => setAttributes( { images: imgs } ); | ||
return ( | ||
<Placeholder | ||
|
@@ -119,21 +143,26 @@ registerBlockType( 'core/gallery', { | |
} | ||
|
||
return ( | ||
<div className={ `blocks-gallery align${ align }` }> | ||
{ images.map( ( img, i ) => ( | ||
<GalleryImage key={ i } img={ img } /> | ||
<div className={ `blocks-gallery align${ align } columns-${ columns }` }> | ||
{ images.map( ( img ) => ( | ||
<GalleryImage key={ img.url } img={ img } /> | ||
) ) } | ||
{ focus && images.length > 1 && | ||
<InspectorControls> | ||
<label className="blocks-text-control__label" htmlFor={ rangeId }>{ __( 'Columns' ) }</label> | ||
<input id={ rangeId } type="range" min="1" max={ Math.min( MAX_COLUMNS, images.length ) } value={ columns } onChange={ setColumnsNumber } /> | ||
<span>{columns}</span> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could be a nice improvement to make the slider control generic (the same way we do for text) https://github.com/WordPress/gutenberg/blob/master/blocks/inspector-controls/text-control/index.js There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in #1144. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, @paulwilde! |
||
</InspectorControls> } | ||
</div> | ||
); | ||
}, | ||
|
||
save( { attributes } ) { | ||
const { images, align = 'none' } = attributes; | ||
|
||
const { images, columns = defaultColumnsNumber( attributes ), align = 'none' } = attributes; | ||
return ( | ||
<div className={ `blocks-gallery align${ align }` } > | ||
{ images.map( ( img, i ) => ( | ||
<GalleryImage key={ i } img={ img } /> | ||
<div className={ `blocks-gallery align${ align } columns-${ columns }` } > | ||
{ images.map( ( img ) => ( | ||
<GalleryImage key={ img.url } img={ img } /> | ||
) ) } | ||
</div> | ||
); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
<!-- wp:core/gallery --> | ||
<div class="blocks-gallery alignnone"> | ||
<figure class="blocks-gallery-image"><img src="https://cldup.com/uuUqE_dXzy.jpg" alt="title" /></figure> | ||
<div class="blocks-gallery alignnone columns-2"> | ||
<figure class="blocks-gallery-image"><img src="https://cldup.com/uuUqE_dXzy.jpg" alt="title" /></figure> | ||
<figure class="blocks-gallery-image"><img src="http://google.com/hi.png" alt="title" /></figure> | ||
</div> | ||
<!-- /wp:core/gallery --> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
query
should always return an array. In what cases was it not?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was caused by my own confusion and I should’ve removed it. Let me explain, because I think other block implementors can fall in the same trap. I wanted to add a default value for the
columns
attribute. The function that calculates that depends on the number of images and I hoped to avoid theattributes.images = attributes.images || [];
indefaultColumnsNumber()
. I hadn’t realized however that theattributes
property is evaluated only when parsing an existing block and not when creating a new one. So when I got the missingattributes.images
on creating a new block this was my first line of defense.I still haven't internalized where data comes from and the overall attributes flow, starting from a user adding a block, through making changes, persisting, loading,
setAttributes
, etc.Have you considered making the block a class, which has the
attributes
as a member variable and only modify it throughsetAttributes
ala React?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an interesting observation, and not one I'd internalized myself. I agree this would be quite confusing.
Attributes initialization in general is an area I think needs a considerable rethink. There's a handful of discussion issues and alternatives explorations that have been tried:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While initialization is a concern I think the bigger problems are the lifecycle (what happens when) and the pattern of passing
attributes
&setAttributes
around if we want to do anything slightly more complex.I still think it would make the most sense the block to be a class with few lifecycle methods. It introduces some state, but the clarity first, and the closeness with React/Vue, second are more important for me. @youknowriad, I know you didn't like the imperative solution, do you think there's a better way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really think most of the problem is a documentation problem. What does each property of the block settings mean. when it's executed. If we take a step back and take a look at the API, we can split it into three different parts:
1- Parsing attributes from content:
Currently done using the
attributes
anddefaultAttributes
setting. There are propositions to use aschema
orgetInitialAttributes
method.Maybe for clarity, we should simplify this to a
parse( commentAttributes, rawContent ) => blockAttributes
whererawContent
could be a string (like now) or a tree structure (like proposed elsewhere).2- Editing the attributes:
Once parsed, the block get rendered using the
edit
Component receivingattributes
andsetAttributes
. I like how this works, I think it's clear enough, maybe I'd just try to find a better name for this settings:form
orformComponent
. I don't think we should hide the fact that it's a component like we're trying to do now.3- Serializing attributes to content:
We do this now using the
save
method. Theschema
proposed could serve this purpose too, but we're not sure about it yet.My opinion is that we should make it clear that it's a serialization and that this functions is the opposite of the
parse
function.serialize( attributes ) => { commentAttributes, rawContent }
. ShouldrawContent
here be a string or a tree structure or a React Component. Good question.What I want to say is that we should break our documentation to three steps like this, I think, this makes the API clearer no matter the details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of my concerns here is difficulty in extending base classes if a plugin author chooses not to use ES6. This can be seen in React's
createClass
deprecation, which will now require an additional dependency if developers are bound to it for whatever reason. Class inheritance is not entirely unwieldy in ES5, depending if we need extends vs. plain class.ES5
class TextBlock
:ES5
class TextBlock extends Block
:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Classical_inheritance_with_Object.create()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aduth wouldn't we need ES6 to make everything work anyway? ES5 would bring other complications, like class variables.
@youknowriad I totally agree that better documentation and naming will go a long way. Speaking of that, looking at the current blocks, there are few two patterns that could be a bit cleaner.
edit
orsave
, because they’re run in a totally different context. And they need to often receiveattributes
and/orsetAttributes
.edit
andsave
.If we don't go the
class
route, here’s another suggestion: switch the docs (and our current blocks) to have only variable references or short literals in the block object itself and define everything at the top:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily. What did you have in mind? Assigning
edit
andsave
as React class components could be challenging, but maybe reasonable at that point to usecreate-react-class
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aduth if others’ code isn't part of a webpack build how would they import
registerBlockType
, so that they have access to the API? And if they’re already using webpack, ES6 becomes a reasonable expectation.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each entry point (top-level directory) exposes itself on the
wp
global object:https://github.com/WordPress/gutenberg/blob/740b93d/webpack.config.js#L27-L31
So by depending on
wp-blocks
, a plugin author can merely register their block usingwp.blocks.registerBlock
from the global scope.Also documented at: https://github.com/WordPress/gutenberg/tree/master/blocks