1
0
mirror of https://github.com/ascribe/onion.git synced 2024-06-30 13:41:57 +02:00

Merge with AD-1264

This commit is contained in:
Brett Sun 2015-11-03 11:34:48 +01:00
commit cd6673f255
19 changed files with 212 additions and 263 deletions

View File

@ -21,8 +21,7 @@ let FurtherDetailsFileuploader = React.createClass({
submitFile: React.PropTypes.func, submitFile: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func, isReadyForFormSubmission: React.PropTypes.func,
editable: React.PropTypes.bool, editable: React.PropTypes.bool,
multiple: React.PropTypes.bool, multiple: React.PropTypes.bool
location: React.PropTypes.object
}, },
getDefaultProps() { getDefaultProps() {
@ -46,6 +45,7 @@ let FurtherDetailsFileuploader = React.createClass({
return ( return (
<Property <Property
name="other_data_key"
label={this.props.label}> label={this.props.label}>
<ReactS3FineUploader <ReactS3FineUploader
uploadStarted={this.props.uploadStarted} uploadStarted={this.props.uploadStarted}
@ -91,8 +91,7 @@ let FurtherDetailsFileuploader = React.createClass({
}} }}
areAssetsDownloadable={true} areAssetsDownloadable={true}
areAssetsEditable={this.props.editable} areAssetsEditable={this.props.editable}
multiple={this.props.multiple} multiple={this.props.multiple} />
location={this.props.location}/>
</Property> </Property>
); );
} }

View File

@ -12,8 +12,6 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
import requests from '../../utils/requests'; import requests from '../../utils/requests';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
import { mergeOptionsWithDuplicates } from '../../utils/general_utils';
let Form = React.createClass({ let Form = React.createClass({
propTypes: { propTypes: {
@ -124,12 +122,12 @@ let Form = React.createClass({
getFormData() { getFormData() {
let data = {}; let data = {};
for(let ref in this.refs) { for (let ref in this.refs) {
data[this.refs[ref].props.name] = this.refs[ref].state.value; data[this.refs[ref].props.name] = this.refs[ref].state.value;
} }
if(typeof this.props.getFormData === 'function') { if (typeof this.props.getFormData === 'function') {
data = mergeOptionsWithDuplicates(data, this.props.getFormData()); data = Object.assign(data, this.props.getFormData());
} }
return data; return data;

View File

@ -28,8 +28,7 @@ let CreateContractForm = React.createClass({
fileClassToUpload: React.PropTypes.shape({ fileClassToUpload: React.PropTypes.shape({
singular: React.PropTypes.string, singular: React.PropTypes.string,
plural: React.PropTypes.string plural: React.PropTypes.string
}), })
location: React.PropTypes.object
}, },
getInitialState() { getInitialState() {
@ -87,8 +86,7 @@ let CreateContractForm = React.createClass({
areAssetsEditable={true} areAssetsEditable={true}
setIsUploadReady={this.setIsUploadReady} setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile} isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
fileClassToUpload={this.props.fileClassToUpload} fileClassToUpload={this.props.fileClassToUpload} />
location={this.props.location}/>
</Property> </Property>
<Property <Property
name='name' name='name'
@ -110,4 +108,4 @@ let CreateContractForm = React.createClass({
} }
}); });
export default CreateContractForm; export default CreateContractForm;

View File

@ -26,12 +26,15 @@ let RegisterPieceForm = React.createClass({
isFineUploaderActive: React.PropTypes.bool, isFineUploaderActive: React.PropTypes.bool,
isFineUploaderEditable: React.PropTypes.bool, isFineUploaderEditable: React.PropTypes.bool,
enableLocalHashing: React.PropTypes.bool, enableLocalHashing: React.PropTypes.bool,
children: React.PropTypes.element,
onLoggedOut: React.PropTypes.func, onLoggedOut: React.PropTypes.func,
// For this form to work with SlideContainer, we sometimes have to disable it // For this form to work with SlideContainer, we sometimes have to disable it
disabled: React.PropTypes.bool, disabled: React.PropTypes.bool,
location: React.PropTypes.object location: React.PropTypes.object,
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
])
}, },
getDefaultProps() { getDefaultProps() {
@ -116,7 +119,7 @@ let RegisterPieceForm = React.createClass({
onLoggedOut={this.props.onLoggedOut} onLoggedOut={this.props.onLoggedOut}
disabled={!this.props.isFineUploaderEditable} disabled={!this.props.isFineUploaderEditable}
enableLocalHashing={enableLocalHashing} enableLocalHashing={enableLocalHashing}
location={this.props.location}/> uploadMethod={this.props.location.query.method} />
</Property> </Property>
<Property <Property
name='artist_name' name='artist_name'

View File

@ -37,6 +37,7 @@ let InputFineUploader = React.createClass({
onLoggedOut: React.PropTypes.func, onLoggedOut: React.PropTypes.func,
enableLocalHashing: React.PropTypes.bool, enableLocalHashing: React.PropTypes.bool,
uploadMethod: React.PropTypes.string,
// provided by Property // provided by Property
disabled: React.PropTypes.bool, disabled: React.PropTypes.bool,
@ -46,8 +47,7 @@ let InputFineUploader = React.createClass({
fileClassToUpload: React.PropTypes.shape({ fileClassToUpload: React.PropTypes.shape({
singular: React.PropTypes.string, singular: React.PropTypes.string,
plural: React.PropTypes.string plural: React.PropTypes.string
}), })
location: React.PropTypes.object
}, },
getInitialState() { getInitialState() {
@ -107,10 +107,10 @@ let InputFineUploader = React.createClass({
}} }}
onInactive={this.props.onLoggedOut} onInactive={this.props.onLoggedOut}
enableLocalHashing={this.props.enableLocalHashing} enableLocalHashing={this.props.enableLocalHashing}
fileClassToUpload={this.props.fileClassToUpload} uploadMethod={this.props.uploadMethod}
location={this.props.location}/> fileClassToUpload={this.props.fileClassToUpload} />
); );
} }
}); });
export default InputFineUploader; export default InputFineUploader;

View File

@ -20,8 +20,7 @@ import { getLangText } from '../../utils/lang_utils';
let ContractSettingsUpdateButton = React.createClass({ let ContractSettingsUpdateButton = React.createClass({
propTypes: { propTypes: {
contract: React.PropTypes.object, contract: React.PropTypes.object
location: React.PropTypes.object
}, },
submitFile(file) { submitFile(file) {
@ -90,10 +89,9 @@ let ContractSettingsUpdateButton = React.createClass({
plural: getLangText('UPDATE') plural: getLangText('UPDATE')
}} }}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile} isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
submitFile={this.submitFile} submitFile={this.submitFile} />
location={this.props.location}/>
); );
} }
}); });
export default ContractSettingsUpdateButton; export default ContractSettingsUpdateButton;

View File

@ -27,6 +27,7 @@ let FileDragAndDrop = React.createClass({
areAssetsEditable: React.PropTypes.bool, areAssetsEditable: React.PropTypes.bool,
enableLocalHashing: React.PropTypes.bool, enableLocalHashing: React.PropTypes.bool,
uploadMethod: React.PropTypes.string,
// triggers a FileDragAndDrop-global spinner // triggers a FileDragAndDrop-global spinner
hashingProgress: React.PropTypes.number, hashingProgress: React.PropTypes.number,
@ -41,8 +42,7 @@ let FileDragAndDrop = React.createClass({
plural: React.PropTypes.string plural: React.PropTypes.string
}), }),
allowedExtensions: React.PropTypes.string, allowedExtensions: React.PropTypes.string
location: React.PropTypes.object
}, },
handleDragOver(event) { handleDragOver(event) {
@ -137,19 +137,19 @@ let FileDragAndDrop = React.createClass({
}, },
render: function () { render: function () {
let { filesToUpload, const {
dropzoneInactive, filesToUpload,
className, dropzoneInactive,
hashingProgress, className,
handleCancelHashing, hashingProgress,
multiple, handleCancelHashing,
enableLocalHashing, multiple,
fileClassToUpload, enableLocalHashing,
areAssetsDownloadable, uploadMethod,
areAssetsEditable, fileClassToUpload,
allowedExtensions, areAssetsDownloadable,
location areAssetsEditable,
} = this.props; allowedExtensions } = this.props;
// has files only is true if there are files that do not have the status deleted or canceled // has files only is true if there are files that do not have the status deleted or canceled
let hasFiles = filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1).length > 0; let hasFiles = filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1).length > 0;
@ -185,8 +185,8 @@ let FileDragAndDrop = React.createClass({
hasFiles={hasFiles} hasFiles={hasFiles}
onClick={this.handleOnClick} onClick={this.handleOnClick}
enableLocalHashing={enableLocalHashing} enableLocalHashing={enableLocalHashing}
fileClassToUpload={fileClassToUpload} uploadMethod={uploadMethod}
location={location}/> fileClassToUpload={fileClassToUpload} />
<FileDragAndDropPreviewIterator <FileDragAndDropPreviewIterator
files={filesToUpload} files={filesToUpload}
handleDeleteFile={this.handleDeleteFile} handleDeleteFile={this.handleDeleteFile}

View File

@ -3,30 +3,28 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { getLangText } from '../../../utils/lang_utils';
import { dragAndDropAvailable } from '../../../utils/feature_detection_utils'; import { dragAndDropAvailable } from '../../../utils/feature_detection_utils';
import { getLangText } from '../../../utils/lang_utils';
import { getCurrentQueryParams } from '../../../utils/url_utils';
let FileDragAndDropDialog = React.createClass({ let FileDragAndDropDialog = React.createClass({
propTypes: { propTypes: {
hasFiles: React.PropTypes.bool, hasFiles: React.PropTypes.bool,
multipleFiles: React.PropTypes.bool, multipleFiles: React.PropTypes.bool,
onClick: React.PropTypes.func,
enableLocalHashing: React.PropTypes.bool, enableLocalHashing: React.PropTypes.bool,
uploadMethod: React.PropTypes.string,
onClick: React.PropTypes.func,
// 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({
singular: React.PropTypes.string, singular: React.PropTypes.string,
plural: React.PropTypes.string plural: React.PropTypes.string
}), })
location: React.PropTypes.object
}, },
getDragDialog(fileClass) { getDragDialog(fileClass) {
if(dragAndDropAvailable) { if (dragAndDropAvailable) {
return [ return [
<p>{getLangText('Drag %s here', fileClass)}</p>, <p>{getLangText('Drag %s here', fileClass)}</p>,
<p>{getLangText('or')}</p> <p>{getLangText('or')}</p>
@ -37,26 +35,31 @@ let FileDragAndDropDialog = React.createClass({
}, },
render() { render() {
const queryParams = this.props.location.query; const {
hasFiles,
multipleFiles,
enableLocalHashing,
uploadMethod,
fileClassToUpload,
onClick } = this.props;
if(this.props.hasFiles) { if (hasFiles) {
return null; return null;
} else { } else {
if(this.props.enableLocalHashing && !queryParams.method) { if (enableLocalHashing && !uploadMethod) {
const currentQueryParams = getCurrentQueryParams();
let queryParamsHash = Object.assign({}, queryParams); const queryParamsHash = Object.assign({}, currentQueryParams);
queryParamsHash.method = 'hash'; queryParamsHash.method = 'hash';
let queryParamsUpload = Object.assign({}, queryParams); const queryParamsUpload = Object.assign({}, currentQueryParams);
queryParamsUpload.method = 'upload'; queryParamsUpload.method = 'upload';
let { location } = this.props;
return ( return (
<div className="file-drag-and-drop-dialog present-options"> <div className="file-drag-and-drop-dialog present-options">
<p>{getLangText('Would you rather')}</p> <p>{getLangText('Would you rather')}</p>
<Link <Link
to={location.pathname} to={window.location.pathname}
query={queryParamsHash}> query={queryParamsHash}>
<span className="btn btn-default btn-sm"> <span className="btn btn-default btn-sm">
{getLangText('Hash your work')} {getLangText('Hash your work')}
@ -64,9 +67,9 @@ let FileDragAndDropDialog = React.createClass({
</Link> </Link>
<span> or </span> <span> or </span>
<Link <Link
to={location.pathname} to={window.location.pathname}
query={queryParamsUpload}> query={queryParamsUpload}>
<span className="btn btn-default btn-sm"> <span className="btn btn-default btn-sm">
{getLangText('Upload and hash your work')} {getLangText('Upload and hash your work')}
@ -75,26 +78,27 @@ let FileDragAndDropDialog = React.createClass({
</div> </div>
); );
} else { } else {
if(this.props.multipleFiles) { if (multipleFiles) {
return ( return (
<span className="file-drag-and-drop-dialog"> <span className="file-drag-and-drop-dialog">
{this.getDragDialog(this.props.fileClassToUpload.plural)} {this.getDragDialog(fileClassToUpload.plural)}
<span <span
className="btn btn-default" className="btn btn-default"
onClick={this.props.onClick}> onClick={onClick}>
{getLangText('choose %s to upload', this.props.fileClassToUpload.plural)} {getLangText('choose %s to upload', fileClassToUpload.plural)}
</span> </span>
</span> </span>
); );
} else { } else {
let dialog = queryParams.method === 'hash' ? getLangText('choose a %s to hash', this.props.fileClassToUpload.singular) : getLangText('choose a %s to upload', this.props.fileClassToUpload.singular); const dialog = uploadMethod === 'hash' ? getLangText('choose a %s to hash', fileClassToUpload.singular)
: getLangText('choose a %s to upload', fileClassToUpload.singular);
return ( return (
<span className="file-drag-and-drop-dialog"> <span className="file-drag-and-drop-dialog">
{this.getDragDialog(this.props.fileClassToUpload.singular)} {this.getDragDialog(fileClassToUpload.singular)}
<span <span
className="btn btn-default" className="btn btn-default"
onClick={this.props.onClick}> onClick={onClick}>
{dialog} {dialog}
</span> </span>
</span> </span>
@ -105,4 +109,4 @@ let FileDragAndDropDialog = React.createClass({
} }
}); });
export default FileDragAndDropDialog; export default FileDragAndDropDialog;

View File

@ -18,7 +18,6 @@ import { displayValidFilesFilter, transformAllowedExtensionsToInputAcceptProp }
import { getCookie } from '../../utils/fetch_api_utils'; import { getCookie } from '../../utils/fetch_api_utils';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
let ReactS3FineUploader = React.createClass({ let ReactS3FineUploader = React.createClass({
propTypes: { propTypes: {
keyRoutine: React.PropTypes.shape({ keyRoutine: React.PropTypes.shape({
@ -107,11 +106,14 @@ let ReactS3FineUploader = React.createClass({
// One solution we found in the process of tackling this problem was to hash // 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 // the file in the browser using md5 and then uploading the resulting text document instead
// of the actual file. // of the actual file.
// This boolean essentially enables that behavior //
// 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: React.PropTypes.bool, enableLocalHashing: React.PropTypes.bool,
uploadMethod: React.PropTypes.string,
// automatically injected by React-Router
query: React.PropTypes.object,
// 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
@ -126,9 +128,7 @@ let ReactS3FineUploader = React.createClass({
fileInputElement: React.PropTypes.oneOfType([ fileInputElement: React.PropTypes.oneOfType([
React.PropTypes.func, React.PropTypes.func,
React.PropTypes.element React.PropTypes.element
]), ])
location: React.PropTypes.object
}, },
getDefaultProps() { getDefaultProps() {
@ -192,17 +192,17 @@ let ReactS3FineUploader = React.createClass({
filesToUpload: [], filesToUpload: [],
uploader: new fineUploader.s3.FineUploaderBasic(this.propsToConfig()), uploader: new fineUploader.s3.FineUploaderBasic(this.propsToConfig()),
csrfToken: getCookie(AppConstants.csrftoken), csrfToken: getCookie(AppConstants.csrftoken),
// -1: aborted // -1: aborted
// -2: uninitialized // -2: uninitialized
hashingProgress: -2, hashingProgress: -2,
// this is for logging // this is for logging
chunks: {} chunks: {}
}; };
}, },
componentWillUpdate() { componentWillReceiveProps() {
// since the csrf header is defined in this component's props, // since the csrf header is defined in this component's props,
// everytime the csrf cookie is changed we'll need to reinitalize // everytime the csrf cookie is changed we'll need to reinitalize
// fineuploader and update the actual csrf token // fineuploader and update the actual csrf token
@ -354,7 +354,6 @@ let ReactS3FineUploader = React.createClass({
/* FineUploader specific callback function handlers */ /* FineUploader specific callback function handlers */
onUploadChunk(id, name, chunkData) { onUploadChunk(id, name, chunkData) {
let chunks = this.state.chunks; let chunks = this.state.chunks;
chunks[id + '-' + chunkData.startByte + '-' + chunkData.endByte] = { chunks[id + '-' + chunkData.startByte + '-' + chunkData.endByte] = {
@ -370,10 +369,9 @@ let ReactS3FineUploader = React.createClass({
}, },
onUploadChunkSuccess(id, chunkData, responseJson, xhr) { onUploadChunkSuccess(id, chunkData, responseJson, xhr) {
let chunks = this.state.chunks; let chunks = this.state.chunks;
let chunkKey = id + '-' + chunkData.startByte + '-' + chunkData.endByte; let chunkKey = id + '-' + chunkData.startByte + '-' + chunkData.endByte;
if(chunks[chunkKey]) { if(chunks[chunkKey]) {
chunks[chunkKey].completed = true; chunks[chunkKey].completed = true;
chunks[chunkKey].responseJson = responseJson; chunks[chunkKey].responseJson = responseJson;
@ -414,7 +412,7 @@ let ReactS3FineUploader = React.createClass({
} else { } else {
console.warn('You didn\'t define submitFile in as a prop in react-s3-fine-uploader'); console.warn('You didn\'t define submitFile in as a prop in react-s3-fine-uploader');
} }
// for explanation, check comment of if statement above // for explanation, check comment of if statement above
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) { if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
// also, lets check if after the completion of this upload, // also, lets check if after the completion of this upload,
@ -597,7 +595,6 @@ let ReactS3FineUploader = React.createClass({
} else { } else {
throw new Error(getLangText('File upload could not be paused.')); throw new Error(getLangText('File upload could not be paused.'));
} }
}, },
handleResumeFile(fileId) { handleResumeFile(fileId) {
@ -647,16 +644,14 @@ let ReactS3FineUploader = React.createClass({
// md5 hash of a file locally and just upload a txt file containing that hash. // md5 hash of a file locally and just upload a txt file containing that hash.
// //
// In the view this only happens when the user is allowed to do local hashing as well // In the view this only happens when the user is allowed to do local hashing as well
// as when the correct query parameter is present in the url ('hash' and not 'upload') // as when the correct method prop is present ('hash' and not 'upload')
let queryParams = this.props.location.query; if (this.props.enableLocalHashing && this.props.uploadMethod === 'hash') {
if(this.props.enableLocalHashing && queryParams && queryParams.method === 'hash') { const convertedFilePromises = [];
let convertedFilePromises = [];
let overallFileSize = 0; let overallFileSize = 0;
// "files" is not a classical Javascript array but a Javascript FileList, therefore // "files" is not a classical Javascript array but a Javascript FileList, therefore
// we can not use map to convert values // we can not use map to convert values
for(let i = 0; i < files.length; i++) { for(let i = 0; i < files.length; i++) {
// for calculating the overall progress of all submitted files // for calculating the overall progress of all submitted files
// we'll need to calculate the overall sum of all files' sizes // we'll need to calculate the overall sum of all files' sizes
overallFileSize += files[i].size; overallFileSize += files[i].size;
@ -668,7 +663,6 @@ let ReactS3FineUploader = React.createClass({
// we're using promises to handle that // we're using promises to handle that
let hashedFilePromise = computeHashOfFile(files[i]); let hashedFilePromise = computeHashOfFile(files[i]);
convertedFilePromises.push(hashedFilePromise); convertedFilePromises.push(hashedFilePromise);
} }
// To react after the computation of all files, we define the resolvement // To react after the computation of all files, we define the resolvement
@ -676,7 +670,6 @@ let ReactS3FineUploader = React.createClass({
// with their txt representative // with their txt representative
Q.all(convertedFilePromises) Q.all(convertedFilePromises)
.progress(({index, value: {progress, reject}}) => { .progress(({index, value: {progress, reject}}) => {
// hashing progress has been aborted from outside // hashing progress has been aborted from outside
// To get out of the executing, we need to call reject from the // To get out of the executing, we need to call reject from the
// inside of the promise's execution. // inside of the promise's execution.
@ -696,18 +689,14 @@ let ReactS3FineUploader = React.createClass({
// currently hashing files // currently hashing files
let overallHashingProgress = 0; let overallHashingProgress = 0;
for(let i = 0; i < files.length; i++) { for(let i = 0; i < files.length; i++) {
let filesSliceOfOverall = files[i].size / overallFileSize; let filesSliceOfOverall = files[i].size / overallFileSize;
overallHashingProgress += filesSliceOfOverall * files[i].progress; overallHashingProgress += filesSliceOfOverall * files[i].progress;
} }
// Multiply by 100, since react-progressbar expects decimal numbers // Multiply by 100, since react-progressbar expects decimal numbers
this.setState({ hashingProgress: overallHashingProgress * 100}); this.setState({ hashingProgress: overallHashingProgress * 100});
}) })
.then((convertedFiles) => { .then((convertedFiles) => {
// clear hashing progress, since its done // clear hashing progress, since its done
this.setState({ hashingProgress: -2}); this.setState({ hashingProgress: -2});
@ -823,20 +812,18 @@ let ReactS3FineUploader = React.createClass({
changeSet.status = { $set: status }; changeSet.status = { $set: status };
let filesToUpload = React.addons.update(this.state.filesToUpload, { [fileId]: changeSet }); let filesToUpload = React.addons.update(this.state.filesToUpload, { [fileId]: changeSet });
this.setState({ filesToUpload }); this.setState({ filesToUpload });
}, },
isDropzoneInactive() { isDropzoneInactive() {
let filesToDisplay = this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1); const filesToDisplay = this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1);
let queryParams = this.props.location.query;
if((this.props.enableLocalHashing && !queryParams.method) || !this.props.areAssetsEditable || !this.props.multiple && filesToDisplay.length > 0) { if ((this.props.enableLocalHashing && !this.props.uploadMethod) || !this.props.areAssetsEditable || !this.props.multiple && filesToDisplay.length > 0) {
return true; return true;
} else { } else {
return false; return false;
} }
}, },
getAllowedExtensions() { getAllowedExtensions() {
@ -850,17 +837,16 @@ let ReactS3FineUploader = React.createClass({
}, },
render() { render() {
let { const {
multiple, multiple,
areAssetsDownloadable, areAssetsDownloadable,
areAssetsEditable, areAssetsEditable,
onInactive, onInactive,
enableLocalHashing, enableLocalHashing,
fileClassToUpload, uploadMethod,
validation, fileClassToUpload,
fileInputElement, validation,
location fileInputElement } = this.props;
} = this.props;
// Here we initialize the template that has been either provided from the outside // Here we initialize the template that has been either provided from the outside
// or the default input that is FileDragAndDrop. // or the default input that is FileDragAndDrop.
@ -870,8 +856,8 @@ let ReactS3FineUploader = React.createClass({
areAssetsEditable, areAssetsEditable,
onInactive, onInactive,
enableLocalHashing, enableLocalHashing,
uploadMethod,
fileClassToUpload, fileClassToUpload,
location,
onDrop: this.handleUploadFile, onDrop: this.handleUploadFile,
filesToUpload: this.state.filesToUpload, filesToUpload: this.state.filesToUpload,
handleDeleteFile: this.handleDeleteFile, handleDeleteFile: this.handleDeleteFile,

View File

@ -33,7 +33,6 @@ import { mergeOptions } from '../../../../../../utils/general_utils';
let CylandPieceContainer = React.createClass({ let CylandPieceContainer = React.createClass({
propTypes: { propTypes: {
location: React.PropTypes.object,
params: React.PropTypes.object params: React.PropTypes.object
}, },
@ -107,8 +106,7 @@ let CylandPieceContainer = React.createClass({
<CylandAdditionalDataForm <CylandAdditionalDataForm
piece={this.state.piece} piece={this.state.piece}
disabled={!this.state.piece.acl.acl_edit} disabled={!this.state.piece.acl.acl_edit}
isInline={true} isInline={true} />
location={this.props.location}/>
</CollapsibleParagraph> </CollapsibleParagraph>
</WalletPieceContainer> </WalletPieceContainer>
); );

View File

@ -26,8 +26,7 @@ let CylandAdditionalDataForm = React.createClass({
handleSuccess: React.PropTypes.func, handleSuccess: React.PropTypes.func,
piece: React.PropTypes.object.isRequired, piece: React.PropTypes.object.isRequired,
disabled: React.PropTypes.bool, disabled: React.PropTypes.bool,
isInline: React.PropTypes.bool, isInline: React.PropTypes.bool
location: React.PropTypes.object
}, },
getDefaultProps() { getDefaultProps() {
@ -143,8 +142,7 @@ let CylandAdditionalDataForm = React.createClass({
isReadyForFormSubmission={formSubmissionValidation.fileOptional} isReadyForFormSubmission={formSubmissionValidation.fileOptional}
pieceId={piece.id} pieceId={piece.id}
otherData={piece.other_data} otherData={piece.other_data}
multiple={true} multiple={true} />
location={this.props.location}/>
</Form> </Form>
); );
} else { } else {

View File

@ -210,8 +210,7 @@ let CylandRegisterPiece = React.createClass({
<CylandAdditionalDataForm <CylandAdditionalDataForm
disabled={this.state.step > 1} disabled={this.state.step > 1}
handleSuccess={this.handleAdditionalDataSuccess} handleSuccess={this.handleAdditionalDataSuccess}
piece={this.state.piece} piece={this.state.piece} />
location={this.props.location}/>
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@ -2,8 +2,8 @@
import requests from '../utils/requests'; import requests from '../utils/requests';
import { generateOrderingQueryParams } from '../utils/fetch_api_utils';
import { mergeOptions } from '../utils/general_utils'; import { mergeOptions } from '../utils/general_utils';
import { generateOrderingQueryParams } from '../utils/url_utils';
let EditionListFetcher = { let EditionListFetcher = {
/** /**

View File

@ -3,7 +3,7 @@
import requests from '../utils/requests'; import requests from '../utils/requests';
import { mergeOptions } from '../utils/general_utils'; import { mergeOptions } from '../utils/general_utils';
import { generateOrderingQueryParams } from '../utils/fetch_api_utils'; import { generateOrderingQueryParams } from '../utils/url_utils';
let PieceListFetcher = { let PieceListFetcher = {
/** /**

View File

@ -2,63 +2,10 @@
import Q from 'q'; import Q from 'q';
import { sanitize } from './general_utils';
import AppConstants from '../constants/application_constants'; import AppConstants from '../constants/application_constants';
// TODO: Create Unittests that test all functions // TODO: Create Unittests that test all functions
/**
* Takes a key-value object of this form:
*
* {
* 'page': 1,
* 'pageSize': 10
* }
*
* and converts it to a query-parameter, which you can append to your URL.
* The return looks like this:
*
* ?page=1&page_size=10
*
* CamelCase gets converted to snake_case!
*
*/
export function argsToQueryParams(obj) {
obj = sanitize(obj);
return Object
.keys(obj)
.map((key, i) => {
let s = '';
if(i === 0) {
s += '?';
} else {
s += '&';
}
let snakeCaseKey = key.replace(/[A-Z]/, (match) => '_' + match.toLowerCase());
return s + snakeCaseKey + '=' + encodeURIComponent(obj[key]);
})
.join('');
}
/**
* Takes a string and a boolean and generates a string query parameter for
* an API call.
*/
export function generateOrderingQueryParams(orderBy, orderAsc) {
let interpolation = '';
if(!orderAsc) {
interpolation += '-';
}
return interpolation + orderBy;
}
export function status(response) { export function status(response) {
if (response.status >= 200 && response.status < 300) { if (response.status >= 200 && response.status < 300) {
return response; return response;
@ -68,7 +15,7 @@ export function status(response) {
export function getCookie(name) { export function getCookie(name) {
let parts = document.cookie.split(';'); let parts = document.cookie.split(';');
for(let i = 0; i < parts.length; i++) { for(let i = 0; i < parts.length; i++) {
if(parts[i].indexOf(AppConstants.csrftoken + '=') > -1) { if(parts[i].indexOf(AppConstants.csrftoken + '=') > -1) {
return parts[i].split('=').pop(); return parts[i].split('=').pop();
@ -111,4 +58,4 @@ export function fetchImageAsBlob(url) {
xhr.send(); xhr.send();
}); });
} }

View File

@ -1,29 +1,21 @@
'use strict'; 'use strict';
/** /**
* Takes an object and deletes all keys that are * Takes an object and returns a shallow copy without any keys
* * that fail the passed in filter function.
* tagged as false by the passed in filter function * Does not modify the passed in object.
* *
* @param {object} obj regular javascript object * @param {object} obj regular javascript object
* @return {object} regular javascript object without null values or empty strings * @return {object} regular javascript object without null values or empty strings
*/ */
export function sanitize(obj, filterFn) { export function sanitize(obj, filterFn) {
if(!filterFn) { if (!filterFn) {
// By matching null with a double equal, we can match undefined and null // By matching null with a double equal, we can match undefined and null
// http://stackoverflow.com/a/15992131 // http://stackoverflow.com/a/15992131
filterFn = (val) => val == null || val === ''; filterFn = (val) => val == null || val === '';
} }
Object return omitFromObject(obj, filterFn);
.keys(obj)
.map((key) => {
if(filterFn(obj[key])) {
delete obj[key];
}
});
return obj;
} }
/** /**
@ -82,8 +74,8 @@ export function formatText() {
}); });
} }
/* /**
Checks a list of objects for key duplicates and returns a boolean * Checks a list of objects for key duplicates and returns a boolean
*/ */
function _doesObjectListHaveDuplicates(l) { function _doesObjectListHaveDuplicates(l) {
let mergedList = []; let mergedList = [];
@ -117,38 +109,11 @@ function _doesObjectListHaveDuplicates(l) {
export function mergeOptions(...l) { export function mergeOptions(...l) {
// If the objects submitted in the list have duplicates,in their key names, // If the objects submitted in the list have duplicates,in their key names,
// abort the merge and tell the function's user to check his objects. // abort the merge and tell the function's user to check his objects.
if(_doesObjectListHaveDuplicates(l)) { if (_doesObjectListHaveDuplicates(l)) {
throw new Error('The objects you submitted for merging have duplicates. Merge aborted.'); throw new Error('The objects you submitted for merging have duplicates. Merge aborted.');
} }
let newObj = {}; return Object.assign({}, ...l);
for(let i = 1; i < l.length; i++) {
newObj = _mergeOptions(newObj, _mergeOptions(l[i - 1], l[i]));
}
return newObj;
}
/**
* Merges a number of objects even if there're having duplicates,
* taking the last given value for the key.
*
* DOES NOT RETURN AN ERROR!
*
* Takes a list of object and merges their keys to one object.
* Uses mergeOptions for two objects.
* @param {[type]} l [description]
* @return {[type]} [description]
*/
export function mergeOptionsWithDuplicates(...l) {
let newObj = {};
for(let i = 1; i < l.length; i++) {
newObj = _mergeOptions(newObj, _mergeOptions(l[i - 1], l[i]));
}
return newObj;
} }
/** /**
@ -164,25 +129,6 @@ export function update(a, ...l) {
return a; return a;
} }
/**
* Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
* @param obj1
* @param obj2
* @returns obj3 a new object based on obj1 and obj2
* Taken from: http://stackoverflow.com/a/171256/1263876
*/
function _mergeOptions(obj1, obj2) {
let obj3 = {};
for (let attrname in obj1) {
obj3[attrname] = obj1[attrname];
}
for (let attrname in obj2) {
obj3[attrname] = obj2[attrname];
}
return obj3;
}
/** /**
* Escape HTML in a string so it can be injected safely using * Escape HTML in a string so it can be injected safely using
* React's `dangerouslySetInnerHTML` * React's `dangerouslySetInnerHTML`

View File

@ -2,24 +2,13 @@
import Q from 'q'; import Q from 'q';
import { argsToQueryParams, getCookie } from '../utils/fetch_api_utils';
import AppConstants from '../constants/application_constants'; import AppConstants from '../constants/application_constants';
import { getCookie } from '../utils/fetch_api_utils';
import { omitFromObject } from '../utils/general_utils'; import { omitFromObject } from '../utils/general_utils';
import { argsToQueryParams } from '../utils/url_utils';
class Requests { class Requests {
_merge(defaults, options) {
let merged = {};
for (let key in defaults) {
merged[key] = defaults[key];
}
for (let key in options) {
merged[key] = options[key];
}
return merged;
}
unpackResponse(response) { unpackResponse(response) {
if (response.status >= 500) { if (response.status >= 500) {
throw new Error(response.status + ' - ' + response.statusText + ' - on URL:' + response.url); throw new Error(response.status + ' - ' + response.statusText + ' - on URL:' + response.url);
@ -112,7 +101,7 @@ class Requests {
request(verb, url, options) { request(verb, url, options) {
options = options || {}; options = options || {};
let merged = this._merge(this.httpOptions, options); let merged = Object.assign({}, this.httpOptions, options);
let csrftoken = getCookie(AppConstants.csrftoken); let csrftoken = getCookie(AppConstants.csrftoken);
if (csrftoken) { if (csrftoken) {
merged.headers['X-CSRFToken'] = csrftoken; merged.headers['X-CSRFToken'] = csrftoken;
@ -124,16 +113,16 @@ class Requests {
} }
get(url, params) { get(url, params) {
if (url === undefined){ if (url === undefined) {
throw new Error('Url undefined'); throw new Error('Url undefined');
} }
let paramsCopy = this._merge(params); let paramsCopy = Object.assign({}, params);
let newUrl = this.prepareUrl(url, paramsCopy, true); let newUrl = this.prepareUrl(url, paramsCopy, true);
return this.request('get', newUrl); return this.request('get', newUrl);
} }
delete(url, params) { delete(url, params) {
let paramsCopy = this._merge(params); let paramsCopy = Object.assign({}, params);
let newUrl = this.prepareUrl(url, paramsCopy, true); let newUrl = this.prepareUrl(url, paramsCopy, true);
return this.request('delete', newUrl); return this.request('delete', newUrl);
} }
@ -153,11 +142,11 @@ class Requests {
return this._putOrPost(url, params, 'post'); return this._putOrPost(url, params, 'post');
} }
put(url, params){ put(url, params) {
return this._putOrPost(url, params, 'put'); return this._putOrPost(url, params, 'put');
} }
patch(url, params){ patch(url, params) {
return this._putOrPost(url, params, 'patch'); return this._putOrPost(url, params, 'patch');
} }

83
js/utils/url_utils.js Normal file
View File

@ -0,0 +1,83 @@
'use strict'
import camelCase from 'camelcase';
import decamelize from 'decamelize';
import qs from 'qs';
import { sanitize } from './general_utils';
// TODO: Create Unittests that test all functions
/**
* Takes a key-value dictionary of this form:
*
* {
* 'page': 1,
* 'pageSize': 10
* }
*
* and converts it to a query-parameter, which you can append to your URL.
* The return looks like this:
*
* ?page=1&page_size=10
*
* CamelCase gets converted to snake_case!
*
* @param {object} obj Query params dictionary
* @return {string} Query params string
*/
export function argsToQueryParams(obj) {
const sanitizedObj = sanitize(obj);
const queryParamObj = {};
Object
.keys(sanitizedObj)
.forEach((key) => {
queryParamObj[decamelize(key)] = sanitizedObj[key];
});
// Use bracket arrayFormat as history.js and react-router use it
return '?' + qs.stringify(queryParamObj, { arrayFormat: 'brackets' });
}
/**
* Get the current url's query params as an key-val dictionary.
* snake_case gets converted to CamelCase!
* @return {object} Query params dictionary
*/
export function getCurrentQueryParams() {
return queryParamsToArgs(window.location.search.substring(1));
}
/**
* Convert the given query param string into a key-val dictionary.
* snake_case gets converted to CamelCase!
* @param {string} queryParamString Query params string
* @return {object} Query params dictionary
*/
export function queryParamsToArgs(queryParamString) {
const qsQueryParamObj = qs.parse(queryParamString);
const camelCaseParamObj = {};
Object
.keys(qsQueryParamObj)
.forEach((key) => {
camelCaseParamObj[camelCase(key)] = qsQueryParamObj[key];
});
return camelCaseParamObj;
}
/**
* Takes a string and a boolean and generates a string query parameter for
* an API call.
*/
export function generateOrderingQueryParams(orderBy, orderAsc) {
let interpolation = '';
if(!orderAsc) {
interpolation += '-';
}
return interpolation + orderBy;
}

View File

@ -46,8 +46,10 @@
"browser-sync": "^2.7.5", "browser-sync": "^2.7.5",
"browserify": "^9.0.8", "browserify": "^9.0.8",
"browserify-shim": "^3.8.10", "browserify-shim": "^3.8.10",
"camelcase": "^1.2.1",
"classnames": "^1.2.2", "classnames": "^1.2.2",
"compression": "^1.4.4", "compression": "^1.4.4",
"decamelize": "^1.1.1",
"envify": "^3.4.0", "envify": "^3.4.0",
"eslint": "^0.22.1", "eslint": "^0.22.1",
"eslint-plugin-react": "^2.5.0", "eslint-plugin-react": "^2.5.0",
@ -73,6 +75,7 @@
"object-assign": "^2.0.0", "object-assign": "^2.0.0",
"opn": "^3.0.2", "opn": "^3.0.2",
"q": "^1.4.1", "q": "^1.4.1",
"qs": "^4.0.0",
"raven-js": "^1.1.19", "raven-js": "^1.1.19",
"react": "0.13.2", "react": "0.13.2",
"react-bootstrap": "0.25.1", "react-bootstrap": "0.25.1",