2015-06-23 10:16:53 +02:00
'use strict' ;
import React from 'react/addons' ;
2015-09-15 14:20:30 +02:00
import fineUploader from 'fineUploader' ;
2015-07-24 13:49:16 +02:00
import Q from 'q' ;
2015-06-23 10:16:53 +02:00
2015-06-30 15:41:39 +02:00
import S3Fetcher from '../../fetchers/s3_fetcher' ;
2015-06-23 10:16:53 +02:00
2015-09-15 11:13:17 +02:00
import FileDragAndDrop from './ascribe_file_drag_and_drop/file_drag_and_drop' ;
2015-06-23 10:16:53 +02:00
2015-12-08 18:23:02 +01:00
import ErrorQueueStore from '../../stores/error_queue_store' ;
2015-06-25 11:12:40 +02:00
import GlobalNotificationModel from '../../models/global_notification_model' ;
import GlobalNotificationActions from '../../actions/global_notification_actions' ;
2015-07-16 18:17:45 +02:00
import AppConstants from '../../constants/application_constants' ;
2015-12-08 18:23:02 +01:00
import { ErrorClasses , testErrorAgainstAll } from '../../constants/error_constants' ;
2016-04-20 15:15:59 +02:00
import { RETRY _ATTEMPT _TO _SHOW _CONTACT _US , ENDPOINTS } from '../../constants/uploader_constants' ;
2015-07-16 18:17:45 +02:00
2015-11-25 16:27:32 +01:00
import { displayValidFilesFilter , FileStatus , transformAllowedExtensionsToInputAcceptProp } from './react_s3_fine_uploader_utils' ;
2015-09-15 11:50:23 +02:00
import { getCookie } from '../../utils/fetch_api_utils' ;
2016-01-26 11:34:12 +01:00
import { computeHashOfFile , extractFileExtensionFromString } from '../../utils/file_utils' ;
2015-09-15 11:50:23 +02:00
import { getLangText } from '../../utils/lang_utils' ;
2015-07-23 15:57:25 +02:00
2015-11-17 13:37:01 +01:00
const { shape ,
string ,
oneOfType ,
number ,
func ,
bool ,
any ,
object ,
oneOf ,
element ,
arrayOf } = React . PropTypes ;
const ReactS3FineUploader = React . createClass ( {
2015-06-23 10:16:53 +02:00
propTypes : {
2015-12-08 18:09:24 +01:00
areAssetsDownloadable : bool ,
areAssetsEditable : bool ,
2015-12-08 18:22:11 +01:00
errorNotificationMessage : string ,
2015-12-08 18:09:24 +01:00
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
2016-02-05 17:06:16 +01:00
onValidationFailed : func ,
setWarning : func , // for when the parent component wants to be notified of uploader warnings (ie. upload failed)
showErrorPrompt : bool ,
2015-12-08 18:09:24 +01:00
// Handle form validation
setIsUploadReady : func , //TODO: rename to setIsUploaderValidated
isReadyForFormSubmission : func ,
// We encountered some cases where people had difficulties to upload their
// works to ascribe due to a slow internet connection.
// One solution we found in the process of tackling this problem was to hash
// the file in the browser using md5 and then uploading the resulting text document instead
// of the actual file.
//
// This boolean and string essentially enable that behavior.
// Right now, we determine which upload method to use by appending a query parameter,
// which should be passed into 'uploadMethod':
// 'hash': upload using the hash
// 'upload': upload full file (default if not specified)
enableLocalHashing : bool ,
uploadMethod : oneOf ( [ 'hash' , 'upload' ] ) ,
// A class of a file the user has to upload
// Needs to be defined both in singular as well as in plural
fileClassToUpload : shape ( {
singular : string ,
plural : string
} ) ,
// Uploading functionality of react fineuploader is disconnected from its UI
// layer, which means that literally every (properly adjusted) react element
// can handle the UI handling.
fileInputElement : oneOfType ( [
func ,
element
] ) ,
// S3 helpers
2016-02-05 17:06:16 +01:00
createBlobRoutine : shape ( {
2015-11-17 13:37:01 +01:00
url : string ,
2016-01-14 14:52:33 +01:00
pieceId : number
2015-06-23 10:16:53 +02:00
} ) ,
2016-02-05 17:06:16 +01:00
keyRoutine : shape ( {
2015-11-17 13:37:01 +01:00
url : string ,
2016-02-05 17:06:16 +01:00
fileClass : string ,
2016-01-14 14:52:33 +01:00
pieceId : number
2015-06-23 13:55:05 +02:00
} ) ,
2015-12-08 18:09:24 +01:00
// FineUploader options
2015-11-17 13:37:01 +01:00
debug : bool ,
2016-02-05 17:06:16 +01:00
autoUpload : bool ,
chunking : shape ( {
enabled : bool
} ) ,
cors : shape ( {
expected : bool
} ) ,
deleteFile : shape ( {
enabled : bool ,
method : string ,
endpoint : string ,
customHeaders : object
} ) . isRequired ,
formatFileName : func ,
messages : shape ( {
unsupportedBrowser : string
} ) ,
2015-12-08 18:09:24 +01:00
multiple : bool ,
2015-11-17 13:37:01 +01:00
objectProperties : shape ( {
acl : string
2015-06-23 10:16:53 +02:00
} ) ,
2015-11-17 13:37:01 +01:00
request : shape ( {
endpoint : string ,
accessKey : string ,
params : shape ( {
csrfmiddlewaretoken : string
2015-06-23 10:16:53 +02:00
} )
} ) ,
2016-02-05 17:06:16 +01:00
resume : shape ( {
enabled : bool
} ) ,
retry : shape ( {
enableAuto : bool
} ) ,
session : shape ( {
customHeaders : object ,
endpoint : string ,
params : object ,
refreshOnRequests : bool
} ) ,
2015-11-17 13:37:01 +01:00
signature : shape ( {
endpoint : string
2015-07-02 16:51:22 +02:00
} ) . isRequired ,
2015-11-17 13:37:01 +01:00
uploadSuccess : shape ( {
method : string ,
endpoint : string ,
params : shape ( {
isBrowserPreviewCapable : any , // maybe fix this later
bitcoin _ID _noPrefix : string
2015-06-23 10:16:53 +02:00
} )
} ) ,
2015-11-17 13:37:01 +01:00
validation : shape ( {
itemLimit : number ,
2016-01-21 12:24:15 +01:00
sizeLimit : number ,
2015-11-17 13:37:01 +01:00
allowedExtensions : arrayOf ( string )
2015-12-08 18:09:24 +01:00
} )
2015-06-23 10:16:53 +02:00
} ,
2015-06-29 10:00:26 +02:00
getDefaultProps ( ) {
return {
2015-12-08 18:22:11 +01:00
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
2015-06-29 10:00:26 +02:00
autoUpload : true ,
debug : false ,
2015-12-08 18:22:11 +01:00
multiple : false ,
2015-06-29 10:00:26 +02:00
objectProperties : {
acl : 'public-read' ,
bucket : 'ascribe0'
} ,
request : {
2016-04-20 15:15:59 +02:00
endpoint : ENDPOINTS . accelerate ,
2015-06-29 10:00:26 +02:00
accessKey : 'AKIAIVCZJ33WSCBQ3QDA'
} ,
uploadSuccess : {
params : {
isBrowserPreviewCapable : fineUploader . supportedFeatures . imagePreviews
}
} ,
2015-07-02 18:54:47 +02:00
cors : {
expected : true ,
sendCredentials : true
} ,
2015-06-29 10:00:26 +02:00
chunking : {
2015-07-23 18:14:17 +02:00
enabled : true ,
concurrent : {
enabled : true
}
2015-06-29 10:00:26 +02:00
} ,
resume : {
enabled : true
} ,
retry : {
enableAuto : false
} ,
session : {
endpoint : null
} ,
messages : {
2015-07-23 17:17:48 +02:00
unsupportedBrowser : '<h3>' + getLangText ( 'Upload is not functional in IE7 as IE7 has no support for CORS!' ) + '</h3>'
2015-06-29 10:00:26 +02:00
} ,
2016-02-05 15:17:59 +01:00
formatFileName : function ( name ) { // fix maybe
2015-06-29 10:00:26 +02:00
if ( name !== undefined && name . length > 26 ) {
name = name . slice ( 0 , 15 ) + '...' + name . slice ( - 15 ) ;
}
return name ;
2015-12-08 18:22:11 +01:00
}
2015-06-29 10:00:26 +02:00
} ;
} ,
2015-06-29 11:01:45 +02:00
2015-06-23 13:55:05 +02:00
getInitialState ( ) {
return {
filesToUpload : [ ] ,
2015-12-08 18:22:11 +01:00
uploader : this . createNewFineUploader ( ) ,
2015-07-23 17:05:52 +02:00
csrfToken : getCookie ( AppConstants . csrftoken ) ,
2015-12-08 18:22:11 +01:00
errorState : {
manualRetryAttempt : 0 ,
2016-02-05 15:17:59 +01:00
errorClass : null
2015-12-08 18:22:11 +01:00
} ,
uploadInProgress : false ,
2015-11-13 12:20:52 +01:00
2015-07-24 16:17:27 +02:00
// -1: aborted
// -2: uninitialized
2015-08-03 18:35:56 +02:00
hashingProgress : - 2 ,
2015-11-13 12:20:52 +01:00
2015-08-03 18:35:56 +02:00
// this is for logging
chunks : { }
2015-06-23 13:55:05 +02:00
} ;
} ,
2015-07-08 10:15:58 +02:00
componentWillUpdate ( ) {
2015-07-27 15:34:45 +02:00
// since the csrf header is defined in this component's props,
// everytime the csrf cookie is changed we'll need to reinitalize
// fineuploader and update the actual csrf token
2015-07-16 18:17:45 +02:00
let potentiallyNewCSRFToken = getCookie ( AppConstants . csrftoken ) ;
2015-07-02 16:51:22 +02:00
if ( this . state . csrfToken !== potentiallyNewCSRFToken ) {
this . setState ( {
2015-12-08 18:22:11 +01:00
uploader : this . createNewFineUploader ( ) ,
2015-07-02 16:51:22 +02:00
csrfToken : potentiallyNewCSRFToken
} ) ;
}
} ,
2015-07-14 20:10:41 +02:00
componentWillUnmount ( ) {
// Without this method, fineuploader will continue to try to upload artworks
// even though this component is not mounted any more.
// Therefore we cancel all uploads
this . state . uploader . cancelAll ( ) ;
} ,
2015-12-08 18:22:11 +01:00
createNewFineUploader ( ) {
return new fineUploader . s3 . FineUploaderBasic ( this . propsToConfig ( ) ) ;
} ,
2015-06-23 10:16:53 +02:00
propsToConfig ( ) {
2015-12-08 18:22:11 +01:00
const objectProperties = Object . assign ( { } , this . props . objectProperties ) ;
2015-06-23 13:55:05 +02:00
objectProperties . key = this . requestKey ;
2015-06-23 10:16:53 +02:00
return {
autoUpload : this . props . autoUpload ,
debug : this . props . debug ,
objectProperties : objectProperties , // do a special key handling here
request : this . props . request ,
signature : this . props . signature ,
uploadSuccess : this . props . uploadSuccess ,
cors : this . props . cors ,
chunking : this . props . chunking ,
resume : this . props . resume ,
deleteFile : this . props . deleteFile ,
session : this . props . session ,
validation : this . props . validation ,
messages : this . props . messages ,
formatFileName : this . props . formatFileName ,
multiple : this . props . multiple ,
retry : this . props . retry ,
callbacks : {
2015-12-08 18:22:11 +01:00
onAllComplete : this . onAllComplete ,
2015-06-23 10:16:53 +02:00
onComplete : this . onComplete ,
2015-06-29 10:01:54 +02:00
onCancel : this . onCancel ,
2015-06-23 10:16:53 +02:00
onProgress : this . onProgress ,
2015-06-30 15:41:39 +02:00
onDeleteComplete : this . onDeleteComplete ,
2015-07-14 10:33:15 +02:00
onSessionRequestComplete : this . onSessionRequestComplete ,
2015-07-15 16:50:32 +02:00
onError : this . onError ,
2015-08-03 18:35:56 +02:00
onUploadChunk : this . onUploadChunk ,
onUploadChunkSuccess : this . onUploadChunkSuccess
2015-06-23 10:16:53 +02:00
}
} ;
} ,
2015-06-23 13:55:05 +02:00
2015-09-10 11:22:42 +02:00
// Resets the whole react fineuploader component to its initial state
reset ( ) {
// Cancel all currently ongoing uploads
2015-11-13 12:20:52 +01:00
this . cancelUploads ( ) ;
2015-09-10 11:22:42 +02:00
// and reset component in general
this . state . uploader . reset ( ) ;
// proclaim that upload is not ready
this . props . setIsUploadReady ( false ) ;
2015-12-08 18:23:02 +01:00
// reset any warnings propagated to parent
this . setWarning ( false ) ;
2015-09-10 11:22:42 +02:00
// reset internal data structures of component
this . setState ( this . getInitialState ( ) ) ;
} ,
2015-06-23 10:16:53 +02:00
requestKey ( fileId ) {
let filename = this . state . uploader . getName ( fileId ) ;
2015-07-20 18:59:32 +02:00
let uuid = this . state . uploader . getUuid ( fileId ) ;
2015-06-23 13:55:05 +02:00
2015-07-27 09:28:50 +02:00
return Q . Promise ( ( resolve , reject ) => {
window . fetch ( this . props . keyRoutine . url , {
method : 'post' ,
headers : {
'Accept' : 'application/json' ,
'Content-Type' : 'application/json' ,
'X-CSRFToken' : getCookie ( AppConstants . csrftoken )
} ,
credentials : 'include' ,
body : JSON . stringify ( {
'filename' : filename ,
'category' : this . props . keyRoutine . fileClass ,
'uuid' : uuid ,
'piece_id' : this . props . keyRoutine . pieceId
} )
} )
. then ( ( res ) => {
return res . json ( ) ;
2015-07-14 10:36:37 +02:00
} )
2015-07-27 09:28:50 +02:00
. then ( ( res ) => {
resolve ( res . key ) ;
2015-07-14 10:36:37 +02:00
} )
2015-07-27 09:28:50 +02:00
. catch ( ( err ) => {
2015-11-11 18:17:32 +01:00
this . onErrorPromiseProxy ( err ) ;
2015-07-27 09:28:50 +02:00
reject ( err ) ;
} ) ;
2015-06-23 13:55:05 +02:00
} ) ;
} ,
createBlob ( file ) {
2015-11-09 16:58:35 +01:00
const { createBlobRoutine } = this . props ;
2015-07-27 09:28:50 +02:00
return Q . Promise ( ( resolve , reject ) => {
2015-11-11 18:17:32 +01:00
// if createBlobRoutine is not defined,
// we're progressing right away without posting to S3
// so that this can be done manually by the form
2016-02-05 17:06:16 +01:00
if ( ! createBlobRoutine ) {
2015-11-11 18:17:32 +01:00
// still we warn the user of this component
console . warn ( 'createBlobRoutine was not defined for ReactS3FineUploader. Continuing without creating the blob on the server.' ) ;
resolve ( ) ;
2015-12-14 11:57:10 +01:00
return ;
2015-11-11 18:17:32 +01:00
}
window . fetch ( createBlobRoutine . url , {
2015-07-27 09:28:50 +02:00
method : 'post' ,
headers : {
'Accept' : 'application/json' ,
'Content-Type' : 'application/json' ,
'X-CSRFToken' : getCookie ( AppConstants . csrftoken )
} ,
credentials : 'include' ,
body : JSON . stringify ( {
'filename' : file . name ,
'key' : file . key ,
2015-11-11 18:17:32 +01:00
'piece_id' : createBlobRoutine . pieceId
2015-07-27 09:28:50 +02:00
} )
2015-06-23 13:55:05 +02:00
} )
2015-07-27 09:28:50 +02:00
. then ( ( res ) => {
return res . json ( ) ;
} )
2015-10-21 14:52:25 +02:00
. then ( ( res ) => {
2015-07-27 09:28:50 +02:00
if ( res . otherdata ) {
file . s3Url = res . otherdata . url _safe ;
file . s3UrlSafe = res . otherdata . url _safe ;
} else if ( res . digitalwork ) {
file . s3Url = res . digitalwork . url _safe ;
file . s3UrlSafe = res . digitalwork . url _safe ;
2015-08-31 17:29:43 +02:00
} else if ( res . contractblob ) {
file . s3Url = res . contractblob . url _safe ;
file . s3UrlSafe = res . contractblob . url _safe ;
2015-11-11 18:17:32 +01:00
} else if ( res . thumbnail ) {
file . s3Url = res . thumbnail . url _safe ;
file . s3UrlSafe = res . thumbnail . url _safe ;
2015-07-27 09:28:50 +02:00
} else {
throw new Error ( getLangText ( 'Could not find a url to download.' ) ) ;
}
2015-07-27 14:21:26 +02:00
resolve ( res ) ;
2015-07-27 09:28:50 +02:00
} )
. catch ( ( err ) => {
2015-11-11 18:17:32 +01:00
this . onErrorPromiseProxy ( err ) ;
2015-07-27 09:28:50 +02:00
reject ( err ) ;
} ) ;
2015-06-23 13:55:05 +02:00
} ) ;
2015-06-23 10:16:53 +02:00
} ,
2016-01-26 11:33:38 +01:00
// Cancel uploads and clear previously selected files on the input element
cancelUploads ( id ) {
typeof id !== 'undefined' ? this . state . uploader . cancel ( id ) : this . state . uploader . cancelAll ( ) ;
2015-11-23 17:59:20 +01:00
2016-01-26 11:33:38 +01:00
// Reset the file input element to clear the previously selected files so that
// the user can reselect them again.
this . clearFileSelection ( ) ;
2015-12-08 18:23:02 +01:00
} ,
2015-12-08 18:22:11 +01:00
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' ) ;
}
} ,
2016-01-26 11:33:38 +01:00
clearFileSelection ( ) {
const { fileInput } = this . refs ;
if ( fileInput && typeof fileInput . clearSelection === 'function' ) {
fileInput . clearSelection ( ) ;
}
} ,
2015-12-08 18:22:11 +01:00
2016-01-26 11:33:38 +01:00
getAllowedExtensions ( ) {
const { validation : { allowedExtensions } = { } } = this . props ;
2015-12-08 18:22:11 +01:00
2016-01-26 11:33:38 +01:00
if ( allowedExtensions && allowedExtensions . length ) {
return transformAllowedExtensionsToInputAcceptProp ( allowedExtensions ) ;
2015-12-08 18:22:11 +01:00
} else {
2016-01-26 11:33:38 +01:00
return null ;
2015-12-08 18:22:11 +01:00
}
} ,
getUploadErrorClass ( { type = 'upload' , reason , xhr } ) {
const { manualRetryAttempt } = this . state . errorState ;
let matchedErrorClass ;
2016-02-05 15:17:59 +01:00
if ( 'onLine' in window . navigator && ! window . navigator . onLine ) {
// If the user's offline, this is definitely the most important error to show.
// TODO: use a better mechanism for checking network state, ie. offline.js
matchedErrorClass = ErrorClasses . upload . offline ;
} else if ( manualRetryAttempt === RETRY _ATTEMPT _TO _SHOW _CONTACT _US ) {
// Use the contact us error class if they've retried a number of times
// and are still unsuccessful
2015-12-08 18:22:11 +01:00
matchedErrorClass = ErrorClasses . upload . contactUs ;
} else {
matchedErrorClass = testErrorAgainstAll ( { type , reason , xhr } ) ;
2016-02-05 15:17:59 +01:00
if ( ! matchedErrorClass ) {
// If none found, show the next error message in the queue for upload errors
2015-12-08 18:22:11 +01:00
matchedErrorClass = ErrorQueueStore . getNextError ( 'upload' ) ;
}
}
return matchedErrorClass ;
} ,
getXhrErrorComment ( xhr ) {
if ( xhr ) {
return {
response : xhr . response ,
url : xhr . responseURL ,
status : xhr . status ,
statusText : xhr . statusText
} ;
}
} ,
2016-01-26 11:33:38 +01:00
isDropzoneInactive ( ) {
2016-02-05 17:06:16 +01:00
const { areAssetsEditable , enableLocalHashing , multiple , showErrorPrompt , uploadMethod } = this . props ;
const { errorState , filesToUpload } = this . state ;
2015-07-14 10:36:37 +02:00
2016-02-05 17:06:16 +01:00
const filesToDisplay = filesToUpload . filter ( ( file ) => {
return file . status !== FileStatus . DELETED &&
file . status !== FileStatus . CANCELED &&
file . status !== FileStatus . UPLOAD _FAILED &&
file . size !== - 1 ;
} ) ;
2016-01-26 11:33:38 +01:00
2016-02-05 17:06:16 +01:00
return ( enableLocalHashing && ! uploadMethod ) ||
! areAssetsEditable ||
( showErrorPrompt && errorState . errorClass ) ||
2016-02-08 10:41:36 +01:00
( ! multiple && filesToDisplay . length > 0 ) ;
2016-01-26 11:33:38 +01:00
} ,
2016-01-26 11:34:12 +01:00
isFileValid ( file ) {
const { validation : { allowedExtensions , sizeLimit = 0 } , onValidationFailed } = this . props ;
const fileExt = extractFileExtensionFromString ( file . name ) ;
if ( file . size > sizeLimit ) {
const fileSizeInMegaBytes = sizeLimit / 1000000 ;
const notification = new GlobalNotificationModel ( getLangText ( 'A file you submitted is bigger than ' + fileSizeInMegaBytes + 'MB.' ) , 'danger' , 5000 ) ;
GlobalNotificationActions . appendGlobalNotification ( notification ) ;
if ( typeof onValidationFailed === 'function' ) {
onValidationFailed ( file ) ;
}
return false ;
} else if ( allowedExtensions && ! allowedExtensions . includes ( fileExt ) ) {
const notification = new GlobalNotificationModel ( getLangText ( ` The file you've submitted is of an invalid file format: Valid format(s): ${ allowedExtensions . join ( ', ' ) } ` ) , 'danger' , 5000 ) ;
GlobalNotificationActions . appendGlobalNotification ( notification ) ;
return false ;
} else {
return true ;
}
} ,
selectValidFiles ( files ) {
return Array . from ( files ) . reduce ( ( validFiles , file ) => {
if ( this . isFileValid ( file ) ) {
validFiles . push ( file ) ;
}
return validFiles ;
} , [ ] ) ;
} ,
2016-01-26 11:33:38 +01:00
// This method has been made promise-based to immediately afterwards
// call a callback function (instantly after this.setState went through)
// This is e.g. needed when showing/hiding the optional thumbnail upload
// field in the registration form
setStatusOfFile ( fileId , status ) {
return Q . Promise ( ( resolve ) => {
let changeSet = { } ;
2016-02-05 17:06:16 +01:00
if ( status === FileStatus . DELETED || status === FileStatus . CANCELED || status === FileStatus . UPLOAD _FAILED ) {
2016-01-26 11:33:38 +01:00
changeSet . progress = { $set : 0 } ;
}
changeSet . status = { $set : status } ;
let filesToUpload = React . addons . update ( this . state . filesToUpload , { [ fileId ] : changeSet } ) ;
this . setState ( { filesToUpload } , resolve ) ;
} ) ;
} ,
2015-11-23 17:59:20 +01:00
setThumbnailForFileId ( fileId , url ) {
const { filesToUpload } = this . state ;
if ( fileId < filesToUpload . length ) {
const changeSet = { $set : url } ;
2015-12-03 10:42:55 +01:00
const newFilesToUpload = React . addons . update ( filesToUpload , {
[ fileId ] : { thumbnailUrl : changeSet }
} ) ;
2015-11-23 17:59:20 +01:00
this . setState ( { filesToUpload : newFilesToUpload } ) ;
} else {
2015-12-03 10:42:55 +01:00
throw new Error ( 'Accessing an index out of range of filesToUpload' ) ;
2015-11-23 17:59:20 +01:00
}
} ,
2016-02-05 17:06:16 +01:00
setWarning ( hasWarning ) {
if ( typeof this . props . setWarning === 'function' ) {
this . props . setWarning ( hasWarning ) ;
}
} ,
2015-07-14 10:36:37 +02:00
2016-02-05 17:06:16 +01:00
/* FineUploader specific callback function handlers */
2015-08-03 18:35:56 +02:00
onUploadChunk ( id , name , chunkData ) {
let chunks = this . state . chunks ;
chunks [ id + '-' + chunkData . startByte + '-' + chunkData . endByte ] = {
id ,
name ,
chunkData ,
completed : false
} ;
2015-09-07 12:02:01 +02:00
let startedChunks = React . addons . update ( this . state . startedChunks , { $set : chunks } ) ;
2015-08-03 18:35:56 +02:00
2015-09-07 12:02:01 +02:00
this . setState ( { startedChunks } ) ;
2015-08-03 18:35:56 +02:00
} ,
onUploadChunkSuccess ( id , chunkData , responseJson , xhr ) {
let chunks = this . state . chunks ;
let chunkKey = id + '-' + chunkData . startByte + '-' + chunkData . endByte ;
2015-11-13 12:20:52 +01:00
2015-08-03 18:35:56 +02:00
if ( chunks [ chunkKey ] ) {
chunks [ chunkKey ] . completed = true ;
chunks [ chunkKey ] . responseJson = responseJson ;
chunks [ chunkKey ] . xhr = xhr ;
2015-09-07 12:02:01 +02:00
let startedChunks = React . addons . update ( this . state . startedChunks , { $set : chunks } ) ;
2015-08-03 18:35:56 +02:00
2015-09-07 12:02:01 +02:00
this . setState ( { startedChunks } ) ;
2015-08-03 18:35:56 +02:00
}
2015-12-08 18:22:11 +01:00
} ,
2015-08-03 18:35:56 +02:00
2015-12-08 18:22:11 +01:00
onAllComplete ( succeed , failed ) {
if ( this . state . uploadInProgress ) {
this . setState ( {
uploadInProgress : false
} ) ;
}
2015-08-03 18:35:56 +02:00
} ,
2015-08-03 10:54:34 +02:00
onComplete ( id , name , res , xhr ) {
2015-11-13 17:33:01 +01:00
// There has been an issue with the server's connection
if ( xhr && xhr . status === 0 && res . success ) {
2015-12-14 14:31:53 +01:00
console . logGlobal ( new Error ( 'Upload succeeded with a status code 0' ) , {
2015-08-03 18:50:14 +02:00
files : this . state . filesToUpload ,
2015-11-13 17:33:01 +01:00
chunks : this . state . chunks ,
xhr : this . getXhrErrorComment ( xhr )
2015-08-03 18:50:14 +02:00
} ) ;
2015-11-13 17:33:01 +01:00
// onError will catch any errors, so we can ignore them here
2015-12-08 20:25:18 +01:00
} else if ( ! res . error && res . success ) {
2015-09-04 17:31:58 +02:00
let files = this . state . filesToUpload ;
2015-08-03 18:50:14 +02:00
2015-09-04 17:31:58 +02:00
// Set the state of the completed file to 'upload successful' in order to
// remove it from the GUI
2015-11-25 16:27:32 +01:00
files [ id ] . status = FileStatus . UPLOAD _SUCCESSFUL ;
2015-09-04 17:31:58 +02:00
files [ id ] . key = this . state . uploader . getKey ( id ) ;
2015-07-14 10:36:37 +02:00
2015-09-14 11:14:02 +02:00
let filesToUpload = React . addons . update ( this . state . filesToUpload , { $set : files } ) ;
this . setState ( { filesToUpload } ) ;
2015-07-14 10:36:37 +02:00
2015-09-04 17:31:58 +02:00
// Only after the blob has been created server-side, we can make the form submittable.
this . createBlob ( files [ id ] )
. then ( ( ) => {
2015-12-08 18:22:11 +01:00
if ( typeof this . props . submitFile === 'function' ) {
2015-09-14 14:46:03 +02:00
this . props . submitFile ( files [ id ] ) ;
2015-07-27 09:33:31 +02:00
} else {
2015-11-12 10:46:08 +01:00
console . warn ( 'You didn\'t define submitFile as a prop in react-s3-fine-uploader' ) ;
2015-07-27 09:33:31 +02:00
}
2015-11-13 12:20:52 +01:00
2015-12-08 18:22:11 +01:00
this . checkFormSubmissionReady ( ) ;
} ) ;
2015-09-04 17:31:58 +02:00
}
2015-07-14 10:36:37 +02:00
} ,
2015-11-11 18:17:32 +01:00
/ * *
* We want to channel all errors in this component through one single method .
2015-11-12 10:46:08 +01:00
* As fineuploader ' s ` onError ` method cannot handle the callback parameters of
2015-11-11 18:17:32 +01:00
* a promise we define this proxy method to crunch them into the correct form .
*
2015-11-12 10:46:08 +01:00
* @ param { error } err a plain Javascript error
2015-11-11 18:17:32 +01:00
* /
onErrorPromiseProxy ( err ) {
this . onError ( null , null , err . message ) ;
} ,
2015-11-13 16:16:54 +01:00
onError ( id , name , errorReason , xhr ) {
2015-12-08 18:22:11 +01:00
const { errorNotificationMessage , showErrorPrompt } = this . props ;
const { chunks , filesToUpload } = this . state ;
2015-12-14 14:31:53 +01:00
console . logGlobal ( errorReason , {
2015-12-08 18:22:11 +01:00
files : filesToUpload ,
chunks : chunks ,
2015-11-13 17:32:20 +01:00
xhr : this . getXhrErrorComment ( xhr )
} ) ;
2015-12-08 18:22:11 +01:00
let notificationMessage ;
2015-11-13 17:32:20 +01:00
2015-12-08 18:22:11 +01:00
if ( showErrorPrompt ) {
this . setStatusOfFile ( id , FileStatus . UPLOAD _FAILED ) ;
2015-06-23 10:16:53 +02:00
2015-12-08 18:22:11 +01:00
// If we've already found an error on this upload, just ignore other errors
// that pop up. They'll likely pop up again when the user retries.
if ( ! this . state . errorState . errorClass ) {
2015-12-08 20:25:18 +01:00
notificationMessage = errorNotificationMessage ;
2015-07-23 17:17:48 +02:00
2015-12-08 18:22:11 +01:00
const errorState = React . addons . update ( this . state . errorState , {
errorClass : {
$set : this . getUploadErrorClass ( {
reason : errorReason ,
xhr
} )
}
} ) ;
2015-08-24 13:57:02 +02:00
2015-12-08 18:22:11 +01:00
this . setState ( { errorState } ) ;
2015-12-08 18:23:02 +01:00
this . setWarning ( true ) ;
2015-12-21 11:53:27 +01:00
}
2015-08-24 13:57:02 +02:00
} else {
2015-12-08 18:22:11 +01:00
notificationMessage = errorReason || errorNotificationMessage ;
this . cancelUploads ( ) ;
2015-07-15 16:50:32 +02:00
}
2015-12-08 18:22:11 +01:00
2015-12-08 20:25:18 +01:00
if ( notificationMessage ) {
const notification = new GlobalNotificationModel ( notificationMessage , 'danger' , 5000 ) ;
GlobalNotificationActions . appendGlobalNotification ( notification ) ;
2015-07-15 16:50:32 +02:00
}
} ,
2015-06-29 10:01:54 +02:00
onCancel ( id ) {
2015-08-11 11:39:58 +02:00
// when a upload is canceled, we need to update this components file array
2015-11-25 16:27:32 +01:00
this . setStatusOfFile ( id , FileStatus . CANCELED )
2015-11-17 15:46:46 +01:00
. then ( ( ) => {
2015-11-23 17:59:20 +01:00
if ( typeof this . props . handleChangedFile === 'function' ) {
this . props . handleChangedFile ( this . state . filesToUpload [ id ] ) ;
2015-11-17 15:46:46 +01:00
}
} ) ;
2015-06-29 10:01:54 +02:00
2015-07-23 17:17:48 +02:00
let notification = new GlobalNotificationModel ( getLangText ( 'File upload canceled' ) , 'success' , 5000 ) ;
2015-06-29 10:01:54 +02:00
GlobalNotificationActions . appendGlobalNotification ( notification ) ;
2015-06-29 11:44:16 +02:00
2015-12-08 18:22:11 +01:00
this . checkFormSubmissionReady ( ) ;
// FineUploader's onAllComplete event doesn't fire if all files are cancelled
// so we need to double check if this is the last file getting cancelled.
//
// Because we're calling FineUploader.getInProgress() in a cancel callback,
// the current file getting cancelled is still considered to be in progress
// so there will be one file left in progress when we're cancelling the last file.
if ( this . state . uploader . getInProgress ( ) === 1 ) {
this . setState ( {
uploadInProgress : false
} ) ;
2015-06-29 11:44:16 +02:00
}
2015-09-07 12:02:01 +02:00
return true ;
2015-06-23 10:16:53 +02:00
} ,
onProgress ( id , name , uploadedBytes , totalBytes ) {
2015-09-07 12:02:01 +02:00
let filesToUpload = React . addons . update ( this . state . filesToUpload , {
[ id ] : {
progress : { $set : ( uploadedBytes / totalBytes ) * 100 }
2015-06-23 10:16:53 +02:00
}
} ) ;
2015-09-07 12:02:01 +02:00
this . setState ( { filesToUpload } ) ;
2015-06-23 10:16:53 +02:00
} ,
2015-06-30 15:41:39 +02:00
onSessionRequestComplete ( response , success ) {
if ( success ) {
// fetch blobs for images
response = response . map ( ( file ) => {
2015-07-01 10:00:53 +02:00
file . url = file . s3UrlSafe ;
2015-11-25 16:27:32 +01:00
file . status = FileStatus . ONLINE ;
2015-06-30 15:41:39 +02:00
file . progress = 100 ;
return file ;
} ) ;
// add file to filesToUpload
let updatedFilesToUpload = this . state . filesToUpload . concat ( response ) ;
// refresh all files ids,
updatedFilesToUpload = updatedFilesToUpload . map ( ( file , i ) => {
file . id = i ;
return file ;
} ) ;
2015-09-07 12:02:01 +02:00
let filesToUpload = React . addons . update ( this . state . filesToUpload , { $set : updatedFilesToUpload } ) ;
this . setState ( { filesToUpload } ) ;
2015-06-30 15:41:39 +02:00
} else {
2015-06-30 17:57:20 +02:00
// server has to respond with 204
2015-06-30 17:12:51 +02:00
//let notification = new GlobalNotificationModel('Could not load attached files (Further data)', 'danger', 10000);
//GlobalNotificationActions.appendGlobalNotification(notification);
//
//throw new Error('The session request failed', response);
2015-06-30 15:41:39 +02:00
}
} ,
2015-06-30 16:15:50 +02:00
onDeleteComplete ( id , xhr , isError ) {
if ( isError ) {
2015-11-25 16:27:32 +01:00
this . setStatusOfFile ( id , FileStatus . ONLINE ) ;
2015-09-09 17:21:55 +02:00
let notification = new GlobalNotificationModel ( getLangText ( 'There was an error deleting your file.' ) , 'danger' , 10000 ) ;
2015-06-30 16:15:50 +02:00
GlobalNotificationActions . appendGlobalNotification ( notification ) ;
} else {
2015-07-08 10:15:58 +02:00
let notification = new GlobalNotificationModel ( getLangText ( 'File deleted' ) , 'success' , 5000 ) ;
2015-06-30 16:15:50 +02:00
GlobalNotificationActions . appendGlobalNotification ( notification ) ;
}
2015-12-08 18:22:11 +01:00
this . checkFormSubmissionReady ( ) ;
2015-06-30 16:15:50 +02:00
} ,
2015-06-23 10:16:53 +02:00
handleDeleteFile ( fileId ) {
2015-09-09 17:21:55 +02:00
// We set the files state to 'deleted' immediately, so that the user is not confused with
// the unresponsiveness of the UI
//
2015-11-25 16:27:32 +01:00
// If there is an error during the deletion, we will just change the status back to FileStatus.ONLINE
2015-09-09 17:21:55 +02:00
// and display an error message
2015-11-25 16:27:32 +01:00
this . setStatusOfFile ( fileId , FileStatus . DELETED )
2015-11-17 15:46:46 +01:00
. then ( ( ) => {
2015-11-23 17:59:20 +01:00
if ( typeof this . props . handleChangedFile === 'function' ) {
this . props . handleChangedFile ( this . state . filesToUpload [ fileId ] ) ;
2015-11-17 15:46:46 +01:00
}
} ) ;
2015-09-09 17:21:55 +02:00
2015-08-24 12:20:54 +02:00
// In some instances (when the file was already uploaded and is just displayed to the user
2015-08-31 11:05:33 +02:00
// - for example in the contract or additional files dialog)
2015-06-30 15:41:39 +02:00
// fineuploader does not register an id on the file (we do, don't be confused by this!).
// Since you can only delete a file by its id, we have to implement this method ourselves
2015-06-30 16:15:50 +02:00
//
2015-06-30 15:41:39 +02:00
// So, if an id is not present, we delete the file manually
// To check which files are already uploaded from previous sessions we check their status.
// If they are, it is "online"
2015-11-25 16:27:32 +01:00
if ( this . state . filesToUpload [ fileId ] . status !== FileStatus . ONLINE ) {
2015-06-30 15:41:39 +02:00
// delete file from server
this . state . uploader . deleteFile ( fileId ) ;
2015-08-11 11:39:58 +02:00
// this is being continued in onDeleteFile, as
2015-06-30 15:41:39 +02:00
// fineuploaders deleteFile does not return a correct callback or
// promise
} else {
let fileToDelete = this . state . filesToUpload [ fileId ] ;
S3Fetcher
. deleteFile ( fileToDelete . s3Key , fileToDelete . s3Bucket )
2015-06-30 16:15:50 +02:00
. then ( ( ) => this . onDeleteComplete ( fileToDelete . id , null , false ) )
. catch ( ( ) => this . onDeleteComplete ( fileToDelete . id , null , true ) ) ;
2015-06-30 15:41:39 +02:00
}
2015-06-23 10:16:53 +02:00
} ,
2015-06-29 10:01:54 +02:00
handleCancelFile ( fileId ) {
2015-11-13 12:20:52 +01:00
this . cancelUploads ( fileId ) ;
2015-06-29 10:01:54 +02:00
} ,
2016-01-26 11:33:38 +01:00
handleCancelHashing ( ) {
// Every progress tick of the hashing function in handleUploadFile there is a
// check if this.state.hashingProgress is -1. If so, there is an error thrown that cancels
// the hashing of all files immediately.
this . setState ( { hashingProgress : - 1 } ) ;
} ,
2015-06-29 17:37:14 +02:00
handlePauseFile ( fileId ) {
if ( this . state . uploader . pauseUpload ( fileId ) ) {
2015-11-25 16:27:32 +01:00
this . setStatusOfFile ( fileId , FileStatus . PAUSED ) ;
2015-06-29 17:37:14 +02:00
} else {
2015-07-23 17:17:48 +02:00
throw new Error ( getLangText ( 'File upload could not be paused.' ) ) ;
2015-06-29 17:37:14 +02:00
}
} ,
handleResumeFile ( fileId ) {
if ( this . state . uploader . continueUpload ( fileId ) ) {
2015-11-25 16:27:32 +01:00
this . setStatusOfFile ( fileId , FileStatus . UPLOADING ) ;
2015-06-29 17:37:14 +02:00
} else {
2015-07-23 17:17:48 +02:00
throw new Error ( getLangText ( 'File upload could not be resumed.' ) ) ;
2015-06-29 17:37:14 +02:00
}
} ,
2015-12-08 18:22:11 +01:00
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
} ) ;
2015-12-08 18:23:02 +01:00
this . setWarning ( false ) ;
2015-12-08 18:22:11 +01:00
} ,
2015-06-23 10:16:53 +02:00
handleUploadFile ( files ) {
2015-11-11 18:17:32 +01:00
// While files are being uploaded, the form cannot be ready
// for submission
this . props . setIsUploadReady ( false ) ;
2015-06-25 11:12:40 +02:00
// If multiple set and user already uploaded its work,
// cancel upload
2015-09-10 09:54:02 +02:00
if ( ! this . props . multiple && this . state . filesToUpload . filter ( displayValidFilesFilter ) . length > 0 ) {
2015-11-13 12:20:52 +01:00
this . clearFileSelection ( ) ;
2015-06-25 11:12:40 +02:00
return ;
}
2016-01-26 11:34:12 +01:00
// Select only the submitted files that fit the file size and allowed extensions
files = this . selectValidFiles ( files ) ;
2015-08-24 13:57:02 +02:00
2015-06-25 11:12:40 +02:00
// if multiple is set to false and user drops multiple files into the dropzone,
// take the first one and notify user that only one file can be submitted
if ( ! this . props . multiple && files . length > 1 ) {
let tempFilesList = [ ] ;
tempFilesList . push ( files [ 0 ] ) ;
// replace filelist with first-element file list
files = tempFilesList ;
2015-07-03 19:08:56 +02:00
// TOOD translate?
2015-07-08 10:15:58 +02:00
let notification = new GlobalNotificationModel ( getLangText ( 'Only one file allowed (took first one)' ) , 'danger' , 10000 ) ;
2015-06-25 11:12:40 +02:00
GlobalNotificationActions . appendGlobalNotification ( notification ) ;
}
2015-07-23 15:57:25 +02:00
// As mentioned already in the propTypes declaration, in some instances we need to calculate the
// md5 hash of a file locally and just upload a txt file containing that hash.
2015-07-27 15:34:45 +02:00
//
// In the view this only happens when the user is allowed to do local hashing as well
2015-10-30 17:43:20 +01:00
// as when the correct method prop is present ('hash' and not 'upload')
if ( this . props . enableLocalHashing && this . props . uploadMethod === 'hash' ) {
const convertedFilePromises = [ ] ;
2015-07-24 14:39:04 +02:00
let overallFileSize = 0 ;
2015-10-30 17:43:20 +01:00
2015-07-23 15:57:25 +02:00
// "files" is not a classical Javascript array but a Javascript FileList, therefore
// we can not use map to convert values
for ( let i = 0 ; i < files . length ; i ++ ) {
2015-07-24 14:39:04 +02:00
// for calculating the overall progress of all submitted files
// we'll need to calculate the overall sum of all files' sizes
overallFileSize += files [ i ] . size ;
// also, we need to set the files' initial progress value
files [ i ] . progress = 0 ;
2015-07-23 15:57:25 +02:00
// since the actual computation of a file's hash is an async task ,
// we're using promises to handle that
let hashedFilePromise = computeHashOfFile ( files [ i ] ) ;
convertedFilePromises . push ( hashedFilePromise ) ;
}
// To react after the computation of all files, we define the resolvement
// with the all function for iterables and essentially replace all original files
// with their txt representative
2015-07-24 13:44:28 +02:00
Q . all ( convertedFilePromises )
2015-07-24 16:29:57 +02:00
. progress ( ( { index , value : { progress , reject } } ) => {
2015-07-24 16:17:27 +02:00
// hashing progress has been aborted from outside
// To get out of the executing, we need to call reject from the
// inside of the promise's execution.
// This is why we're passing (along with value) a function that essentially
// just does that (calling reject(err))
//
// In the promises catch method, we're then checking if the interruption
// was due to that error or another generic one.
if ( this . state . hashingProgress === - 1 ) {
2015-07-24 16:29:57 +02:00
reject ( new Error ( getLangText ( 'Hashing canceled' ) ) ) ;
2015-07-24 16:17:27 +02:00
}
2015-07-24 14:39:04 +02:00
// update file's progress
2015-07-24 16:17:27 +02:00
files [ index ] . progress = progress ;
2015-07-24 14:39:04 +02:00
2015-07-24 15:13:20 +02:00
// calculate weighted average for overall progress of all
// currently hashing files
2015-07-24 14:39:04 +02:00
let overallHashingProgress = 0 ;
for ( let i = 0 ; i < files . length ; i ++ ) {
2015-07-24 16:17:27 +02:00
let filesSliceOfOverall = files [ i ] . size / overallFileSize ;
2015-07-24 14:39:04 +02:00
overallHashingProgress += filesSliceOfOverall * files [ i ] . progress ;
}
2015-07-24 15:13:20 +02:00
// Multiply by 100, since react-progressbar expects decimal numbers
this . setState ( { hashingProgress : overallHashingProgress * 100 } ) ;
2015-07-24 14:39:04 +02:00
} )
2015-07-23 15:57:25 +02:00
. then ( ( convertedFiles ) => {
2015-07-24 15:13:20 +02:00
// clear hashing progress, since its done
2015-07-24 16:17:27 +02:00
this . setState ( { hashingProgress : - 2 } ) ;
2015-07-24 15:13:20 +02:00
2015-07-23 15:57:25 +02:00
// actually replacing all files with their txt-hash representative
files = convertedFiles ;
2015-07-23 16:47:32 +02:00
// routine for adding all the files submitted to fineuploader for actual uploading them
// to the server
this . state . uploader . addFiles ( files ) ;
this . synchronizeFileLists ( files ) ;
2015-07-23 17:05:52 +02:00
2015-07-23 15:57:25 +02:00
} )
. catch ( ( err ) => {
2015-07-24 16:17:27 +02:00
// If the error is that hashing has been canceled, we want to display a success
// message instead of a danger message
let typeOfMessage = 'danger' ;
if ( err . message === getLangText ( 'Hashing canceled' ) ) {
typeOfMessage = 'success' ;
this . setState ( { hashingProgress : - 2 } ) ;
} else {
// if there was a more generic error, we also log it
console . logGlobal ( err ) ;
}
let notification = new GlobalNotificationModel ( err . message , typeOfMessage , 5000 ) ;
2015-07-23 15:57:25 +02:00
GlobalNotificationActions . appendGlobalNotification ( notification ) ;
} ) ;
2015-07-23 15:27:14 +02:00
2015-07-23 16:47:32 +02:00
// if we're not hashing the files locally, we're just going to hand them over to fineuploader
// to upload them to the server
} else {
2015-08-24 14:16:13 +02:00
if ( files . length > 0 ) {
this . state . uploader . addFiles ( files ) ;
this . synchronizeFileLists ( files ) ;
2015-12-08 18:22:11 +01:00
this . setState ( {
uploadInProgress : true
} ) ;
2015-08-24 14:16:13 +02:00
}
2015-07-23 16:47:32 +02:00
}
} ,
2015-07-23 15:57:25 +02:00
2015-07-23 16:47:32 +02:00
// ReactFineUploader is essentially just a react layer around s3 fineuploader.
// However, since we need to display the status of a file (progress, uploading) as well as
// be able to execute actions on a currently uploading file we need to exactly sync the file list
// fineuploader is keeping internally.
//
// Unfortunately though fineuploader is not keeping all of a File object's properties after
// submitting them via .addFiles (it deletes the type, key as well as the ObjectUrl (which we need for
// displaying a thumbnail)), we need to readd them manually after each file that gets submitted
// to the dropzone.
// This method is essentially taking care of all these steps.
synchronizeFileLists ( files ) {
2015-06-23 10:16:53 +02:00
let oldFiles = this . state . filesToUpload ;
let oldAndNewFiles = this . state . uploader . getUploads ( ) ;
2015-09-09 17:21:55 +02:00
2015-06-23 10:16:53 +02:00
// Add fineuploader specific information to new files
for ( let i = 0 ; i < oldAndNewFiles . length ; i ++ ) {
for ( let j = 0 ; j < files . length ; j ++ ) {
if ( oldAndNewFiles [ i ] . originalName === files [ j ] . name ) {
oldAndNewFiles [ i ] . progress = 0 ;
oldAndNewFiles [ i ] . type = files [ j ] . type ;
oldAndNewFiles [ i ] . url = URL . createObjectURL ( files [ j ] ) ;
}
}
}
// and re-add fineuploader specific information for old files as well
for ( let i = 0 ; i < oldAndNewFiles . length ; i ++ ) {
for ( let j = 0 ; j < oldFiles . length ; j ++ ) {
2015-09-09 17:21:55 +02:00
// EXCEPTION:
//
// Files do not necessarily come from the user's hard drive but can also be fetched
// from Amazon S3. This is handled in onSessionRequestComplete.
//
// If the user deletes one of those files, then fineuploader will still keep it in his
// files array but with key, progress undefined and size === -1 but
2015-11-25 16:27:32 +01:00
// status === FileStatus.UPLOAD_SUCCESSFUL.
2015-09-09 17:21:55 +02:00
// This poses a problem as we depend on the amount of files that have
2015-11-25 16:27:32 +01:00
// status === FileStatus.UPLOAD_SUCCESSFUL, therefore once the file is synced,
// we need to tag its status as FileStatus.DELETED (which basically happens here)
2015-09-09 17:21:55 +02:00
if ( oldAndNewFiles [ i ] . size === - 1 && ( ! oldAndNewFiles [ i ] . progress || oldAndNewFiles [ i ] . progress === 0 ) ) {
2015-11-25 16:27:32 +01:00
oldAndNewFiles [ i ] . status = FileStatus . DELETED ;
2015-09-09 17:21:55 +02:00
}
2015-06-23 10:16:53 +02:00
if ( oldAndNewFiles [ i ] . originalName === oldFiles [ j ] . name ) {
2015-06-23 13:55:05 +02:00
oldAndNewFiles [ i ] . progress = oldFiles [ j ] . progress ;
2015-06-23 10:16:53 +02:00
oldAndNewFiles [ i ] . type = oldFiles [ j ] . type ;
oldAndNewFiles [ i ] . url = oldFiles [ j ] . url ;
2015-06-29 16:00:26 +02:00
oldAndNewFiles [ i ] . key = oldFiles [ j ] . key ;
2015-06-23 10:16:53 +02:00
}
}
}
2015-06-29 11:01:45 +02:00
// set the new file array
2015-09-07 12:02:01 +02:00
let filesToUpload = React . addons . update ( this . state . filesToUpload , { $set : oldAndNewFiles } ) ;
2015-11-17 15:46:46 +01:00
this . setState ( { filesToUpload } , ( ) => {
// when files have been dropped or selected by a user, we want to propagate that
// information to the outside components, so they can act on it (in our case, because
// we want the user to define a thumbnail when the actual work is not renderable
// (like e.g. a .zip file))
2015-11-23 17:59:20 +01:00
if ( typeof this . props . handleChangedFile === 'function' ) {
// its save to assume that the last file in `filesToUpload` is always
// the latest file added
this . props . handleChangedFile ( this . state . filesToUpload . slice ( - 1 ) [ 0 ] ) ;
2015-11-17 15:46:46 +01:00
}
} ) ;
2015-06-23 10:16:53 +02:00
} ,
render ( ) {
2015-12-08 18:22:11 +01:00
const { errorState : { errorClass } , filesToUpload , uploadInProgress } = this . state ;
2016-02-08 14:08:54 +01:00
const { areAssetsDownloadable ,
areAssetsEditable ,
enableLocalHashing ,
fileClassToUpload ,
fileInputElement : FileInputElement ,
multiple ,
showErrorPrompt ,
uploadMethod } = this . props ;
2015-12-08 18:09:24 +01:00
// Only show the error state once all files are finished
2015-12-08 20:25:18 +01:00
const showError = ! uploadInProgress && showErrorPrompt && errorClass != null ;
2015-09-15 10:43:45 +02:00
2015-11-13 12:20:52 +01:00
const props = {
2015-09-30 18:30:50 +02:00
multiple ,
areAssetsDownloadable ,
areAssetsEditable ,
enableLocalHashing ,
2015-10-30 17:43:20 +01:00
uploadMethod ,
2015-09-30 18:30:50 +02:00
fileClassToUpload ,
2015-12-08 18:09:24 +01:00
filesToUpload ,
2015-12-08 18:22:11 +01:00
uploadInProgress ,
errorClass ,
showError ,
2015-09-15 10:43:45 +02:00
onDrop : this . handleUploadFile ,
handleDeleteFile : this . handleDeleteFile ,
handleCancelFile : this . handleCancelFile ,
handlePauseFile : this . handlePauseFile ,
handleResumeFile : this . handleResumeFile ,
2015-12-08 18:22:11 +01:00
handleRetryFiles : this . handleRetryFiles ,
2015-09-15 10:43:45 +02:00
handleCancelHashing : this . handleCancelHashing ,
dropzoneInactive : this . isDropzoneInactive ( ) ,
hashingProgress : this . state . hashingProgress ,
2015-09-15 16:35:45 +02:00
allowedExtensions : this . getAllowedExtensions ( )
2015-11-13 12:20:52 +01:00
} ;
2015-06-23 10:16:53 +02:00
2015-11-13 12:20:52 +01:00
return (
< FileInputElement
ref = "fileInput"
{ ... props } / >
) ;
}
2015-06-23 10:16:53 +02:00
} ) ;
export default ReactS3FineUploader ;