mirror of
https://github.com/ascribe/onion.git
synced 2024-12-22 17:33:14 +01:00
Merge with AD-1264
This commit is contained in:
commit
cd6673f255
@ -21,8 +21,7 @@ let FurtherDetailsFileuploader = React.createClass({
|
||||
submitFile: React.PropTypes.func,
|
||||
isReadyForFormSubmission: React.PropTypes.func,
|
||||
editable: React.PropTypes.bool,
|
||||
multiple: React.PropTypes.bool,
|
||||
location: React.PropTypes.object
|
||||
multiple: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
@ -46,6 +45,7 @@ let FurtherDetailsFileuploader = React.createClass({
|
||||
|
||||
return (
|
||||
<Property
|
||||
name="other_data_key"
|
||||
label={this.props.label}>
|
||||
<ReactS3FineUploader
|
||||
uploadStarted={this.props.uploadStarted}
|
||||
@ -91,8 +91,7 @@ let FurtherDetailsFileuploader = React.createClass({
|
||||
}}
|
||||
areAssetsDownloadable={true}
|
||||
areAssetsEditable={this.props.editable}
|
||||
multiple={this.props.multiple}
|
||||
location={this.props.location}/>
|
||||
multiple={this.props.multiple} />
|
||||
</Property>
|
||||
);
|
||||
}
|
||||
|
@ -12,8 +12,6 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
|
||||
import requests from '../../utils/requests';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { mergeOptionsWithDuplicates } from '../../utils/general_utils';
|
||||
|
||||
|
||||
let Form = React.createClass({
|
||||
propTypes: {
|
||||
@ -124,12 +122,12 @@ let Form = React.createClass({
|
||||
getFormData() {
|
||||
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;
|
||||
}
|
||||
|
||||
if(typeof this.props.getFormData === 'function') {
|
||||
data = mergeOptionsWithDuplicates(data, this.props.getFormData());
|
||||
if (typeof this.props.getFormData === 'function') {
|
||||
data = Object.assign(data, this.props.getFormData());
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -28,8 +28,7 @@ let CreateContractForm = React.createClass({
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: React.PropTypes.string,
|
||||
plural: React.PropTypes.string
|
||||
}),
|
||||
location: React.PropTypes.object
|
||||
})
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
@ -87,8 +86,7 @@ let CreateContractForm = React.createClass({
|
||||
areAssetsEditable={true}
|
||||
setIsUploadReady={this.setIsUploadReady}
|
||||
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
|
||||
fileClassToUpload={this.props.fileClassToUpload}
|
||||
location={this.props.location}/>
|
||||
fileClassToUpload={this.props.fileClassToUpload} />
|
||||
</Property>
|
||||
<Property
|
||||
name='name'
|
||||
@ -110,4 +108,4 @@ let CreateContractForm = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
export default CreateContractForm;
|
||||
export default CreateContractForm;
|
||||
|
@ -26,12 +26,15 @@ let RegisterPieceForm = React.createClass({
|
||||
isFineUploaderActive: React.PropTypes.bool,
|
||||
isFineUploaderEditable: React.PropTypes.bool,
|
||||
enableLocalHashing: React.PropTypes.bool,
|
||||
children: React.PropTypes.element,
|
||||
onLoggedOut: React.PropTypes.func,
|
||||
|
||||
// For this form to work with SlideContainer, we sometimes have to disable it
|
||||
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() {
|
||||
@ -116,7 +119,7 @@ let RegisterPieceForm = React.createClass({
|
||||
onLoggedOut={this.props.onLoggedOut}
|
||||
disabled={!this.props.isFineUploaderEditable}
|
||||
enableLocalHashing={enableLocalHashing}
|
||||
location={this.props.location}/>
|
||||
uploadMethod={this.props.location.query.method} />
|
||||
</Property>
|
||||
<Property
|
||||
name='artist_name'
|
||||
|
@ -37,6 +37,7 @@ let InputFineUploader = React.createClass({
|
||||
onLoggedOut: React.PropTypes.func,
|
||||
|
||||
enableLocalHashing: React.PropTypes.bool,
|
||||
uploadMethod: React.PropTypes.string,
|
||||
|
||||
// provided by Property
|
||||
disabled: React.PropTypes.bool,
|
||||
@ -46,8 +47,7 @@ let InputFineUploader = React.createClass({
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: React.PropTypes.string,
|
||||
plural: React.PropTypes.string
|
||||
}),
|
||||
location: React.PropTypes.object
|
||||
})
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
@ -107,10 +107,10 @@ let InputFineUploader = React.createClass({
|
||||
}}
|
||||
onInactive={this.props.onLoggedOut}
|
||||
enableLocalHashing={this.props.enableLocalHashing}
|
||||
fileClassToUpload={this.props.fileClassToUpload}
|
||||
location={this.props.location}/>
|
||||
uploadMethod={this.props.uploadMethod}
|
||||
fileClassToUpload={this.props.fileClassToUpload} />
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default InputFineUploader;
|
||||
export default InputFineUploader;
|
||||
|
@ -20,8 +20,7 @@ import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let ContractSettingsUpdateButton = React.createClass({
|
||||
propTypes: {
|
||||
contract: React.PropTypes.object,
|
||||
location: React.PropTypes.object
|
||||
contract: React.PropTypes.object
|
||||
},
|
||||
|
||||
submitFile(file) {
|
||||
@ -90,10 +89,9 @@ let ContractSettingsUpdateButton = React.createClass({
|
||||
plural: getLangText('UPDATE')
|
||||
}}
|
||||
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
|
||||
submitFile={this.submitFile}
|
||||
location={this.props.location}/>
|
||||
submitFile={this.submitFile} />
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default ContractSettingsUpdateButton;
|
||||
export default ContractSettingsUpdateButton;
|
||||
|
@ -27,6 +27,7 @@ let FileDragAndDrop = React.createClass({
|
||||
areAssetsEditable: React.PropTypes.bool,
|
||||
|
||||
enableLocalHashing: React.PropTypes.bool,
|
||||
uploadMethod: React.PropTypes.string,
|
||||
|
||||
// triggers a FileDragAndDrop-global spinner
|
||||
hashingProgress: React.PropTypes.number,
|
||||
@ -41,8 +42,7 @@ let FileDragAndDrop = React.createClass({
|
||||
plural: React.PropTypes.string
|
||||
}),
|
||||
|
||||
allowedExtensions: React.PropTypes.string,
|
||||
location: React.PropTypes.object
|
||||
allowedExtensions: React.PropTypes.string
|
||||
},
|
||||
|
||||
handleDragOver(event) {
|
||||
@ -137,19 +137,19 @@ let FileDragAndDrop = React.createClass({
|
||||
},
|
||||
|
||||
render: function () {
|
||||
let { filesToUpload,
|
||||
dropzoneInactive,
|
||||
className,
|
||||
hashingProgress,
|
||||
handleCancelHashing,
|
||||
multiple,
|
||||
enableLocalHashing,
|
||||
fileClassToUpload,
|
||||
areAssetsDownloadable,
|
||||
areAssetsEditable,
|
||||
allowedExtensions,
|
||||
location
|
||||
} = this.props;
|
||||
const {
|
||||
filesToUpload,
|
||||
dropzoneInactive,
|
||||
className,
|
||||
hashingProgress,
|
||||
handleCancelHashing,
|
||||
multiple,
|
||||
enableLocalHashing,
|
||||
uploadMethod,
|
||||
fileClassToUpload,
|
||||
areAssetsDownloadable,
|
||||
areAssetsEditable,
|
||||
allowedExtensions } = this.props;
|
||||
|
||||
// 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;
|
||||
@ -185,8 +185,8 @@ let FileDragAndDrop = React.createClass({
|
||||
hasFiles={hasFiles}
|
||||
onClick={this.handleOnClick}
|
||||
enableLocalHashing={enableLocalHashing}
|
||||
fileClassToUpload={fileClassToUpload}
|
||||
location={location}/>
|
||||
uploadMethod={uploadMethod}
|
||||
fileClassToUpload={fileClassToUpload} />
|
||||
<FileDragAndDropPreviewIterator
|
||||
files={filesToUpload}
|
||||
handleDeleteFile={this.handleDeleteFile}
|
||||
|
@ -3,30 +3,28 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import { getLangText } from '../../../utils/lang_utils';
|
||||
import { dragAndDropAvailable } from '../../../utils/feature_detection_utils';
|
||||
|
||||
|
||||
import { getLangText } from '../../../utils/lang_utils';
|
||||
import { getCurrentQueryParams } from '../../../utils/url_utils';
|
||||
|
||||
let FileDragAndDropDialog = React.createClass({
|
||||
propTypes: {
|
||||
hasFiles: React.PropTypes.bool,
|
||||
multipleFiles: React.PropTypes.bool,
|
||||
onClick: React.PropTypes.func,
|
||||
enableLocalHashing: React.PropTypes.bool,
|
||||
uploadMethod: React.PropTypes.string,
|
||||
onClick: React.PropTypes.func,
|
||||
|
||||
// A class of a file the user has to upload
|
||||
// Needs to be defined both in singular as well as in plural
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: React.PropTypes.string,
|
||||
plural: React.PropTypes.string
|
||||
}),
|
||||
|
||||
location: React.PropTypes.object
|
||||
})
|
||||
},
|
||||
|
||||
getDragDialog(fileClass) {
|
||||
if(dragAndDropAvailable) {
|
||||
if (dragAndDropAvailable) {
|
||||
return [
|
||||
<p>{getLangText('Drag %s here', fileClass)}</p>,
|
||||
<p>{getLangText('or')}</p>
|
||||
@ -37,26 +35,31 @@ let FileDragAndDropDialog = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
const queryParams = this.props.location.query;
|
||||
const {
|
||||
hasFiles,
|
||||
multipleFiles,
|
||||
enableLocalHashing,
|
||||
uploadMethod,
|
||||
fileClassToUpload,
|
||||
onClick } = this.props;
|
||||
|
||||
if(this.props.hasFiles) {
|
||||
if (hasFiles) {
|
||||
return null;
|
||||
} 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';
|
||||
|
||||
let queryParamsUpload = Object.assign({}, queryParams);
|
||||
const queryParamsUpload = Object.assign({}, currentQueryParams);
|
||||
queryParamsUpload.method = 'upload';
|
||||
|
||||
let { location } = this.props;
|
||||
|
||||
return (
|
||||
<div className="file-drag-and-drop-dialog present-options">
|
||||
<p>{getLangText('Would you rather')}</p>
|
||||
<Link
|
||||
to={location.pathname}
|
||||
to={window.location.pathname}
|
||||
query={queryParamsHash}>
|
||||
<span className="btn btn-default btn-sm">
|
||||
{getLangText('Hash your work')}
|
||||
@ -64,9 +67,9 @@ let FileDragAndDropDialog = React.createClass({
|
||||
</Link>
|
||||
|
||||
<span> or </span>
|
||||
|
||||
|
||||
<Link
|
||||
to={location.pathname}
|
||||
to={window.location.pathname}
|
||||
query={queryParamsUpload}>
|
||||
<span className="btn btn-default btn-sm">
|
||||
{getLangText('Upload and hash your work')}
|
||||
@ -75,26 +78,27 @@ let FileDragAndDropDialog = React.createClass({
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
if(this.props.multipleFiles) {
|
||||
if (multipleFiles) {
|
||||
return (
|
||||
<span className="file-drag-and-drop-dialog">
|
||||
{this.getDragDialog(this.props.fileClassToUpload.plural)}
|
||||
{this.getDragDialog(fileClassToUpload.plural)}
|
||||
<span
|
||||
className="btn btn-default"
|
||||
onClick={this.props.onClick}>
|
||||
{getLangText('choose %s to upload', this.props.fileClassToUpload.plural)}
|
||||
onClick={onClick}>
|
||||
{getLangText('choose %s to upload', fileClassToUpload.plural)}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
} 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 (
|
||||
<span className="file-drag-and-drop-dialog">
|
||||
{this.getDragDialog(this.props.fileClassToUpload.singular)}
|
||||
{this.getDragDialog(fileClassToUpload.singular)}
|
||||
<span
|
||||
className="btn btn-default"
|
||||
onClick={this.props.onClick}>
|
||||
onClick={onClick}>
|
||||
{dialog}
|
||||
</span>
|
||||
</span>
|
||||
@ -105,4 +109,4 @@ let FileDragAndDropDialog = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
export default FileDragAndDropDialog;
|
||||
export default FileDragAndDropDialog;
|
||||
|
@ -18,7 +18,6 @@ import { displayValidFilesFilter, transformAllowedExtensionsToInputAcceptProp }
|
||||
import { getCookie } from '../../utils/fetch_api_utils';
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
|
||||
let ReactS3FineUploader = React.createClass({
|
||||
propTypes: {
|
||||
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
|
||||
// the file in the browser using md5 and then uploading the resulting text document instead
|
||||
// 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,
|
||||
|
||||
// automatically injected by React-Router
|
||||
query: React.PropTypes.object,
|
||||
uploadMethod: React.PropTypes.string,
|
||||
|
||||
// A class of a file the user has to upload
|
||||
// Needs to be defined both in singular as well as in plural
|
||||
@ -126,9 +128,7 @@ let ReactS3FineUploader = React.createClass({
|
||||
fileInputElement: React.PropTypes.oneOfType([
|
||||
React.PropTypes.func,
|
||||
React.PropTypes.element
|
||||
]),
|
||||
|
||||
location: React.PropTypes.object
|
||||
])
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
@ -192,17 +192,17 @@ let ReactS3FineUploader = React.createClass({
|
||||
filesToUpload: [],
|
||||
uploader: new fineUploader.s3.FineUploaderBasic(this.propsToConfig()),
|
||||
csrfToken: getCookie(AppConstants.csrftoken),
|
||||
|
||||
|
||||
// -1: aborted
|
||||
// -2: uninitialized
|
||||
hashingProgress: -2,
|
||||
|
||||
|
||||
// this is for logging
|
||||
chunks: {}
|
||||
};
|
||||
},
|
||||
|
||||
componentWillUpdate() {
|
||||
componentWillReceiveProps() {
|
||||
// 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
|
||||
@ -354,7 +354,6 @@ let ReactS3FineUploader = React.createClass({
|
||||
/* FineUploader specific callback function handlers */
|
||||
|
||||
onUploadChunk(id, name, chunkData) {
|
||||
|
||||
let chunks = this.state.chunks;
|
||||
|
||||
chunks[id + '-' + chunkData.startByte + '-' + chunkData.endByte] = {
|
||||
@ -370,10 +369,9 @@ let ReactS3FineUploader = React.createClass({
|
||||
},
|
||||
|
||||
onUploadChunkSuccess(id, chunkData, responseJson, xhr) {
|
||||
|
||||
let chunks = this.state.chunks;
|
||||
let chunkKey = id + '-' + chunkData.startByte + '-' + chunkData.endByte;
|
||||
|
||||
|
||||
if(chunks[chunkKey]) {
|
||||
chunks[chunkKey].completed = true;
|
||||
chunks[chunkKey].responseJson = responseJson;
|
||||
@ -414,7 +412,7 @@ let ReactS3FineUploader = React.createClass({
|
||||
} else {
|
||||
console.warn('You didn\'t define submitFile 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,
|
||||
@ -597,7 +595,6 @@ let ReactS3FineUploader = React.createClass({
|
||||
} else {
|
||||
throw new Error(getLangText('File upload could not be paused.'));
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
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.
|
||||
//
|
||||
// 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')
|
||||
let queryParams = this.props.location.query;
|
||||
if(this.props.enableLocalHashing && queryParams && queryParams.method === 'hash') {
|
||||
|
||||
let convertedFilePromises = [];
|
||||
// as when the correct method prop is present ('hash' and not 'upload')
|
||||
if (this.props.enableLocalHashing && this.props.uploadMethod === 'hash') {
|
||||
const convertedFilePromises = [];
|
||||
let overallFileSize = 0;
|
||||
|
||||
// "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++) {
|
||||
|
||||
// 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;
|
||||
@ -668,7 +663,6 @@ let ReactS3FineUploader = React.createClass({
|
||||
// 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
|
||||
@ -676,7 +670,6 @@ let ReactS3FineUploader = React.createClass({
|
||||
// with their txt representative
|
||||
Q.all(convertedFilePromises)
|
||||
.progress(({index, value: {progress, reject}}) => {
|
||||
|
||||
// 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.
|
||||
@ -696,18 +689,14 @@ let ReactS3FineUploader = React.createClass({
|
||||
// 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});
|
||||
|
||||
@ -823,20 +812,18 @@ let ReactS3FineUploader = React.createClass({
|
||||
changeSet.status = { $set: status };
|
||||
|
||||
let filesToUpload = React.addons.update(this.state.filesToUpload, { [fileId]: changeSet });
|
||||
|
||||
|
||||
this.setState({ filesToUpload });
|
||||
},
|
||||
|
||||
isDropzoneInactive() {
|
||||
let filesToDisplay = this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1);
|
||||
let queryParams = this.props.location.query;
|
||||
const filesToDisplay = this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1);
|
||||
|
||||
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;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
getAllowedExtensions() {
|
||||
@ -850,17 +837,16 @@ let ReactS3FineUploader = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
let {
|
||||
multiple,
|
||||
areAssetsDownloadable,
|
||||
areAssetsEditable,
|
||||
onInactive,
|
||||
enableLocalHashing,
|
||||
fileClassToUpload,
|
||||
validation,
|
||||
fileInputElement,
|
||||
location
|
||||
} = this.props;
|
||||
const {
|
||||
multiple,
|
||||
areAssetsDownloadable,
|
||||
areAssetsEditable,
|
||||
onInactive,
|
||||
enableLocalHashing,
|
||||
uploadMethod,
|
||||
fileClassToUpload,
|
||||
validation,
|
||||
fileInputElement } = this.props;
|
||||
|
||||
// Here we initialize the template that has been either provided from the outside
|
||||
// or the default input that is FileDragAndDrop.
|
||||
@ -870,8 +856,8 @@ let ReactS3FineUploader = React.createClass({
|
||||
areAssetsEditable,
|
||||
onInactive,
|
||||
enableLocalHashing,
|
||||
uploadMethod,
|
||||
fileClassToUpload,
|
||||
location,
|
||||
onDrop: this.handleUploadFile,
|
||||
filesToUpload: this.state.filesToUpload,
|
||||
handleDeleteFile: this.handleDeleteFile,
|
||||
|
@ -33,7 +33,6 @@ import { mergeOptions } from '../../../../../../utils/general_utils';
|
||||
|
||||
let CylandPieceContainer = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object,
|
||||
params: React.PropTypes.object
|
||||
},
|
||||
|
||||
@ -107,8 +106,7 @@ let CylandPieceContainer = React.createClass({
|
||||
<CylandAdditionalDataForm
|
||||
piece={this.state.piece}
|
||||
disabled={!this.state.piece.acl.acl_edit}
|
||||
isInline={true}
|
||||
location={this.props.location}/>
|
||||
isInline={true} />
|
||||
</CollapsibleParagraph>
|
||||
</WalletPieceContainer>
|
||||
);
|
||||
|
@ -26,8 +26,7 @@ let CylandAdditionalDataForm = React.createClass({
|
||||
handleSuccess: React.PropTypes.func,
|
||||
piece: React.PropTypes.object.isRequired,
|
||||
disabled: React.PropTypes.bool,
|
||||
isInline: React.PropTypes.bool,
|
||||
location: React.PropTypes.object
|
||||
isInline: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
@ -143,8 +142,7 @@ let CylandAdditionalDataForm = React.createClass({
|
||||
isReadyForFormSubmission={formSubmissionValidation.fileOptional}
|
||||
pieceId={piece.id}
|
||||
otherData={piece.other_data}
|
||||
multiple={true}
|
||||
location={this.props.location}/>
|
||||
multiple={true} />
|
||||
</Form>
|
||||
);
|
||||
} else {
|
||||
|
@ -210,8 +210,7 @@ let CylandRegisterPiece = React.createClass({
|
||||
<CylandAdditionalDataForm
|
||||
disabled={this.state.step > 1}
|
||||
handleSuccess={this.handleAdditionalDataSuccess}
|
||||
piece={this.state.piece}
|
||||
location={this.props.location}/>
|
||||
piece={this.state.piece} />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
import requests from '../utils/requests';
|
||||
|
||||
import { generateOrderingQueryParams } from '../utils/fetch_api_utils';
|
||||
import { mergeOptions } from '../utils/general_utils';
|
||||
import { generateOrderingQueryParams } from '../utils/url_utils';
|
||||
|
||||
let EditionListFetcher = {
|
||||
/**
|
||||
|
@ -3,7 +3,7 @@
|
||||
import requests from '../utils/requests';
|
||||
|
||||
import { mergeOptions } from '../utils/general_utils';
|
||||
import { generateOrderingQueryParams } from '../utils/fetch_api_utils';
|
||||
import { generateOrderingQueryParams } from '../utils/url_utils';
|
||||
|
||||
let PieceListFetcher = {
|
||||
/**
|
||||
|
@ -2,63 +2,10 @@
|
||||
|
||||
import Q from 'q';
|
||||
|
||||
import { sanitize } from './general_utils';
|
||||
import AppConstants from '../constants/application_constants';
|
||||
|
||||
// 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) {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response;
|
||||
@ -68,7 +15,7 @@ export function status(response) {
|
||||
|
||||
export function getCookie(name) {
|
||||
let parts = document.cookie.split(';');
|
||||
|
||||
|
||||
for(let i = 0; i < parts.length; i++) {
|
||||
if(parts[i].indexOf(AppConstants.csrftoken + '=') > -1) {
|
||||
return parts[i].split('=').pop();
|
||||
@ -111,4 +58,4 @@ export function fetchImageAsBlob(url) {
|
||||
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Takes an object and deletes all keys that are
|
||||
*
|
||||
* tagged as false by the passed in filter function
|
||||
* Takes an object and returns a shallow copy without any keys
|
||||
* that fail the passed in filter function.
|
||||
* Does not modify the passed in object.
|
||||
*
|
||||
* @param {object} obj regular javascript object
|
||||
* @return {object} regular javascript object without null values or empty strings
|
||||
*/
|
||||
export function sanitize(obj, filterFn) {
|
||||
if(!filterFn) {
|
||||
if (!filterFn) {
|
||||
// By matching null with a double equal, we can match undefined and null
|
||||
// http://stackoverflow.com/a/15992131
|
||||
filterFn = (val) => val == null || val === '';
|
||||
}
|
||||
|
||||
Object
|
||||
.keys(obj)
|
||||
.map((key) => {
|
||||
if(filterFn(obj[key])) {
|
||||
delete obj[key];
|
||||
}
|
||||
});
|
||||
|
||||
return obj;
|
||||
return omitFromObject(obj, filterFn);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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) {
|
||||
let mergedList = [];
|
||||
@ -117,38 +109,11 @@ function _doesObjectListHaveDuplicates(l) {
|
||||
export function mergeOptions(...l) {
|
||||
// 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.
|
||||
if(_doesObjectListHaveDuplicates(l)) {
|
||||
if (_doesObjectListHaveDuplicates(l)) {
|
||||
throw new Error('The objects you submitted for merging have duplicates. Merge aborted.');
|
||||
}
|
||||
|
||||
let newObj = {};
|
||||
|
||||
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;
|
||||
return Object.assign({}, ...l);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -164,25 +129,6 @@ export function update(a, ...l) {
|
||||
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
|
||||
* React's `dangerouslySetInnerHTML`
|
||||
|
@ -2,24 +2,13 @@
|
||||
|
||||
import Q from 'q';
|
||||
|
||||
import { argsToQueryParams, getCookie } from '../utils/fetch_api_utils';
|
||||
|
||||
import AppConstants from '../constants/application_constants';
|
||||
|
||||
import { getCookie } from '../utils/fetch_api_utils';
|
||||
import { omitFromObject } from '../utils/general_utils';
|
||||
import { argsToQueryParams } from '../utils/url_utils';
|
||||
|
||||
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) {
|
||||
if (response.status >= 500) {
|
||||
throw new Error(response.status + ' - ' + response.statusText + ' - on URL:' + response.url);
|
||||
@ -112,7 +101,7 @@ class Requests {
|
||||
|
||||
request(verb, url, options) {
|
||||
options = options || {};
|
||||
let merged = this._merge(this.httpOptions, options);
|
||||
let merged = Object.assign({}, this.httpOptions, options);
|
||||
let csrftoken = getCookie(AppConstants.csrftoken);
|
||||
if (csrftoken) {
|
||||
merged.headers['X-CSRFToken'] = csrftoken;
|
||||
@ -124,16 +113,16 @@ class Requests {
|
||||
}
|
||||
|
||||
get(url, params) {
|
||||
if (url === undefined){
|
||||
if (url === undefined) {
|
||||
throw new Error('Url undefined');
|
||||
}
|
||||
let paramsCopy = this._merge(params);
|
||||
let paramsCopy = Object.assign({}, params);
|
||||
let newUrl = this.prepareUrl(url, paramsCopy, true);
|
||||
return this.request('get', newUrl);
|
||||
}
|
||||
|
||||
delete(url, params) {
|
||||
let paramsCopy = this._merge(params);
|
||||
let paramsCopy = Object.assign({}, params);
|
||||
let newUrl = this.prepareUrl(url, paramsCopy, true);
|
||||
return this.request('delete', newUrl);
|
||||
}
|
||||
@ -153,11 +142,11 @@ class Requests {
|
||||
return this._putOrPost(url, params, 'post');
|
||||
}
|
||||
|
||||
put(url, params){
|
||||
put(url, params) {
|
||||
return this._putOrPost(url, params, 'put');
|
||||
}
|
||||
|
||||
patch(url, params){
|
||||
patch(url, params) {
|
||||
return this._putOrPost(url, params, 'patch');
|
||||
}
|
||||
|
||||
|
83
js/utils/url_utils.js
Normal file
83
js/utils/url_utils.js
Normal 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;
|
||||
}
|
@ -46,8 +46,10 @@
|
||||
"browser-sync": "^2.7.5",
|
||||
"browserify": "^9.0.8",
|
||||
"browserify-shim": "^3.8.10",
|
||||
"camelcase": "^1.2.1",
|
||||
"classnames": "^1.2.2",
|
||||
"compression": "^1.4.4",
|
||||
"decamelize": "^1.1.1",
|
||||
"envify": "^3.4.0",
|
||||
"eslint": "^0.22.1",
|
||||
"eslint-plugin-react": "^2.5.0",
|
||||
@ -73,6 +75,7 @@
|
||||
"object-assign": "^2.0.0",
|
||||
"opn": "^3.0.2",
|
||||
"q": "^1.4.1",
|
||||
"qs": "^4.0.0",
|
||||
"raven-js": "^1.1.19",
|
||||
"react": "0.13.2",
|
||||
"react-bootstrap": "0.25.1",
|
||||
|
Loading…
Reference in New Issue
Block a user