mirror of
https://github.com/ascribe/onion.git
synced 2024-12-22 17:33:14 +01:00
Add FileDragAndDropError
This commit is contained in:
parent
04d7e6951a
commit
79780cfb3a
@ -22,6 +22,7 @@ let FurtherDetailsFileuploader = React.createClass({
|
|||||||
|
|
||||||
// Props for ReactS3FineUploader
|
// Props for ReactS3FineUploader
|
||||||
multiple: bool,
|
multiple: bool,
|
||||||
|
showErrorPrompt: bool,
|
||||||
submitFile: func, // TODO: rename to onSubmitFile
|
submitFile: func, // TODO: rename to onSubmitFile
|
||||||
|
|
||||||
setIsUploadReady: func, //TODO: rename to setIsUploaderValidated
|
setIsUploadReady: func, //TODO: rename to setIsUploaderValidated
|
||||||
@ -42,6 +43,7 @@ let FurtherDetailsFileuploader = React.createClass({
|
|||||||
otherData,
|
otherData,
|
||||||
pieceId,
|
pieceId,
|
||||||
setIsUploadReady,
|
setIsUploadReady,
|
||||||
|
showErrorPrompt,
|
||||||
submitFile } = this.props;
|
submitFile } = this.props;
|
||||||
|
|
||||||
// Essentially there a three cases important to the fileuploader
|
// Essentially there a three cases important to the fileuploader
|
||||||
@ -101,8 +103,9 @@ let FurtherDetailsFileuploader = React.createClass({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
areAssetsDownloadable={true}
|
areAssetsDownloadable={true}
|
||||||
areAssetsEditable={this.props.editable}
|
areAssetsEditable={editable}
|
||||||
multiple={this.props.multiple} />
|
multiple={multiple}
|
||||||
|
showErrorPrompt={showErrorPrompt} />
|
||||||
</Property>
|
</Property>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,8 @@ let RegisterPieceForm = React.createClass({
|
|||||||
disabled={!isFineUploaderEditable}
|
disabled={!isFineUploaderEditable}
|
||||||
enableLocalHashing={hashLocally}
|
enableLocalHashing={hashLocally}
|
||||||
uploadMethod={location.query.method}
|
uploadMethod={location.query.method}
|
||||||
handleChangedFile={this.handleChangedDigitalWork}/>
|
handleChangedFile={this.handleChangedDigitalWork}
|
||||||
|
showErrorPrompt />
|
||||||
</Property>
|
</Property>
|
||||||
<Property
|
<Property
|
||||||
name="thumbnail_file"
|
name="thumbnail_file"
|
||||||
@ -196,11 +197,10 @@ let RegisterPieceForm = React.createClass({
|
|||||||
allowedExtensions: ['png', 'jpg', 'jpeg', 'gif']
|
allowedExtensions: ['png', 'jpg', 'jpeg', 'gif']
|
||||||
}}
|
}}
|
||||||
setIsUploadReady={this.setIsUploadReady('thumbnailKeyReady')}
|
setIsUploadReady={this.setIsUploadReady('thumbnailKeyReady')}
|
||||||
location={location}
|
|
||||||
fileClassToUpload={{
|
fileClassToUpload={{
|
||||||
singular: getLangText('Select representative image'),
|
singular: getLangText('Select representative image'),
|
||||||
plural: getLangText('Select representative images')
|
plural: getLangText('Select representative images')
|
||||||
}}/>
|
}} />
|
||||||
</Property>
|
</Property>
|
||||||
<Property
|
<Property
|
||||||
name='artist_name'
|
name='artist_name'
|
||||||
|
@ -25,6 +25,7 @@ const InputFineUploader = React.createClass({
|
|||||||
|
|
||||||
// Props for ReactS3FineUploader
|
// Props for ReactS3FineUploader
|
||||||
areAssetsDownloadable: bool,
|
areAssetsDownloadable: bool,
|
||||||
|
showErrorPrompt: bool,
|
||||||
|
|
||||||
handleChangedFile: func, // TODO: rename to onChangedFile
|
handleChangedFile: func, // TODO: rename to onChangedFile
|
||||||
submitFile: func, // TODO: rename to onSubmitFile
|
submitFile: func, // TODO: rename to onSubmitFile
|
||||||
@ -121,6 +122,7 @@ const InputFineUploader = React.createClass({
|
|||||||
fileClassToUpload,
|
fileClassToUpload,
|
||||||
uploadMethod,
|
uploadMethod,
|
||||||
handleChangedFile,
|
handleChangedFile,
|
||||||
|
showErrorPrompt,
|
||||||
disabled } = this.props;
|
disabled } = this.props;
|
||||||
let editable = isFineUploaderActive;
|
let editable = isFineUploaderActive;
|
||||||
|
|
||||||
@ -142,6 +144,7 @@ const InputFineUploader = React.createClass({
|
|||||||
isReadyForFormSubmission={isReadyForFormSubmission}
|
isReadyForFormSubmission={isReadyForFormSubmission}
|
||||||
areAssetsDownloadable={areAssetsDownloadable}
|
areAssetsDownloadable={areAssetsDownloadable}
|
||||||
areAssetsEditable={editable}
|
areAssetsEditable={editable}
|
||||||
|
showErrorPrompt={showErrorPrompt}
|
||||||
signature={{
|
signature={{
|
||||||
endpoint: AppConstants.serverUrl + 's3/signature/',
|
endpoint: AppConstants.serverUrl + 's3/signature/',
|
||||||
customHeaders: {
|
customHeaders: {
|
||||||
|
@ -39,6 +39,12 @@ let FileDragAndDrop = React.createClass({
|
|||||||
// to -1 which is code for: aborted
|
// to -1 which is code for: aborted
|
||||||
handleCancelHashing: React.PropTypes.func,
|
handleCancelHashing: React.PropTypes.func,
|
||||||
|
|
||||||
|
showError: React.PropTypes.bool,
|
||||||
|
errorClass: React.PropTypes.shape({
|
||||||
|
name: React.PropTypes.string,
|
||||||
|
prettifiedText: React.PropTypes.string
|
||||||
|
}),
|
||||||
|
|
||||||
// A class of a file the user has to upload
|
// A class of a file the user has to upload
|
||||||
// Needs to be defined both in singular as well as in plural
|
// Needs to be defined both in singular as well as in plural
|
||||||
fileClassToUpload: React.PropTypes.shape({
|
fileClassToUpload: React.PropTypes.shape({
|
||||||
@ -144,6 +150,17 @@ let FileDragAndDrop = React.createClass({
|
|||||||
this.refs.fileSelector.getDOMNode().dispatchEvent(evt);
|
this.refs.fileSelector.getDOMNode().dispatchEvent(evt);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getErrorDialog(failedFiles) {
|
||||||
|
const { errorClass } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FileDragAndDropErrorDialog
|
||||||
|
errorClass={errorClass}
|
||||||
|
files={failedFiles}
|
||||||
|
handleRetryFiles={this.props.handleRetryFiles} />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
getPreviewIterator() {
|
getPreviewIterator() {
|
||||||
const { areAssetsDownloadable, areAssetsEditable, filesToUpload } = this.props;
|
const { areAssetsDownloadable, areAssetsEditable, filesToUpload } = this.props;
|
||||||
|
|
||||||
@ -179,6 +196,8 @@ let FileDragAndDrop = React.createClass({
|
|||||||
hashingProgress,
|
hashingProgress,
|
||||||
handleCancelHashing,
|
handleCancelHashing,
|
||||||
multiple,
|
multiple,
|
||||||
|
showError,
|
||||||
|
errorClass,
|
||||||
fileClassToUpload,
|
fileClassToUpload,
|
||||||
allowedExtensions } = this.props;
|
allowedExtensions } = this.props;
|
||||||
|
|
||||||
@ -216,6 +235,7 @@ let FileDragAndDrop = React.createClass({
|
|||||||
onDrag={this.handleDrop}
|
onDrag={this.handleDrop}
|
||||||
onDragOver={this.handleDragOver}
|
onDragOver={this.handleDragOver}
|
||||||
onDrop={this.handleDrop}>
|
onDrop={this.handleDrop}>
|
||||||
|
{hasError ? this.getErrorDialog(failedFiles) : this.getPreviewIterator()}
|
||||||
{!hasFiles && !hasError ? this.getUploadDialog() : null}
|
{!hasFiles && !hasError ? this.getUploadDialog() : null}
|
||||||
{/*
|
{/*
|
||||||
Opera doesn't trigger simulated click events
|
Opera doesn't trigger simulated click events
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { ErrorClasses } from '../../../constants/error_constants';
|
||||||
|
|
||||||
|
import { getLangText } from '../../../utils/lang_utils';
|
||||||
|
|
||||||
|
let FileDragAndDropErrorDialog = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
errorClass: React.PropTypes.shape({
|
||||||
|
name: React.PropTypes.string,
|
||||||
|
prettifiedText: React.PropTypes.string
|
||||||
|
}).isRequired,
|
||||||
|
files: React.PropTypes.array.isRequired,
|
||||||
|
handleRetryFiles: React.PropTypes.func.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
getRetryButton(text, openIntercom) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className='btn btn-default'
|
||||||
|
onClick={() => {
|
||||||
|
if (openIntercom) {
|
||||||
|
window.Intercom('showNewMessage', getLangText("I'm having trouble with uploading my file: "));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.retryAllFiles()
|
||||||
|
}}>
|
||||||
|
{getLangText(text)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
getContactUsDetail() {
|
||||||
|
return (
|
||||||
|
<div className='file-drag-and-drop-error'>
|
||||||
|
<h4>Let us help you</h4>
|
||||||
|
<p>{getLangText('Still having problems? Give us a call!')}</p>
|
||||||
|
{this.getRetryButton('Contact us', true)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
getErrorDetail(multipleFiles) {
|
||||||
|
const { errorClass: { prettifiedText }, files } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='file-drag-and-drop-error'>
|
||||||
|
<div className={classNames('file-drag-and-drop-error-detail', { 'file-drag-and-drop-error-detail-multiple-files': multipleFiles })}>
|
||||||
|
<h4>{getLangText(multipleFiles ? 'Some files did not upload correctly'
|
||||||
|
: 'Error uploading the file!')}
|
||||||
|
</h4>
|
||||||
|
<p>{prettifiedText}</p>
|
||||||
|
{this.getRetryButton('Retry')}
|
||||||
|
</div>
|
||||||
|
<span className={classNames('file-drag-and-drop-error-icon-container', { 'file-drag-and-drop-error-icon-container-multiple-files': multipleFiles })}>
|
||||||
|
<span className='file-drag-and-drop-error-icon'></span>
|
||||||
|
</span>
|
||||||
|
<div className='file-drag-and-drop-error-file-names'>
|
||||||
|
<ul>
|
||||||
|
{files.map((file) => (<li key={file.id} className='file-name'>{file.originalName}</li>))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
retryAllFiles() {
|
||||||
|
const { files, handleRetryFiles } = this.props;
|
||||||
|
handleRetryFiles(files.map(file => file.id));
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { errorClass: { name: errorName }, files } = this.props;
|
||||||
|
|
||||||
|
const multipleFiles = files.length > 1;
|
||||||
|
const contactUs = errorName === ErrorClasses.upload.contactUs.name;
|
||||||
|
|
||||||
|
return contactUs ? this.getContactUsDetail() : this.getErrorDetail(multipleFiles);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default FileDragAndDropErrorDialog;
|
@ -35,6 +35,8 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
propTypes: {
|
propTypes: {
|
||||||
areAssetsDownloadable: bool,
|
areAssetsDownloadable: bool,
|
||||||
areAssetsEditable: bool,
|
areAssetsEditable: bool,
|
||||||
|
errorNotificationMessage: string,
|
||||||
|
showErrorPrompt: bool,
|
||||||
|
|
||||||
handleChangedFile: func, // for when a file is dropped or selected, TODO: rename to onChangedFile
|
handleChangedFile: func, // for when a file is dropped or selected, TODO: rename to onChangedFile
|
||||||
submitFile: func, // for when a file has been successfully uploaded, TODO: rename to onSubmitFile
|
submitFile: func, // for when a file has been successfully uploaded, TODO: rename to onSubmitFile
|
||||||
@ -152,8 +154,18 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
|
|
||||||
getDefaultProps() {
|
getDefaultProps() {
|
||||||
return {
|
return {
|
||||||
|
errorNotificationMessage: getLangText('Oops, we had a problem uploading your file. Please contact us if this happens repeatedly.'),
|
||||||
|
showErrorPrompt: false,
|
||||||
|
fileClassToUpload: {
|
||||||
|
singular: getLangText('file'),
|
||||||
|
plural: getLangText('files')
|
||||||
|
},
|
||||||
|
fileInputElement: FileDragAndDrop,
|
||||||
|
|
||||||
|
// FineUploader options
|
||||||
autoUpload: true,
|
autoUpload: true,
|
||||||
debug: false,
|
debug: false,
|
||||||
|
multiple: false,
|
||||||
objectProperties: {
|
objectProperties: {
|
||||||
acl: 'public-read',
|
acl: 'public-read',
|
||||||
bucket: 'ascribe0'
|
bucket: 'ascribe0'
|
||||||
@ -195,22 +207,20 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
name = name.slice(0, 15) + '...' + name.slice(-15);
|
name = name.slice(0, 15) + '...' + name.slice(-15);
|
||||||
}
|
}
|
||||||
return name;
|
return name;
|
||||||
},
|
}
|
||||||
multiple: false,
|
|
||||||
defaultErrorMessage: getLangText('Unexpected error. Please contact us if this happens repeatedly.'),
|
|
||||||
fileClassToUpload: {
|
|
||||||
singular: getLangText('file'),
|
|
||||||
plural: getLangText('files')
|
|
||||||
},
|
|
||||||
fileInputElement: FileDragAndDrop
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return {
|
return {
|
||||||
filesToUpload: [],
|
filesToUpload: [],
|
||||||
uploader: new fineUploader.s3.FineUploaderBasic(this.propsToConfig()),
|
uploader: this.createNewFineUploader(),
|
||||||
csrfToken: getCookie(AppConstants.csrftoken),
|
csrfToken: getCookie(AppConstants.csrftoken),
|
||||||
|
errorState: {
|
||||||
|
manualRetryAttempt: 0,
|
||||||
|
errorClass: undefined
|
||||||
|
},
|
||||||
|
uploadInProgress: false,
|
||||||
|
|
||||||
// -1: aborted
|
// -1: aborted
|
||||||
// -2: uninitialized
|
// -2: uninitialized
|
||||||
@ -228,7 +238,7 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
let potentiallyNewCSRFToken = getCookie(AppConstants.csrftoken);
|
let potentiallyNewCSRFToken = getCookie(AppConstants.csrftoken);
|
||||||
if(this.state.csrfToken !== potentiallyNewCSRFToken) {
|
if(this.state.csrfToken !== potentiallyNewCSRFToken) {
|
||||||
this.setState({
|
this.setState({
|
||||||
uploader: new fineUploader.s3.FineUploaderBasic(this.propsToConfig()),
|
uploader: this.createNewFineUploader(),
|
||||||
csrfToken: potentiallyNewCSRFToken
|
csrfToken: potentiallyNewCSRFToken
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -241,8 +251,12 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
this.state.uploader.cancelAll();
|
this.state.uploader.cancelAll();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createNewFineUploader() {
|
||||||
|
return new fineUploader.s3.FineUploaderBasic(this.propsToConfig());
|
||||||
|
},
|
||||||
|
|
||||||
propsToConfig() {
|
propsToConfig() {
|
||||||
let objectProperties = this.props.objectProperties;
|
const objectProperties = Object.assign({}, this.props.objectProperties);
|
||||||
objectProperties.key = this.requestKey;
|
objectProperties.key = this.requestKey;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -263,6 +277,7 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
multiple: this.props.multiple,
|
multiple: this.props.multiple,
|
||||||
retry: this.props.retry,
|
retry: this.props.retry,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
|
onAllComplete: this.onAllComplete,
|
||||||
onComplete: this.onComplete,
|
onComplete: this.onComplete,
|
||||||
onCancel: this.onCancel,
|
onCancel: this.onCancel,
|
||||||
onProgress: this.onProgress,
|
onProgress: this.onProgress,
|
||||||
@ -408,6 +423,65 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
checkFormSubmissionReady() {
|
||||||
|
const { isReadyForFormSubmission, setIsUploadReady } = this.props;
|
||||||
|
|
||||||
|
// since the form validation props isReadyForFormSubmission and setIsUploadReady
|
||||||
|
// are optional, we'll only trigger them when they're actually defined
|
||||||
|
if (typeof isReadyForFormSubmission === 'function' && typeof setIsUploadReady === 'function') {
|
||||||
|
// set uploadReady to true if the uploader's ready for submission
|
||||||
|
setIsUploadReady(isReadyForFormSubmission(this.state.filesToUpload));
|
||||||
|
} else {
|
||||||
|
console.warn('You didn\'t define the functions isReadyForFormSubmission and/or setIsUploadReady in as a prop in react-s3-fine-uploader');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isFileValid(file) {
|
||||||
|
const { validation } = this.props;
|
||||||
|
|
||||||
|
if (validation && file.size > validation.sizeLimit) {
|
||||||
|
const fileSizeInMegaBytes = validation.sizeLimit / 1000000;
|
||||||
|
|
||||||
|
const notification = new GlobalNotificationModel(getLangText('A file you submitted is bigger than ' + fileSizeInMegaBytes + 'MB.'), 'danger', 5000);
|
||||||
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getUploadErrorClass({ type = 'upload', reason, xhr }) {
|
||||||
|
const { manualRetryAttempt } = this.state.errorState;
|
||||||
|
let matchedErrorClass;
|
||||||
|
|
||||||
|
// Use the contact us error class if they've retried a number of times
|
||||||
|
// and are still unsuccessful
|
||||||
|
if (manualRetryAttempt === RETRY_ATTEMPT_TO_SHOW_CONTACT_US) {
|
||||||
|
matchedErrorClass = ErrorClasses.upload.contactUs;
|
||||||
|
} else {
|
||||||
|
matchedErrorClass = testErrorAgainstAll({ type, reason, xhr });
|
||||||
|
|
||||||
|
// If none found, show the next error message
|
||||||
|
if (!matchedErrorClass) {
|
||||||
|
matchedErrorClass = ErrorQueueStore.getNextError('upload');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchedErrorClass;
|
||||||
|
},
|
||||||
|
|
||||||
|
getXhrErrorComment(xhr) {
|
||||||
|
if (xhr) {
|
||||||
|
return {
|
||||||
|
response: xhr.response,
|
||||||
|
url: xhr.responseURL,
|
||||||
|
status: xhr.status,
|
||||||
|
statusText: xhr.statusText
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/* FineUploader specific callback function handlers */
|
/* FineUploader specific callback function handlers */
|
||||||
|
|
||||||
onUploadChunk(id, name, chunkData) {
|
onUploadChunk(id, name, chunkData) {
|
||||||
@ -438,7 +512,14 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
|
|
||||||
this.setState({ startedChunks });
|
this.setState({ startedChunks });
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onAllComplete(succeed, failed) {
|
||||||
|
if (this.state.uploadInProgress) {
|
||||||
|
this.setState({
|
||||||
|
uploadInProgress: false
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onComplete(id, name, res, xhr) {
|
onComplete(id, name, res, xhr) {
|
||||||
@ -464,29 +545,14 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
// Only after the blob has been created server-side, we can make the form submittable.
|
// Only after the blob has been created server-side, we can make the form submittable.
|
||||||
this.createBlob(files[id])
|
this.createBlob(files[id])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitFile
|
if (typeof this.props.submitFile === 'function') {
|
||||||
// are optional, we'll only trigger them when they're actually defined
|
|
||||||
if(this.props.submitFile) {
|
|
||||||
this.props.submitFile(files[id]);
|
this.props.submitFile(files[id]);
|
||||||
} else {
|
} else {
|
||||||
console.warn('You didn\'t define submitFile as a prop in react-s3-fine-uploader');
|
console.warn('You didn\'t define submitFile as a prop in react-s3-fine-uploader');
|
||||||
}
|
}
|
||||||
|
|
||||||
// for explanation, check comment of if statement above
|
this.checkFormSubmissionReady();
|
||||||
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
|
});
|
||||||
// also, lets check if after the completion of this upload,
|
|
||||||
// the form is ready for submission or not
|
|
||||||
if(this.props.isReadyForFormSubmission(this.state.filesToUpload)) {
|
|
||||||
// if so, set uploadstatus to true
|
|
||||||
this.props.setIsUploadReady(true);
|
|
||||||
} else {
|
|
||||||
this.props.setIsUploadReady(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn('You didn\'t define the functions isReadyForFormSubmission and/or setIsUploadReady in as a prop in react-s3-fine-uploader');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(this.onErrorPromiseProxy);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -502,42 +568,43 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onError(id, name, errorReason, xhr) {
|
onError(id, name, errorReason, xhr) {
|
||||||
|
const { errorNotificationMessage, showErrorPrompt } = this.props;
|
||||||
|
const { chunks, filesToUpload } = this.state;
|
||||||
|
|
||||||
console.logGlobal(errorReason, false, {
|
console.logGlobal(errorReason, false, {
|
||||||
files: this.state.filesToUpload,
|
files: filesToUpload,
|
||||||
chunks: this.state.chunks,
|
chunks: chunks,
|
||||||
xhr: this.getXhrErrorComment(xhr)
|
xhr: this.getXhrErrorComment(xhr)
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.setIsUploadReady(true);
|
let notificationMessage;
|
||||||
this.cancelUploads();
|
|
||||||
|
|
||||||
let notification = new GlobalNotificationModel(errorReason || this.props.defaultErrorMessage, 'danger', 5000);
|
if (showErrorPrompt) {
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
notificationMessage = errorNotificationMessage;
|
||||||
},
|
|
||||||
|
|
||||||
getXhrErrorComment(xhr) {
|
this.setStatusOfFile(id, FileStatus.UPLOAD_FAILED);
|
||||||
if (xhr) {
|
|
||||||
return {
|
|
||||||
response: xhr.response,
|
|
||||||
url: xhr.responseURL,
|
|
||||||
status: xhr.status,
|
|
||||||
statusText: xhr.statusText
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
isFileValid(file) {
|
// If we've already found an error on this upload, just ignore other errors
|
||||||
if(file.size > this.props.validation.sizeLimit) {
|
// that pop up. They'll likely pop up again when the user retries.
|
||||||
|
if (!this.state.errorState.errorClass) {
|
||||||
|
const errorState = React.addons.update(this.state.errorState, {
|
||||||
|
errorClass: {
|
||||||
|
$set: this.getUploadErrorClass({
|
||||||
|
reason: errorReason,
|
||||||
|
xhr
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let fileSizeInMegaBytes = this.props.validation.sizeLimit / 1000000;
|
this.setState({ errorState });
|
||||||
|
}
|
||||||
let notification = new GlobalNotificationModel(getLangText('A file you submitted is bigger than ' + fileSizeInMegaBytes + 'MB.'), 'danger', 5000);
|
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
} else {
|
} else {
|
||||||
return true;
|
notificationMessage = errorReason || errorNotificationMessage;
|
||||||
|
this.cancelUploads();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const notification = new GlobalNotificationModel(notificationMessage, 'danger', 5000);
|
||||||
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
},
|
},
|
||||||
|
|
||||||
onCancel(id) {
|
onCancel(id) {
|
||||||
@ -552,17 +619,18 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
let notification = new GlobalNotificationModel(getLangText('File upload canceled'), 'success', 5000);
|
let notification = new GlobalNotificationModel(getLangText('File upload canceled'), 'success', 5000);
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
|
|
||||||
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitFile
|
this.checkFormSubmissionReady();
|
||||||
// are optional, we'll only trigger them when they're actually defined
|
|
||||||
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
|
// FineUploader's onAllComplete event doesn't fire if all files are cancelled
|
||||||
if(this.props.isReadyForFormSubmission(this.state.filesToUpload)) {
|
// so we need to double check if this is the last file getting cancelled.
|
||||||
// if so, set uploadstatus to true
|
//
|
||||||
this.props.setIsUploadReady(true);
|
// Because we're calling FineUploader.getInProgress() in a cancel callback,
|
||||||
} else {
|
// the current file getting cancelled is still considered to be in progress
|
||||||
this.props.setIsUploadReady(false);
|
// so there will be one file left in progress when we're cancelling the last file.
|
||||||
}
|
if (this.state.uploader.getInProgress() === 1) {
|
||||||
} else {
|
this.setState({
|
||||||
console.warn('You didn\'t define the functions isReadyForFormSubmission and/or setIsUploadReady in as a prop in react-s3-fine-uploader');
|
uploadInProgress: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -619,20 +687,7 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitFile
|
this.checkFormSubmissionReady();
|
||||||
// are optional, we'll only trigger them when they're actually defined
|
|
||||||
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
|
|
||||||
// also, lets check if after the completion of this upload,
|
|
||||||
// the form is ready for submission or not
|
|
||||||
if(this.props.isReadyForFormSubmission(this.state.filesToUpload)) {
|
|
||||||
// if so, set uploadstatus to true
|
|
||||||
this.props.setIsUploadReady(true);
|
|
||||||
} else {
|
|
||||||
this.props.setIsUploadReady(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn('You didn\'t define the functions isReadyForFormSubmission and/or setIsUploadReady in as a prop in react-s3-fine-uploader');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleDeleteFile(fileId) {
|
handleDeleteFile(fileId) {
|
||||||
@ -692,6 +747,27 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleRetryFiles(fileIds) {
|
||||||
|
let filesToUpload = this.state.filesToUpload;
|
||||||
|
|
||||||
|
if (fileIds.constructor !== Array) {
|
||||||
|
fileIds = [ fileIds ];
|
||||||
|
}
|
||||||
|
|
||||||
|
fileIds.forEach((fileId) => {
|
||||||
|
this.state.uploader.retry(fileId);
|
||||||
|
filesToUpload = React.addons.update(filesToUpload, { [fileId]: { status: { $set: FileStatus.UPLOADING } } });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
// Reset the error class along with the retry
|
||||||
|
errorState: {
|
||||||
|
manualRetryAttempt: this.state.errorState.manualRetryAttempt + 1
|
||||||
|
},
|
||||||
|
filesToUpload
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
handleUploadFile(files) {
|
handleUploadFile(files) {
|
||||||
// While files are being uploaded, the form cannot be ready
|
// While files are being uploaded, the form cannot be ready
|
||||||
// for submission
|
// for submission
|
||||||
@ -819,6 +895,9 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
if(files.length > 0) {
|
if(files.length > 0) {
|
||||||
this.state.uploader.addFiles(files);
|
this.state.uploader.addFiles(files);
|
||||||
this.synchronizeFileLists(files);
|
this.synchronizeFileLists(files);
|
||||||
|
this.setState({
|
||||||
|
uploadInProgress: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -920,7 +999,7 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
isDropzoneInactive() {
|
isDropzoneInactive() {
|
||||||
const { areAssetsEditable, enableLocalHashing, multiple, showErrorStates, uploadMethod } = this.props;
|
const { areAssetsEditable, enableLocalHashing, multiple, showErrorPrompt, uploadMethod } = this.props;
|
||||||
const { errorState, filesToUpload } = this.state;
|
const { errorState, filesToUpload } = this.state;
|
||||||
|
|
||||||
const filesToDisplay = filesToUpload.filter((file) => {
|
const filesToDisplay = filesToUpload.filter((file) => {
|
||||||
@ -931,7 +1010,7 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if ((enableLocalHashing && !uploadMethod) || !areAssetsEditable ||
|
if ((enableLocalHashing && !uploadMethod) || !areAssetsEditable ||
|
||||||
(showErrorStates && errorState.errorClass) ||
|
(showErrorPrompt && errorState.errorClass) ||
|
||||||
(!multiple && filesToDisplay.length > 0)) {
|
(!multiple && filesToDisplay.length > 0)) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -940,7 +1019,7 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
getAllowedExtensions() {
|
getAllowedExtensions() {
|
||||||
let { validation } = this.props;
|
const { validation } = this.props;
|
||||||
|
|
||||||
if(validation && validation.allowedExtensions && validation.allowedExtensions.length > 0) {
|
if(validation && validation.allowedExtensions && validation.allowedExtensions.length > 0) {
|
||||||
return transformAllowedExtensionsToInputAcceptProp(validation.allowedExtensions);
|
return transformAllowedExtensionsToInputAcceptProp(validation.allowedExtensions);
|
||||||
@ -950,6 +1029,7 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { errorState: { errorClass }, filesToUpload, uploadInProgress } = this.state;
|
||||||
const {
|
const {
|
||||||
multiple,
|
multiple,
|
||||||
areAssetsDownloadable,
|
areAssetsDownloadable,
|
||||||
@ -973,11 +1053,15 @@ const ReactS3FineUploader = React.createClass({
|
|||||||
uploadMethod,
|
uploadMethod,
|
||||||
fileClassToUpload,
|
fileClassToUpload,
|
||||||
filesToUpload,
|
filesToUpload,
|
||||||
|
uploadInProgress,
|
||||||
|
errorClass,
|
||||||
|
showError,
|
||||||
onDrop: this.handleUploadFile,
|
onDrop: this.handleUploadFile,
|
||||||
handleDeleteFile: this.handleDeleteFile,
|
handleDeleteFile: this.handleDeleteFile,
|
||||||
handleCancelFile: this.handleCancelFile,
|
handleCancelFile: this.handleCancelFile,
|
||||||
handlePauseFile: this.handlePauseFile,
|
handlePauseFile: this.handlePauseFile,
|
||||||
handleResumeFile: this.handleResumeFile,
|
handleResumeFile: this.handleResumeFile,
|
||||||
|
handleRetryFiles: this.handleRetryFiles,
|
||||||
handleCancelHashing: this.handleCancelHashing,
|
handleCancelHashing: this.handleCancelHashing,
|
||||||
dropzoneInactive: this.isDropzoneInactive(),
|
dropzoneInactive: this.isDropzoneInactive(),
|
||||||
hashingProgress: this.state.hashingProgress,
|
hashingProgress: this.state.hashingProgress,
|
||||||
|
@ -135,7 +135,7 @@ Object.keys(ErrorClasses).forEach((errorGroupKey) => {
|
|||||||
const errorGroup = ErrorClasses[errorGroupKey];
|
const errorGroup = ErrorClasses[errorGroupKey];
|
||||||
Object.keys(errorGroup).forEach((errorClassKey) => {
|
Object.keys(errorGroup).forEach((errorClassKey) => {
|
||||||
const errorClass = errorGroup[errorClassKey];
|
const errorClass = errorGroup[errorClassKey];
|
||||||
errorClass.name = errorClassKey;
|
errorClass.name = errorGroupKey + '-' + errorClassKey;
|
||||||
errorClass.group = errorGroupKey;
|
errorClass.group = errorGroupKey;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -145,7 +145,7 @@ export function escapeHTML(s) {
|
|||||||
* Returns a copy of the given object's own and inherited enumerable
|
* Returns a copy of the given object's own and inherited enumerable
|
||||||
* properties, omitting any keys that pass the given filter function.
|
* properties, omitting any keys that pass the given filter function.
|
||||||
*/
|
*/
|
||||||
function filterObjOnFn(obj, filterFn) {
|
function applyFilterOnObject(obj, filterFn) {
|
||||||
const filteredObj = {};
|
const filteredObj = {};
|
||||||
|
|
||||||
for (let key in obj) {
|
for (let key in obj) {
|
||||||
@ -158,6 +158,37 @@ function filterObjOnFn(obj, filterFn) {
|
|||||||
return filteredObj;
|
return filteredObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction for selectFromObject and omitFromObject
|
||||||
|
* for DRYness
|
||||||
|
* @param {boolean} isInclusion True if the filter should be for including the filtered items
|
||||||
|
* (ie. selecting only them vs omitting only them)
|
||||||
|
*/
|
||||||
|
function filterFromObject(obj, filter, { isInclusion = true } = {}) {
|
||||||
|
if (filter && filter.constructor === Array) {
|
||||||
|
return applyFilterOnObject(obj, isInclusion ? ((_, key) => filter.indexOf(key) < 0)
|
||||||
|
: ((_, key) => filter.indexOf(key) >= 0));
|
||||||
|
} else if (filter && typeof filter === 'function') {
|
||||||
|
// Flip the filter fn's return if it's for inclusion
|
||||||
|
return applyFilterOnObject(obj, isInclusion ? (...args) => !filter(...args)
|
||||||
|
: filter);
|
||||||
|
} else {
|
||||||
|
throw new Error('The given filter is not an array or function. Exclude aborted');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to lodash's _.pick(), this returns a copy of the given object's
|
||||||
|
* own and inherited enumerable properties, selecting only the keys in
|
||||||
|
* the given array or whose value pass the given filter function.
|
||||||
|
* @param {object} obj Source object
|
||||||
|
* @param {array|function} filter Array of key names to select or function to invoke per iteration
|
||||||
|
* @return {object} The new object
|
||||||
|
*/
|
||||||
|
export function selectFromObject(obj, filter) {
|
||||||
|
return filterFromObject(obj, filter);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Similar to lodash's _.omit(), this returns a copy of the given object's
|
* Similar to lodash's _.omit(), this returns a copy of the given object's
|
||||||
* own and inherited enumerable properties, omitting any keys that are
|
* own and inherited enumerable properties, omitting any keys that are
|
||||||
@ -167,15 +198,7 @@ function filterObjOnFn(obj, filterFn) {
|
|||||||
* @return {object} The new object
|
* @return {object} The new object
|
||||||
*/
|
*/
|
||||||
export function omitFromObject(obj, filter) {
|
export function omitFromObject(obj, filter) {
|
||||||
if (filter && filter.constructor === Array) {
|
return filterFromObject(obj, filter, { isInclusion: false });
|
||||||
return filterObjOnFn(obj, (_, key) => {
|
|
||||||
return filter.indexOf(key) >= 0;
|
|
||||||
});
|
|
||||||
} else if (filter && typeof filter === 'function') {
|
|
||||||
return filterObjOnFn(obj, filter);
|
|
||||||
} else {
|
|
||||||
throw new Error('The given filter is not an array or function. Exclude aborted');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -169,6 +169,158 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-drag-and-drop-error {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: $ascribe-pink;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding-left: 45px;
|
||||||
|
padding-right: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make button larger on mobile */
|
||||||
|
@media screen and (max-width: 625px) {
|
||||||
|
.btn {
|
||||||
|
padding: 10px 100px 10px 100px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-drag-and-drop-error-detail {
|
||||||
|
float: left;
|
||||||
|
padding-right: 25px;
|
||||||
|
text-align: left;
|
||||||
|
width: 40%;
|
||||||
|
|
||||||
|
&.file-drag-and-drop-error-detail-multiple-files {
|
||||||
|
width: 45%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Have detail fill up entire width on mobile */
|
||||||
|
@media screen and (max-width: 625px) {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&.file-drag-and-drop-error-detail-multiple-files {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-drag-and-drop-error-file-names {
|
||||||
|
float: left;
|
||||||
|
height: 104px;
|
||||||
|
max-width: 35%;
|
||||||
|
padding-left: 25px;
|
||||||
|
position: relative;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
position: relative;
|
||||||
|
top: 50%;
|
||||||
|
-webkit-transform: translateY(-50%);
|
||||||
|
-ms-transform: translateY(-50%);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
|
||||||
|
li {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drop down file names under the retry button on mobile */
|
||||||
|
@media screen and (max-width: 625px) {
|
||||||
|
height: auto;
|
||||||
|
max-width: none;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-top: 10px;
|
||||||
|
top: 0;
|
||||||
|
-webkit-transform: none;
|
||||||
|
-ms-transform: none;
|
||||||
|
transform: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 25%;
|
||||||
|
padding-right: 10px;
|
||||||
|
|
||||||
|
&:not(:last-child)::after {
|
||||||
|
content: ',';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-drag-and-drop-error-icon-container {
|
||||||
|
background-color: #eeeeee;
|
||||||
|
display: inline-block;
|
||||||
|
float: left;
|
||||||
|
height: 104px;
|
||||||
|
position: relative;
|
||||||
|
width: 104px;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
|
.file-drag-and-drop-error-icon {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.file-drag-and-drop-error-icon-container-multiple-files {
|
||||||
|
background-color: #d7d7d7;
|
||||||
|
left: -15px;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
display: block;
|
||||||
|
height: 104px;
|
||||||
|
left: 15px;
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
width: 104px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
display: block;
|
||||||
|
height: 104px;
|
||||||
|
left: 30px;
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
width: 104px;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-drag-and-drop-error-icon {
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide the icon when the screen is too small */
|
||||||
|
@media screen and (max-width: 625px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ascribe-progress-bar {
|
.ascribe-progress-bar {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
> .progress-bar {
|
> .progress-bar {
|
||||||
@ -197,4 +349,4 @@
|
|||||||
span + .btn {
|
span + .btn {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user