{getLangText('Hash your work')}
@@ -64,9 +67,9 @@ let FileDragAndDropDialog = React.createClass({
or
-
+
{getLangText('Upload and hash your work')}
@@ -75,26 +78,27 @@ let FileDragAndDropDialog = React.createClass({
);
} else {
- if(this.props.multipleFiles) {
+ if (multipleFiles) {
return (
- {this.getDragDialog(this.props.fileClassToUpload.plural)}
+ {this.getDragDialog(fileClassToUpload.plural)}
- {getLangText('choose %s to upload', this.props.fileClassToUpload.plural)}
+ onClick={onClick}>
+ {getLangText('choose %s to upload', fileClassToUpload.plural)}
);
} 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 (
- {this.getDragDialog(this.props.fileClassToUpload.singular)}
+ {this.getDragDialog(fileClassToUpload.singular)}
+ onClick={onClick}>
{dialog}
@@ -105,4 +109,4 @@ let FileDragAndDropDialog = React.createClass({
}
});
-export default FileDragAndDropDialog;
\ No newline at end of file
+export default FileDragAndDropDialog;
diff --git a/js/components/ascribe_uploader/react_s3_fine_uploader.js b/js/components/ascribe_uploader/react_s3_fine_uploader.js
index e8cc8bfa..685c2b2f 100644
--- a/js/components/ascribe_uploader/react_s3_fine_uploader.js
+++ b/js/components/ascribe_uploader/react_s3_fine_uploader.js
@@ -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,
diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js b/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js
index 79d63abf..7e784981 100644
--- a/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js
+++ b/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js
@@ -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({
+ isInline={true} />
);
diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js b/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js
index 26270a60..3baf86f1 100644
--- a/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js
+++ b/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js
@@ -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} />
);
} else {
diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js b/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js
index b1df4469..470da761 100644
--- a/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js
+++ b/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js
@@ -210,8 +210,7 @@ let CylandRegisterPiece = React.createClass({
1}
handleSuccess={this.handleAdditionalDataSuccess}
- piece={this.state.piece}
- location={this.props.location}/>
+ piece={this.state.piece} />
diff --git a/js/fetchers/edition_list_fetcher.js b/js/fetchers/edition_list_fetcher.js
index b416c595..93e4553d 100644
--- a/js/fetchers/edition_list_fetcher.js
+++ b/js/fetchers/edition_list_fetcher.js
@@ -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 = {
/**
diff --git a/js/fetchers/piece_list_fetcher.js b/js/fetchers/piece_list_fetcher.js
index 8e58402a..6bd4eb3a 100644
--- a/js/fetchers/piece_list_fetcher.js
+++ b/js/fetchers/piece_list_fetcher.js
@@ -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 = {
/**
diff --git a/js/utils/fetch_api_utils.js b/js/utils/fetch_api_utils.js
index 3ed964ba..cb676fce 100644
--- a/js/utils/fetch_api_utils.js
+++ b/js/utils/fetch_api_utils.js
@@ -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();
});
-}
\ No newline at end of file
+}
diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js
index f0c7be08..8283f8cf 100644
--- a/js/utils/general_utils.js
+++ b/js/utils/general_utils.js
@@ -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`
diff --git a/js/utils/requests.js b/js/utils/requests.js
index 43c09458..8f015a11 100644
--- a/js/utils/requests.js
+++ b/js/utils/requests.js
@@ -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');
}
diff --git a/js/utils/url_utils.js b/js/utils/url_utils.js
new file mode 100644
index 00000000..86c8dfc5
--- /dev/null
+++ b/js/utils/url_utils.js
@@ -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;
+}
diff --git a/package.json b/package.json
index 2b770fd3..63c6d1e0 100644
--- a/package.json
+++ b/package.json
@@ -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",