1
0
mirror of https://github.com/ascribe/onion.git synced 2024-06-28 00:28:00 +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,
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>
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = {
/**

View File

@ -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 = {
/**

View File

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

View File

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

View File

@ -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
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",
"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",