Skip to content

Commit

Permalink
Added maximum file size validation to media upload; Passed maxUploadS…
Browse files Browse the repository at this point in the history
…ize client-assets.

This change makes client size validation of the file size, avoiding spending time uploading invalid files to server. It also shows a friendly error message if the file size validation fails.
  • Loading branch information
jorgefilipecosta committed May 10, 2018
1 parent a528611 commit 9ba0213
Show file tree
Hide file tree
Showing 18 changed files with 361 additions and 57 deletions.
13 changes: 13 additions & 0 deletions blocks/block-notices/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.block-notices {
margin: 0 0 12px 0;
width: 100%;

.notice {
margin-left: 0px;
margin-right: 0px;

p {
font-size: $default-font-size;
}
}
}
51 changes: 45 additions & 6 deletions blocks/editor-media-upload/index.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,63 @@
/**
* External dependencies
*/
import { noop } from 'lodash';

/**
* WordPress dependencies
*/
import { select } from '@wordpress/data';
import { mediaUpload } from '@wordpress/utils';
import { __, sprintf } from '@wordpress/i18n';

/**
* Upload a media file when the file upload button is activated.
* Wrapper around mediaUpload() that injects the current post ID.
*
* @param {Array} filesList List of files.
* @param {Function} onFileChange Function to be called each time a file or a temporary representation of the file is available.
* @param {string} allowedType The type of media that can be uploaded.
* @param {Object} $0 Parameters object passed to the function.
* @param {string} $0.allowedType The type of media that can be uploaded.
* @param {?Object} $0.additionalData Additional data to include in the request.
* @param {Array} $0.filesList List of files.
* @param {?number} $0.maxUploadFileSize Maximum upload size in bytes allowed for the site.
* @param {Function} $0.onError Function called when an error happens.
* @param {Function} $0.onFileChange Function called each time a file or a temporary representation of the file is available.
*/
export default function editorMediaUpload( filesList, onFileChange, allowedType ) {
export default function editorMediaUpload( {
allowedType,
filesList,
maxUploadFileSize,
onError = noop,
onFileChange,
} ) {
let postId = null;
// Editor isn't guaranteed in block context.
if ( select( 'core/editor' ) ) {
postId = select( 'core/editor' ).getCurrentPostId();
}
mediaUpload( filesList, onFileChange, allowedType, {
post: postId,
const errorHandler = ( { file, sizeAboveLimit, generalError } ) => {
let errorMsg;
if ( sizeAboveLimit ) {
errorMsg = sprintf(
__( '%s exceeds the maximum upload size for this site.' ),
file.name
);
} else if ( generalError ) {
errorMsg = sprintf(
__( 'Error while uploading file %s to the media library.' ),
file.name
);
}
onError( errorMsg );
};

mediaUpload( {
allowedType,
filesList,
onFileChange,
additionalData: {
post: postId,
},
maxUploadFileSize,
onError: errorHandler,
} );
}
87 changes: 87 additions & 0 deletions components/higher-order/with-notices/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* External dependencies
*/
import uuid from 'uuid/v4';

/**
* WordPress dependencies
*/
import { Component, createHigherOrderComponent } from '@wordpress/element';

/**
* Override the default edit UI to include notices if supported.
*
* @param {function|Component} OriginalComponent Original component.
* @return {Component} Wrapped component.
*/
export default createHigherOrderComponent( ( OriginalComponent ) => {
return class WrappedBlockEdit extends Component {
constructor() {
super( ...arguments );

this.createNotice = this.createNotice.bind( this );
this.createErrorNotice = this.createErrorNotice.bind( this );
this.removeNotice = this.removeNotice.bind( this );

this.state = {
noticeList: [],
};
}

/**
* Function passed down as a prop that adds a new notice.
*
* @param {Object} notice Notice to add.
*/
createNotice( notice ) {
const noticeToAdd = notice.id ? notice : { ...notice, id: uuid() };
this.setState( ( state ) => ( {
noticeList: [ ...state.noticeList, noticeToAdd ],
} ) );
}

/**
* Function passed as a prop that adds a new error notice.
*
* @param {string} msg Error message of the notice.
*/
createErrorNotice( msg ) {
this.createNotice( { status: 'error', content: msg } );
}

/**
* Removes a notice by id.
*
* @param {string} id Id of the notice to remove.
*/
removeNotice( id ) {
this.setState( ( state ) => ( {
noticeList: state.noticeList.filter( ( notice ) => notice.id !== id ),
} ) );
}

/**
* Removes all notices
*/
removeAllNotices() {
this.setState( {
noticeList: [],
} );
}

render() {
const notices = {
createNotice: this.createNotice,
createErrorNotice: this.createErrorNotice,
removeAllNotices: this.removeAllNotices,
removeNotice: this.removeNotice,
noticeList: this.state.noticeList,
};
return (
<OriginalComponent
{ ...this.props }
notices={ notices } />
);
}
};
} );
1 change: 1 addition & 0 deletions components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export { default as withFocusOutside } from './higher-order/with-focus-outside';
export { default as withFocusReturn } from './higher-order/with-focus-return';
export { default as withGlobalEvents } from './higher-order/with-global-events';
export { default as withInstanceId } from './higher-order/with-instance-id';
export { default as withNotices } from './higher-order/with-notices';
export { default as withSafeTimeout } from './higher-order/with-safe-timeout';
export { default as withSpokenMessages } from './higher-order/with-spoken-messages';
export { default as withState } from './higher-order/with-state';
14 changes: 12 additions & 2 deletions components/notice/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,21 @@ import { noop, omit } from 'lodash';
*/
import Notice from './';

function NoticeList( { notices, onRemove = noop, children } ) {
/**
* Renders a list of notices.
*
* @param {Object} $0 Props passed to the component.
* @param {Array} $0.notices Array of notices to render.
* @param {Function} $0.onRemove Function called when a notice should be removed / dismissed.
* @param {Object} $0.className Name of the class used by the component.
* @param {Object} $0.children Array of childs to be rendered inside the notice list.
* @return {Object} The rendered notices list.
*/
function NoticeList( { notices, onRemove = noop, className = 'components-notice-list', children } ) {
const removeNotice = ( id ) => () => onRemove( id );

return (
<div className="components-notice-list">
<div className={ className }>
{ children }
{ [ ...notices ].reverse().map( ( notice ) => (
<Notice { ...omit( notice, 'content' ) } key={ notice.id } onRemove={ removeNotice( notice.id ) }>
Expand Down
9 changes: 8 additions & 1 deletion components/placeholder/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,18 @@ import { isString } from 'lodash';
import './style.scss';
import Dashicon from '../dashicon';

function Placeholder( { icon, children, label, instructions, className, ...additionalProps } ) {
/**
* Renders a placeholder. Normally used by blocks to render their empty state.
*
* @param {Object} props The component props.
* @return {Object} The rendered placeholder.
*/
function Placeholder( { icon, children, label, instructions, className, notices, ...additionalProps } ) {
const classes = classnames( 'components-placeholder', className );

return (
<div { ...additionalProps } className={ classes }>
{ !! notices && notices }
<div className="components-placeholder__label">
{ isString( icon ) ? <Dashicon icon={ icon } /> : icon }
{ label }
Expand Down
6 changes: 5 additions & 1 deletion core-blocks/audio/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ export default class AudioEdit extends Component {
return false;
};
const setAudio = ( [ audio ] ) => onSelectAudio( audio );
const uploadFromFiles = ( event ) => editorMediaUpload( event.target.files, setAudio, 'audio' );
const uploadFromFiles = ( event ) => editorMediaUpload( {
filesList: event.target.files,
onFileChange: setAudio,
allowedType: 'audio',
} );

if ( editing ) {
return (
Expand Down
31 changes: 23 additions & 8 deletions core-blocks/gallery/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ import {
SelectControl,
ToggleControl,
Toolbar,
withNotices,
} from '@wordpress/components';
import { editorMediaUpload } from '@wordpress/blocks';
import {
BlockControls,
BlockAlignmentToolbar,
BlockNotices,
MediaUpload,
ImagePlaceholder,
InspectorControls,
Expand All @@ -44,7 +46,7 @@ export function defaultColumnsNumber( attributes ) {
return Math.min( 3, attributes.images.length );
}

export default class GalleryEdit extends Component {
class GalleryEdit extends Component {
constructor() {
super( ...arguments );

Expand Down Expand Up @@ -135,16 +137,17 @@ export default class GalleryEdit extends Component {

addFiles( files ) {
const currentImages = this.props.attributes.images || [];
const { setAttributes } = this.props;
editorMediaUpload(
files,
( images ) => {
const { notices, setAttributes } = this.props;
editorMediaUpload( {
allowedType: 'image',
filesList: files,
onFileChange: ( images ) => {
setAttributes( {
images: currentImages.concat( images ),
} );
},
'image',
);
onError: notices.createErrorNotice,
} );
}

componentWillReceiveProps( nextProps ) {
Expand All @@ -158,7 +161,7 @@ export default class GalleryEdit extends Component {
}

render() {
const { attributes, isSelected, className } = this.props;
const { attributes, isSelected, className, notices } = this.props;
const { images, columns = defaultColumnsNumber( attributes ), align, imageCrop, linkTo } = attributes;

const dropZone = (
Expand Down Expand Up @@ -195,6 +198,13 @@ export default class GalleryEdit extends Component {
</BlockControls>
);

const noticesUI = notices.noticeList.length > 0 &&
<BlockNotices
key="block-notices"
notices={ notices.noticeList }
onRemove={ notices.removeNotice }
/>;

if ( images.length === 0 ) {
return (
<Fragment>
Expand All @@ -205,6 +215,8 @@ export default class GalleryEdit extends Component {
label={ __( 'Gallery' ) }
onSelectImage={ this.onSelectImages }
multiple
notices={ noticesUI }
onError={ notices.createErrorNotice }
/>
</Fragment>
);
Expand Down Expand Up @@ -236,6 +248,7 @@ export default class GalleryEdit extends Component {
/>
</PanelBody>
</InspectorControls>
{ noticesUI }
<ul className={ `${ className } align${ align } columns-${ columns } ${ imageCrop ? 'is-cropped' : '' }` }>
{ dropZone }
{ images.map( ( img, index ) => (
Expand Down Expand Up @@ -271,3 +284,5 @@ export default class GalleryEdit extends Component {
);
}
}

export default withNotices( GalleryEdit );
10 changes: 5 additions & 5 deletions core-blocks/gallery/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,11 @@ export const settings = {
},
transform( files, onChange ) {
const block = createBlock( 'core/gallery' );
editorMediaUpload(
files,
( images ) => onChange( block.uid, { images } ),
'image'
);
editorMediaUpload( {
filesList: files,
onFileChange: ( images ) => onChange( block.uid, { images } ),
allowedType: 'image',
} );
return block;
},
},
Expand Down
Loading

0 comments on commit 9ba0213

Please sign in to comment.