mirror of
https://github.com/ascribe/onion.git
synced 2024-11-15 09:35:10 +01:00
Merge remote-tracking branch 'origin/AD-435-hash-locally-for-when-a-artist-do' into AD-435-hash-locally-for-when-a-artist-do
This commit is contained in:
commit
b105e77009
@ -34,7 +34,12 @@ let SlidesContainer = React.createClass({
|
||||
// check if slide_num was defined, and if not then default to 0
|
||||
let queryParams = this.getQuery();
|
||||
if(!('slide_num' in queryParams)) {
|
||||
this.replaceWith(this.getPathname(), null, {slide_num: 0});
|
||||
|
||||
// we're first requiring all the other possible queryParams and then set
|
||||
// the specific one we need instead of overwriting them
|
||||
queryParams.slide_num = 0;
|
||||
|
||||
this.replaceWith(this.getPathname(), null, queryParams);
|
||||
}
|
||||
|
||||
// init container width
|
||||
@ -60,7 +65,12 @@ let SlidesContainer = React.createClass({
|
||||
setSlideNum(slideNum) {
|
||||
if(slideNum < 0 || slideNum < React.Children.count(this.props.children)) {
|
||||
|
||||
this.replaceWith(this.getPathname(), null, {slide_num: slideNum});
|
||||
// we're first requiring all the other possible queryParams in order to not
|
||||
// delete some of them that might be needed by other components
|
||||
let queryParams = this.getQuery();
|
||||
queryParams.slide_num = slideNum;
|
||||
|
||||
this.replaceWith(this.getPathname(), null, queryParams);
|
||||
this.setState({
|
||||
slideNum: slideNum
|
||||
});
|
||||
|
@ -1,12 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import ProgressBar from 'react-progressbar';
|
||||
|
||||
import FileDragAndDropDialog from './file_drag_and_drop_dialog';
|
||||
import FileDragAndDropPreviewIterator from './file_drag_and_drop_preview_iterator';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
// Taken from: https://github.com/fedosejev/react-file-drag-and-drop
|
||||
@ -31,8 +30,13 @@ let FileDragAndDrop = React.createClass({
|
||||
areAssetsDownloadable: React.PropTypes.bool,
|
||||
areAssetsEditable: React.PropTypes.bool,
|
||||
|
||||
localHashing: React.PropTypes.bool,
|
||||
|
||||
// triggers a FileDragAndDrop-global spinner
|
||||
hashingProgress: React.PropTypes.number
|
||||
hashingProgress: React.PropTypes.number,
|
||||
// sets the value of this.state.hashingProgress in reactfineuploader
|
||||
// to -1 which is code for: aborted
|
||||
handleCancelHashing: React.PropTypes.func
|
||||
},
|
||||
|
||||
handleDragStart(event) {
|
||||
@ -146,16 +150,16 @@ let FileDragAndDrop = React.createClass({
|
||||
className += this.props.dropzoneInactive ? 'inactive-dropzone' : 'active-dropzone';
|
||||
className += this.props.className ? ' ' + this.props.className : '';
|
||||
|
||||
// if !== -1: triggers a FileDragAndDrop-global spinner
|
||||
if(this.props.hashingProgress !== -1) {
|
||||
// if !== -2: triggers a FileDragAndDrop-global spinner
|
||||
if(this.props.hashingProgress !== -2) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<p>{getLangText('Computing hashes... This may take a few minutes.')}</p>
|
||||
<p>{this.props.hashingProgress}</p>
|
||||
<img
|
||||
height={35}
|
||||
className="action-file"
|
||||
src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
||||
<p>{getLangText('Computing hash(es)... This may take a few minutes.')}</p>
|
||||
<p>
|
||||
<span>{Math.ceil(this.props.hashingProgress)}%</span>
|
||||
<span onClick={this.props.handleCancelHashing}> {getLangText('Cancel hashing')}</span>
|
||||
</p>
|
||||
<ProgressBar completed={this.props.hashingProgress} color="#48DACB"/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
@ -172,7 +176,8 @@ let FileDragAndDrop = React.createClass({
|
||||
<FileDragAndDropDialog
|
||||
multipleFiles={this.props.multiple}
|
||||
hasFiles={hasFiles}
|
||||
onClick={this.handleOnClick}/>
|
||||
onClick={this.handleOnClick}
|
||||
localHashing={this.props.localHashing}/>
|
||||
<FileDragAndDropPreviewIterator
|
||||
files={this.props.filesToUpload}
|
||||
handleDeleteFile={this.handleDeleteFile}
|
||||
|
@ -1,38 +1,82 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import ButtonLink from 'react-router-bootstrap/lib/ButtonLink';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
let FileDragAndDropDialog = React.createClass({
|
||||
propTypes: {
|
||||
hasFiles: React.PropTypes.bool,
|
||||
multipleFiles: React.PropTypes.bool,
|
||||
onClick: React.PropTypes.func
|
||||
onClick: React.PropTypes.func,
|
||||
localHashing: React.PropTypes.bool
|
||||
},
|
||||
|
||||
mixins: [Router.State],
|
||||
|
||||
render() {
|
||||
const queryParams = this.getQuery();
|
||||
|
||||
if(this.props.hasFiles) {
|
||||
return null;
|
||||
} else {
|
||||
if(this.props.multipleFiles) {
|
||||
if(!queryParams.method) {
|
||||
|
||||
let queryParamsHash = Object.assign({}, queryParams);
|
||||
queryParamsHash.method = 'hash';
|
||||
|
||||
let queryParamsUpload = Object.assign({}, queryParams);
|
||||
queryParamsUpload.method = 'upload';
|
||||
|
||||
return (
|
||||
<span className="file-drag-and-drop-dialog">
|
||||
{getLangText('Click or drag to add files')}
|
||||
<span className="file-drag-and-drop-dialog present-options">
|
||||
<p>{getLangText('Would you rather')}</p>
|
||||
<Link
|
||||
to={this.getPath()}
|
||||
query={queryParamsHash}>
|
||||
<span className="btn btn-default btn-sm">
|
||||
{getLangText('Hash your work')}
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<span> or </span>
|
||||
|
||||
<Link
|
||||
to={this.getPath()}
|
||||
query={queryParamsUpload}>
|
||||
<span className="btn btn-default btn-sm">
|
||||
{getLangText('Upload and hash your work')}
|
||||
</span>
|
||||
</Link>
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span className="file-drag-and-drop-dialog">
|
||||
<p>{getLangText('Drag a file here')}</p>
|
||||
<p>{getLangText('or')}</p>
|
||||
<span
|
||||
className="btn btn-default"
|
||||
onClick={this.props.onClick}>
|
||||
{getLangText('choose a file to upload')}
|
||||
if(this.props.multipleFiles) {
|
||||
return (
|
||||
<span className="file-drag-and-drop-dialog">
|
||||
{getLangText('Click or drag to add files')}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
);
|
||||
} else {
|
||||
let dialog = queryParams.method === 'hash' ? getLangText('choose a file to hash') : getLangText('choose a file to upload');
|
||||
|
||||
return (
|
||||
<span className="file-drag-and-drop-dialog">
|
||||
<p>{getLangText('Drag a file here')}</p>
|
||||
<p>{getLangText('or')}</p>
|
||||
<span
|
||||
className="btn btn-default"
|
||||
onClick={this.props.onClick}>
|
||||
{dialog}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ var ReactS3FineUploader = React.createClass({
|
||||
}
|
||||
return name;
|
||||
},
|
||||
multiple: true,
|
||||
multiple: false,
|
||||
defaultErrorMessage: getLangText('Unexpected error. Please contact us if this happens repeatedly.')
|
||||
};
|
||||
},
|
||||
@ -161,7 +161,9 @@ var ReactS3FineUploader = React.createClass({
|
||||
filesToUpload: [],
|
||||
uploader: new fineUploader.s3.FineUploaderBasic(this.propsToConfig()),
|
||||
csrfToken: getCookie(AppConstants.csrftoken),
|
||||
hashingProgress: -1 // for hashing feedback
|
||||
hashingProgress: -2
|
||||
// -1: aborted
|
||||
// -2: uninitialized
|
||||
};
|
||||
},
|
||||
|
||||
@ -219,111 +221,123 @@ var ReactS3FineUploader = React.createClass({
|
||||
},
|
||||
|
||||
requestKey(fileId) {
|
||||
let defer = new fineUploader.Promise();
|
||||
let filename = this.state.uploader.getName(fileId);
|
||||
let uuid = this.state.uploader.getUuid(fileId);
|
||||
|
||||
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
|
||||
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();
|
||||
})
|
||||
.then((res) =>{
|
||||
defer.success(res.key);
|
||||
})
|
||||
.catch((err) => {
|
||||
defer.failure(err);
|
||||
.then((res) => {
|
||||
return res.json();
|
||||
})
|
||||
.then((res) =>{
|
||||
resolve(res.key);
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
return defer;
|
||||
},
|
||||
|
||||
createBlob(file) {
|
||||
let defer = new fineUploader.Promise();
|
||||
|
||||
window.fetch(this.props.createBlobRoutine.url, {
|
||||
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,
|
||||
'piece_id': this.props.createBlobRoutine.pieceId
|
||||
return Q.Promise((resolve, reject) => {
|
||||
window.fetch(this.props.createBlobRoutine.url, {
|
||||
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,
|
||||
'piece_id': this.props.createBlobRoutine.pieceId
|
||||
})
|
||||
})
|
||||
})
|
||||
.then((res) => {
|
||||
return res.json();
|
||||
})
|
||||
.then((res) =>{
|
||||
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;
|
||||
} else {
|
||||
throw new Error(getLangText('Could not find a url to download.'));
|
||||
}
|
||||
defer.success(res.key);
|
||||
})
|
||||
.catch((err) => {
|
||||
defer.failure(err);
|
||||
console.logGlobal(err);
|
||||
.then((res) => {
|
||||
return res.json();
|
||||
})
|
||||
.then((res) =>{
|
||||
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;
|
||||
} else {
|
||||
throw new Error(getLangText('Could not find a url to download.'));
|
||||
}
|
||||
resolve(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
console.logGlobal(err);
|
||||
});
|
||||
});
|
||||
return defer;
|
||||
},
|
||||
|
||||
/* FineUploader specific callback function handlers */
|
||||
|
||||
onComplete(id) {
|
||||
let files = this.state.filesToUpload;
|
||||
|
||||
// Set the state of the completed file to 'upload successful' in order to
|
||||
// remove it from the GUI
|
||||
files[id].status = 'upload successful';
|
||||
files[id].key = this.state.uploader.getKey(id);
|
||||
|
||||
let newState = React.addons.update(this.state, {
|
||||
filesToUpload: { $set: files }
|
||||
});
|
||||
|
||||
this.setState(newState);
|
||||
this.createBlob(files[id]);
|
||||
|
||||
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey
|
||||
// are optional, we'll only trigger them when they're actually defined
|
||||
if(this.props.submitKey) {
|
||||
this.props.submitKey(files[id].key);
|
||||
} else {
|
||||
console.warn('You didn\'t define submitKey in as a prop in react-s3-fine-uploader');
|
||||
}
|
||||
// Only after the blob has been created server-side, we can make the form submittable.
|
||||
this.createBlob(files[id])
|
||||
.then(() => {
|
||||
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey
|
||||
// are optional, we'll only trigger them when they're actually defined
|
||||
if(this.props.submitKey) {
|
||||
this.props.submitKey(files[id].key);
|
||||
} else {
|
||||
console.warn('You didn\'t define submitKey in as a prop in react-s3-fine-uploader');
|
||||
}
|
||||
|
||||
// for explanation, check comment of if statement above
|
||||
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((err) => {
|
||||
console.logGlobal(err);
|
||||
let notification = new GlobalNotificationModel(err.message, 'danger', 5000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
});
|
||||
|
||||
|
||||
// for explanation, check comment of if statement above
|
||||
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');
|
||||
}
|
||||
},
|
||||
|
||||
onError() {
|
||||
@ -524,23 +538,42 @@ var ReactS3FineUploader = React.createClass({
|
||||
// with the all function for iterables and essentially replace all original files
|
||||
// with their txt representative
|
||||
Q.all(convertedFilePromises)
|
||||
.progress(({index, value}) => {
|
||||
// update file's progress
|
||||
files[index].progress = value;
|
||||
.progress(({index, value: {progress, reject}}) => {
|
||||
|
||||
// calculate overall progress
|
||||
let overallHashingProgress = 0;
|
||||
for(let i = 0; i < files.length; i++) {
|
||||
let filesSliceOfOverall = files[i].size / overallFileSize;
|
||||
|
||||
overallHashingProgress += filesSliceOfOverall * files[i].progress;
|
||||
// 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) {
|
||||
reject(new Error(getLangText('Hashing canceled')));
|
||||
}
|
||||
|
||||
this.setState({ hashingProgress: overallHashingProgress });
|
||||
// update file's progress
|
||||
files[index].progress = progress;
|
||||
|
||||
// calculate weighted average for overall progress of all
|
||||
// currently hashing files
|
||||
let overallHashingProgress = 0;
|
||||
for(let i = 0; i < files.length; i++) {
|
||||
|
||||
let filesSliceOfOverall = files[i].size / overallFileSize;
|
||||
overallHashingProgress += filesSliceOfOverall * files[i].progress;
|
||||
|
||||
}
|
||||
|
||||
// Multiply by 100, since react-progressbar expects decimal numbers
|
||||
this.setState({ hashingProgress: overallHashingProgress * 100});
|
||||
|
||||
})
|
||||
.then((convertedFiles) => {
|
||||
|
||||
// clear hashing progress, since its done
|
||||
this.setState({ hashingProgress: -2});
|
||||
|
||||
// actually replacing all files with their txt-hash representative
|
||||
files = convertedFiles;
|
||||
|
||||
@ -551,9 +584,19 @@ var ReactS3FineUploader = React.createClass({
|
||||
|
||||
})
|
||||
.catch((err) => {
|
||||
// if we're running into an error during the hash creation, we'll tell the user
|
||||
console.logGlobal(err);
|
||||
let notification = new GlobalNotificationModel(err.message, 'danger', 5000);
|
||||
// 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);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
});
|
||||
|
||||
@ -565,6 +608,13 @@ var ReactS3FineUploader = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
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 });
|
||||
},
|
||||
|
||||
// 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
|
||||
@ -648,11 +698,13 @@ var ReactS3FineUploader = React.createClass({
|
||||
handleCancelFile={this.handleCancelFile}
|
||||
handlePauseFile={this.handlePauseFile}
|
||||
handleResumeFile={this.handleResumeFile}
|
||||
handleCancelHashing={this.handleCancelHashing}
|
||||
multiple={this.props.multiple}
|
||||
areAssetsDownloadable={this.props.areAssetsDownloadable}
|
||||
areAssetsEditable={this.props.areAssetsEditable}
|
||||
dropzoneInactive={!this.props.areAssetsEditable || !this.props.multiple && this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1).length > 0}
|
||||
hashingProgress={this.state.hashingProgress} />
|
||||
hashingProgress={this.state.hashingProgress}
|
||||
localHashing={this.props.localHashing} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -69,7 +69,15 @@ export function computeHashOfFile(file) {
|
||||
end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
|
||||
|
||||
// send progress
|
||||
notify(start / file.size);
|
||||
// Due to the fact that progressHandler and notify are going to be removed in v2
|
||||
// of Q, the functionality of throwing errors in the progressHandler will not be implemented
|
||||
// anymore. To still be able to throw an error however, we can just expose the promise's reject
|
||||
// method to the .progress function to stop the execution immediately.
|
||||
notify({
|
||||
progress: start / file.size,
|
||||
reject
|
||||
});
|
||||
|
||||
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
cursor: default !important;
|
||||
|
||||
padding: 1.5em 1.5em 1.5em 0;
|
||||
padding: 1.5em 0 1.5em 0;
|
||||
}
|
||||
|
||||
.inactive-dropzone {
|
||||
@ -19,6 +19,16 @@
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.present-options {
|
||||
> p {
|
||||
margin-bottom: .75em !important;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin: 0 1em 0 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.file-drag-and-drop .file-drag-and-drop-dialog > p:first-child {
|
||||
font-size: 1.5em !important;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user