mirror of
https://github.com/vale981/Vulcan
synced 2025-03-06 01:51:40 -05:00
Improve upload error handling; add clearFieldErrors;
This commit is contained in:
parent
e37704a94c
commit
bef525eea8
5 changed files with 102 additions and 28 deletions
|
@ -1,11 +1,15 @@
|
|||
/*
|
||||
|
||||
This component supports either uploading and storing a single image, or
|
||||
an array of images.
|
||||
This component supports uploading and storing an array of images.
|
||||
|
||||
Note also that an image can be stored as a simple string, or as an array of formats
|
||||
(each format being itself an object).
|
||||
|
||||
### Deleting Images
|
||||
|
||||
When clearing an image, it is addeds to `deletedValues` and set to `null` in the array,
|
||||
but the array item itself is not deleted. The entire array is then cleaned when submitting the form.
|
||||
|
||||
*/
|
||||
import { Components, getSetting, registerSetting, registerComponent } from 'meteor/vulcan:lib';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
@ -13,6 +17,7 @@ import PropTypes from 'prop-types';
|
|||
import Dropzone from 'react-dropzone';
|
||||
import 'cross-fetch/polyfill'; // patch for browser which don't have fetch implemented
|
||||
import { FormattedMessage } from 'meteor/vulcan:i18n';
|
||||
import set from 'lodash/set';
|
||||
|
||||
registerSetting('cloudinary.cloudName', null, 'Cloudinary cloud name (for image uploads)');
|
||||
|
||||
|
@ -47,7 +52,7 @@ class Image extends PureComponent {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<div className={`upload-image ${this.props.loading ? 'upload-image-loading' : ''}`}>
|
||||
<div className={`upload-image ${this.props.loading ? 'upload-image-loading' : ''} ${this.props.error ? 'upload-image-error' : ''}`}>
|
||||
<div className="upload-image-contents">
|
||||
<img style={{ width: 150 }} src={getImageUrl(this.props.image)} />
|
||||
{this.props.loading && (
|
||||
|
@ -71,21 +76,38 @@ Cloudinary Image Upload component
|
|||
*/
|
||||
class Upload extends PureComponent {
|
||||
|
||||
state = { uploading: false }
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
count = this.props.value.length;
|
||||
// add callback to clean any preview or error values
|
||||
context.addToSubmitForm(data => {
|
||||
// keep only "real" images
|
||||
const images = this.getImages({ includePreviews: false, includeDeleted: false});
|
||||
// replace images in `data` object with real images
|
||||
set(data, this.props.path, images);
|
||||
return data;
|
||||
});
|
||||
|
||||
}
|
||||
state = { uploading: false };
|
||||
|
||||
/*
|
||||
|
||||
Check the field's type to decide if the component should handle
|
||||
multiple image uploads or not.
|
||||
|
||||
For multiple images, the component expects an array of images;
|
||||
for single images it expects a single image object.
|
||||
multiple image uploads or not. Default to yes.
|
||||
|
||||
*/
|
||||
enableMultiple = () => {
|
||||
return this.props.datatype && this.props.datatype[0].type === Array;
|
||||
return this.props.maxCount !== 1;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
Whether to disable the dropzone.
|
||||
|
||||
*/
|
||||
isDisabled = () => {
|
||||
return this.state.uploading || this.props.maxCount <= this.getImages({ includeDeleted: false }).length;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -95,6 +117,9 @@ class Upload extends PureComponent {
|
|||
*/
|
||||
onDrop = files => {
|
||||
const promises = [];
|
||||
const imagesCount = this.getImages().length;
|
||||
|
||||
this.props.clearFieldErrors(this.props.path);
|
||||
|
||||
// set the component in upload mode
|
||||
this.setState({
|
||||
|
@ -106,10 +131,9 @@ class Upload extends PureComponent {
|
|||
|
||||
// trigger a request for each file
|
||||
files.forEach((file, index) => {
|
||||
|
||||
// figure out update path for current image
|
||||
const updateIndex = this.count + index;
|
||||
const updatePath = this.enableMultiple() ? `${this.props.path}.${updateIndex}` : this.props.path;
|
||||
const updateIndex = imagesCount + index;
|
||||
const updatePath = `${this.props.path}.${updateIndex}`;
|
||||
|
||||
// build preview object
|
||||
const previewObject = { secure_url: file.preview, loading: true, preview: true };
|
||||
|
@ -134,6 +158,8 @@ class Upload extends PureComponent {
|
|||
// eslint-disable-next-line no-console
|
||||
console.log(body.error);
|
||||
this.props.throwError({ id: 'upload.error', path: this.props.path, message: body.error.message });
|
||||
const errorObject = { ...previewObject, loading: false, error: true };
|
||||
this.props.updateCurrentValues({ [updatePath]: errorObject });
|
||||
return null;
|
||||
} else {
|
||||
// use the https:// url given by cloudinary; or eager property if using transformations
|
||||
|
@ -165,28 +191,33 @@ class Upload extends PureComponent {
|
|||
|
||||
/*
|
||||
|
||||
Remove the image at `index` (or just remove image if no index is passed)
|
||||
Remove the image at `index`
|
||||
|
||||
*/
|
||||
clearImage = index => {
|
||||
if (this.enableMultiple()) {
|
||||
this.props.addToDeletedValues(`${this.props.path}.${index}`);
|
||||
} else {
|
||||
this.props.addToDeletedValues(this.props.path);
|
||||
}
|
||||
this.props.updateCurrentValues({ [`${this.props.path}.${index}`]: null });
|
||||
};
|
||||
|
||||
getImages = () => {
|
||||
// show the actual uploaded image(s)
|
||||
return this.enableMultiple() ? this.props.value : [this.props.value];
|
||||
};
|
||||
/*
|
||||
|
||||
Get images, with or without previews/deleted images
|
||||
|
||||
*/
|
||||
getImages = (args = {}) => {
|
||||
const { includePreviews = true, includeDeleted = false } = args;
|
||||
let images = this.props.value;
|
||||
// remove previews if needed
|
||||
images = includePreviews ? images : images.filter(image => !image.preview);
|
||||
// remove deleted images
|
||||
images = includeDeleted ? images : images.filter((image, index) => !this.isDeleted(index));
|
||||
return images;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { uploading } = this.state;
|
||||
const images = this.getImages();
|
||||
const images = this.getImages({ includeDeleted: true });
|
||||
return (
|
||||
<div className="form-group row">
|
||||
<div className={`form-group row ${this.isDisabled() ? 'upload-disabled' : ''}`}>
|
||||
<label className="control-label col-sm-3">{this.props.label}</label>
|
||||
<div className="col-sm-9">
|
||||
<div className="upload-field">
|
||||
|
@ -198,7 +229,7 @@ class Upload extends PureComponent {
|
|||
className="dropzone-base"
|
||||
activeClassName="dropzone-active"
|
||||
rejectClassName="dropzone-reject"
|
||||
disabled={this.state.uploading}
|
||||
disabled={this.isDisabled()}
|
||||
>
|
||||
<div>
|
||||
<FormattedMessage id="upload.prompt" />
|
||||
|
@ -217,8 +248,7 @@ class Upload extends PureComponent {
|
|||
<div className="upload-images">
|
||||
{images.map(
|
||||
(image, index) =>
|
||||
!this.isDeleted(index) &&
|
||||
image && (
|
||||
!this.isDeleted(index) && (
|
||||
<Image
|
||||
clearImage={this.clearImage}
|
||||
key={index}
|
||||
|
@ -226,6 +256,7 @@ class Upload extends PureComponent {
|
|||
image={image}
|
||||
loading={image.loading}
|
||||
preview={image.preview}
|
||||
error={image.error}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
@ -245,6 +276,10 @@ Upload.propTypes = {
|
|||
label: PropTypes.string,
|
||||
};
|
||||
|
||||
Upload.contextTypes = {
|
||||
addToSubmitForm: PropTypes.func,
|
||||
};
|
||||
|
||||
registerComponent('Upload', Upload);
|
||||
|
||||
export default Upload;
|
||||
|
|
|
@ -72,3 +72,28 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.upload-disabled{
|
||||
.dropzone-base{
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='40' height='40' viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23cccccc' fill-opacity='0.4' fill-rule='evenodd'%3E%3Cpath d='M0 40L40 0H20L0 20M40 40V20L20 40'/%3E%3C/g%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
|
||||
.upload-image-error{
|
||||
.upload-image-contents{
|
||||
position: relative;
|
||||
&:after{
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='40' height='40' viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23ff0000' fill-opacity='0.4' fill-rule='evenodd'%3E%3Cpath d='M0 40L40 0H20L0 20M40 40V20L20 40'/%3E%3C/g%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -372,6 +372,16 @@ class Form extends Component {
|
|||
}));
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
Clear errors for a field
|
||||
|
||||
*/
|
||||
clearFieldErrors = path => {
|
||||
const errors = this.state.errors.filter(error => error.path !== path);
|
||||
this.setState({ errors });
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------- //
|
||||
// ------------------------------- Context ----------------------------- //
|
||||
// --------------------------------------------------------------------- //
|
||||
|
@ -668,6 +678,7 @@ class Form extends Component {
|
|||
updateCurrentValues={this.updateCurrentValues}
|
||||
deletedValues={this.state.deletedValues}
|
||||
addToDeletedValues={this.addToDeletedValues}
|
||||
clearFieldErrors={this.clearFieldErrors}
|
||||
formType={this.getFormType()}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -139,6 +139,7 @@ class FormComponent extends PureComponent {
|
|||
currentValues,
|
||||
addToDeletedValues,
|
||||
deletedValues,
|
||||
clearFieldErrors,
|
||||
} = this.props;
|
||||
|
||||
const value = this.getValue();
|
||||
|
@ -165,6 +166,7 @@ class FormComponent extends PureComponent {
|
|||
updateCurrentValues,
|
||||
deletedValues,
|
||||
addToDeletedValues,
|
||||
clearFieldErrors,
|
||||
};
|
||||
|
||||
// if control is a React component, use it
|
||||
|
|
|
@ -52,6 +52,7 @@ class FormGroup extends PureComponent {
|
|||
updateCurrentValues={this.props.updateCurrentValues}
|
||||
deletedValues={this.props.deletedValues}
|
||||
addToDeletedValues={this.props.addToDeletedValues}
|
||||
clearFieldErrors={this.props.clearFieldErrors}
|
||||
formType={this.props.formType}
|
||||
/>
|
||||
))}
|
||||
|
|
Loading…
Add table
Reference in a new issue