1
0
mirror of https://github.com/ascribe/onion.git synced 2024-11-15 01:25:17 +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:
diminator 2015-07-27 14:39:42 +02:00
commit b105e77009
6 changed files with 256 additions and 127 deletions

View File

@ -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
});

View File

@ -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}

View File

@ -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>
);
}
}
}
}

View File

@ -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');
}
this.setState(newState);
// 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>
);
}

View File

@ -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));
}

View File

@ -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;