diff --git a/gulpfile.js b/gulpfile.js index c7816a3a..3c92945d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -189,17 +189,7 @@ function bundle(watch) { .pipe(gulpif(!argv.production, sourcemaps.write())) // writes .map file .on('error', notify.onError('Error: <%= error.message %>')) .pipe(gulpif(argv.production, uglify({ - mangle: true, - compress: { - sequences: true, - dead_code: true, - conditionals: true, - booleans: true, - unused: true, - if_return: true, - join_vars: true, - drop_console: true - } + mangle: true }))) .on('error', notify.onError('Error: <%= error.message %>')) .pipe(gulp.dest('./build/js')) diff --git a/js/components/ascribe_buttons/button_submit.js b/js/components/ascribe_buttons/button_submit.js deleted file mode 100644 index ef5999cd..00000000 --- a/js/components/ascribe_buttons/button_submit.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -import React from 'react'; - -let ButtonSubmitOrClose = React.createClass({ - propTypes: { - submitted: React.PropTypes.bool.isRequired, - text: React.PropTypes.string.isRequired - }, - - render() { - if (this.props.submitted){ - return ( -
- -
- ); - } - return ( -
- -
- ); - } -}); - -export default ButtonSubmitOrClose; diff --git a/js/components/ascribe_buttons/button_submit_close.js b/js/components/ascribe_buttons/button_submit_close.js deleted file mode 100644 index 11d3c0a4..00000000 --- a/js/components/ascribe_buttons/button_submit_close.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -import React from 'react'; - -import AppConstants from '../../constants/application_constants'; -import { getLangText } from '../../utils/lang_utils.js' - -let ButtonSubmitOrClose = React.createClass({ - propTypes: { - submitted: React.PropTypes.bool.isRequired, - text: React.PropTypes.string.isRequired, - onClose: React.PropTypes.func.isRequired - }, - - render() { - if (this.props.submitted){ - return ( -
- -
- ); - } - return ( -
- - -
- ); - } -}); - -export default ButtonSubmitOrClose; diff --git a/js/components/ascribe_detail/edition_container.js b/js/components/ascribe_detail/edition_container.js index 15086434..2194123d 100644 --- a/js/components/ascribe_detail/edition_container.js +++ b/js/components/ascribe_detail/edition_container.js @@ -50,7 +50,7 @@ let EditionContainer = React.createClass({ }, render() { - if('title' in this.state.edition) { + if(this.state.edition && this.state.edition.title) { return ( {return data.id; }).join() : null; + let otherDataIds = this.props.otherData ? this.props.otherData.map((data) => data.id).join() : null; return ( this[this.props.method](), 100); } else { throw new Error('This HTTP method is not supported by form.js (' + this.props.method + ')'); @@ -100,13 +107,13 @@ let Form = React.createClass({ .catch(this.handleError); }, - getFormData(){ + 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 ('getFormData' in this.props){ + if (this.props.getFormData && typeof this.props.getFormData === 'function'){ data = mergeOptionsWithDuplicates(data, this.props.getFormData()); } @@ -118,11 +125,12 @@ let Form = React.createClass({ }, handleSuccess(response){ - if ('handleSuccess' in this.props){ + if(this.props.handleSuccess && typeof this.props.handleSuccess === 'function') { this.props.handleSuccess(response); } - for (var ref in this.refs){ - if ('handleSuccess' in this.refs[ref]){ + + for(let ref in this.refs) { + if(this.refs[ref] && this.refs[ref].handleSuccess && typeof this.refs[ref].handleSuccess === 'function'){ this.refs[ref].handleSuccess(); } } @@ -134,9 +142,9 @@ let Form = React.createClass({ handleError(err){ if (err.json) { - for (var input in err.json.errors){ + for (let input in err.json.errors){ if (this.refs && this.refs[input] && this.refs[input].state) { - this.refs[input].setErrors( err.json.errors[input]); + this.refs[input].setErrors(err.json.errors[input]); } else { this.setState({errors: this.state.errors.concat(err.json.errors[input])}); } @@ -164,8 +172,8 @@ let Form = React.createClass({ }, clearErrors(){ - for (var ref in this.refs){ - if ('clearErrors' in this.refs[ref]){ + for(let ref in this.refs){ + if (this.refs[ref] && this.refs[ref].clearErrors && typeof this.refs[ref].clearErrors === 'function'){ this.refs[ref].clearErrors(); } } @@ -185,8 +193,16 @@ let Form = React.createClass({ buttons = (

- - + +

); @@ -251,6 +267,7 @@ let Form = React.createClass({ role="form" className={className} onSubmit={this.submit} + onReset={this.reset} autoComplete={this.props.autoComplete}> {this.getFakeAutocompletableInputs()} {this.getErrors()} diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js index 276ff492..60dad486 100644 --- a/js/components/ascribe_forms/form_loan.js +++ b/js/components/ascribe_forms/form_loan.js @@ -69,10 +69,11 @@ let LoanForm = React.createClass({ }, handleOnChange(event) { - let potentialEmail = event.target.value; - - if(potentialEmail.match(/.*@.*/)) { - LoanContractActions.fetchLoanContract(potentialEmail); + // event.target.value is the submitted email of the loanee + if(event && event.target && event.target.value && event.target.value.match(/.*@.*/)) { + LoanContractActions.fetchLoanContract(event.target.value); + } else { + LoanContractActions.flushLoanContract(); } }, @@ -143,6 +144,7 @@ let LoanForm = React.createClass({ ref='form' url={this.props.url} getFormData={this.getFormData} + onReset={this.handleOnChange} handleSuccess={this.props.handleSuccess} buttons={this.getButtons()} spinner={ diff --git a/js/components/ascribe_forms/form_register_piece.js b/js/components/ascribe_forms/form_register_piece.js index 3519c976..8f2666c0 100644 --- a/js/components/ascribe_forms/form_register_piece.js +++ b/js/components/ascribe_forms/form_register_piece.js @@ -7,13 +7,10 @@ import UserActions from '../../actions/user_actions'; import Form from './form'; import Property from './property'; +import InputFineUploader from './input_fineuploader'; -import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader'; - -import AppConstants from '../../constants/application_constants'; import ApiUrls from '../../constants/api_urls'; -import { getCookie } from '../../utils/fetch_api_utils'; import { getLangText } from '../../utils/lang_utils'; import { mergeOptions } from '../../utils/general_utils'; import { isReadyForFormSubmission } from '../ascribe_uploader/react_s3_fine_uploader_utils'; @@ -45,7 +42,6 @@ let RegisterPieceForm = React.createClass({ getInitialState(){ return mergeOptions( { - digitalWorkKey: null, isUploadReady: false }, UserStore.getState() @@ -65,18 +61,6 @@ let RegisterPieceForm = React.createClass({ this.setState(state); }, - getFormData(){ - return { - digital_work_key: this.state.digitalWorkKey - }; - }, - - submitKey(key){ - this.setState({ - digitalWorkKey: key - }); - }, - setIsUploadReady(isReady) { this.setState({ isUploadReady: isReady @@ -94,14 +78,15 @@ let RegisterPieceForm = React.createClass({ className="ascribe-form-bordered" ref='form' url={ApiUrls.pieces_list} - getFormData={this.getFormData} handleSuccess={this.props.handleSuccess} - buttons={} + buttons={ + + } spinner={ @@ -111,9 +96,9 @@ let RegisterPieceForm = React.createClass({

{this.props.headerMessage}

- - ); - } -}); - export default RegisterPieceForm; diff --git a/js/components/ascribe_forms/input_date.js b/js/components/ascribe_forms/input_date.js index 837221e5..3e2892c0 100644 --- a/js/components/ascribe_forms/input_date.js +++ b/js/components/ascribe_forms/input_date.js @@ -18,7 +18,8 @@ let InputDate = React.createClass({ getInitialState() { return { - value: null + value: null, + value_moment: null }; }, @@ -45,6 +46,10 @@ let InputDate = React.createClass({ }); }, + reset() { + this.setState(this.getInitialState()); + }, + render() { return (
diff --git a/js/components/ascribe_forms/input_fineuploader.js b/js/components/ascribe_forms/input_fineuploader.js new file mode 100644 index 00000000..52e1d5b5 --- /dev/null +++ b/js/components/ascribe_forms/input_fineuploader.js @@ -0,0 +1,95 @@ +'use strict'; + +import React from 'react'; + +import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader'; + +import AppConstants from '../../constants/application_constants'; +import ApiUrls from '../../constants/api_urls'; + +import { getCookie } from '../../utils/fetch_api_utils'; + +let InputFileUploader = React.createClass({ + propTypes: { + setIsUploadReady: React.PropTypes.func, + isReadyForFormSubmission: React.PropTypes.func, + onClick: React.PropTypes.func, + + // isFineUploaderActive is used to lock react fine uploader in case + // a user is actually not logged in already to prevent him from droping files + // before login in + isFineUploaderActive: React.PropTypes.bool, + onLoggedOut: React.PropTypes.func, + editable: React.PropTypes.bool, + enableLocalHashing: React.PropTypes.bool, + + // provided by Property + disabled: React.PropTypes.bool + }, + + getInitialState() { + return { + value: null + }; + }, + + submitKey(key){ + this.setState({ + value: key + }); + }, + + reset() { + this.refs.fineuploader.reset(); + }, + + render() { + let editable = this.props.isFineUploaderActive; + + // if disabled is actually set by property, we want to override + // isFineUploaderActive + if(typeof this.props.disabled !== 'undefined') { + editable = !this.props.disabled; + } + + return ( + + ); + } +}); + +export default InputFileUploader; \ No newline at end of file diff --git a/js/components/ascribe_forms/input_textarea_toggable.js b/js/components/ascribe_forms/input_textarea_toggable.js index bc70c530..ac3994a7 100644 --- a/js/components/ascribe_forms/input_textarea_toggable.js +++ b/js/components/ascribe_forms/input_textarea_toggable.js @@ -4,6 +4,7 @@ import React from 'react'; import TextareaAutosize from 'react-textarea-autosize'; + let InputTextAreaToggable = React.createClass({ propTypes: { editable: React.PropTypes.bool.isRequired, @@ -17,14 +18,17 @@ let InputTextAreaToggable = React.createClass({ value: this.props.defaultValue }; }, + handleChange(event) { this.setState({value: event.target.value}); this.props.onChange(event); }, + render() { let className = 'form-control ascribe-textarea'; let textarea = null; - if (this.props.editable){ + + if(this.props.editable) { className = className + ' ascribe-textarea-editable'; textarea = ( ); - } - else{ + } else { textarea =
{this.state.value}
; } + return textarea; } }); diff --git a/js/components/ascribe_forms/property.js b/js/components/ascribe_forms/property.js index cfc4c4d9..61c5c96e 100644 --- a/js/components/ascribe_forms/property.js +++ b/js/components/ascribe_forms/property.js @@ -29,8 +29,11 @@ let Property = React.createClass({ handleChange: React.PropTypes.func, ignoreFocus: React.PropTypes.bool, className: React.PropTypes.string, + onClick: React.PropTypes.func, onChange: React.PropTypes.func, + onBlur: React.PropTypes.func, + children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element @@ -96,12 +99,20 @@ let Property = React.createClass({ // resets the value of a plain HTML5 input this.refs.input.getDOMNode().value = this.state.initialValue; + // For some inputs, reseting state.value is not enough to visually reset the + // component. + // + // So if the input actually needs a visual reset, it needs to implement + // a dedicated reset method. + if(this.refs.input.reset && typeof this.refs.input.reset === 'function') { + this.refs.input.reset(); + } }, handleChange(event) { this.props.handleChange(event); - if ('onChange' in this.props) { + if (this.props.onChange && typeof this.props.onChange === 'function') { this.props.onChange(event); } @@ -117,7 +128,7 @@ let Property = React.createClass({ // if onClick is defined from the outside, // just call it - if(this.props.onClick) { + if(this.props.onClick && typeof this.props.onClick === 'function') { this.props.onClick(); } @@ -132,7 +143,7 @@ let Property = React.createClass({ isFocused: false }); - if(this.props.onBlur) { + if(this.props.onBlur && typeof this.props.onBlur === 'function') { this.props.onBlur(event); } }, @@ -190,6 +201,7 @@ let Property = React.createClass({ }, render() { + let footer = null; let tooltip = ; let style = this.props.style ? mergeOptions({}, this.props.style) : {}; @@ -199,7 +211,7 @@ let Property = React.createClass({ {this.props.tooltip} ); } - let footer = null; + if(this.props.footer){ footer = (
diff --git a/js/components/ascribe_forms/property_collapsible.js b/js/components/ascribe_forms/property_collapsible.js index 03ec404d..ef9a1329 100644 --- a/js/components/ascribe_forms/property_collapsible.js +++ b/js/components/ascribe_forms/property_collapsible.js @@ -42,6 +42,13 @@ let PropertyCollapsile = React.createClass({ } }, + reset() { + // If the child input is a native HTML element, it will be reset automatically + // by the DOM. + // However, we need to collapse this component again. + this.setState(this.getInitialState()); + }, + render() { let tooltip = ; if (this.props.tooltip){ diff --git a/js/components/ascribe_media/media_player.js b/js/components/ascribe_media/media_player.js index 09fafd27..e767a800 100644 --- a/js/components/ascribe_media/media_player.js +++ b/js/components/ascribe_media/media_player.js @@ -28,12 +28,20 @@ let Other = React.createClass({ }, render() { - let ext = this.props.url.split('.').pop(); + let filename = this.props.url.split('/').pop(); + let tokens = filename.split('.'); + let preview; + + if (tokens.length > 1) { + preview = '.' + tokens.pop(); + } else { + preview = 'file'; + } return (

- .{ext} + {preview}

); diff --git a/js/components/ascribe_slides_container/slides_container.js b/js/components/ascribe_slides_container/slides_container.js index 8b800377..84dff61c 100644 --- a/js/components/ascribe_slides_container/slides_container.js +++ b/js/components/ascribe_slides_container/slides_container.js @@ -4,13 +4,12 @@ import React from 'react'; import Router from 'react-router'; import ReactAddons from 'react/addons'; -import Col from 'react-bootstrap/lib/Col'; - import SlidesContainerBreadcrumbs from './slides_container_breadcrumbs'; let State = Router.State; let Navigation = Router.Navigation; + let SlidesContainer = React.createClass({ propTypes: { children: React.PropTypes.arrayOf(React.PropTypes.element), @@ -30,12 +29,15 @@ let SlidesContainer = React.createClass({ let slideNum = -1; let startFrom = -1; + // We can actually need to check if slide_num is present as a key in queryParams. + // We do not really care about its value though... if(queryParams && 'slide_num' in queryParams) { slideNum = parseInt(queryParams.slide_num, 10); } // if slide_num is not set, this will be done in componentDidMount // the query param 'start_from' removes all slide children before the respective number + // Also, we use the 'in' keyword for the same reason as above in 'slide_num' if(queryParams && 'start_from' in queryParams) { startFrom = parseInt(queryParams.start_from, 10); } @@ -51,6 +53,9 @@ let SlidesContainer = React.createClass({ componentDidMount() { // check if slide_num was defined, and if not then default to 0 let queryParams = this.getQuery(); + + // We use 'in' to check if the key is present in the user's browser url bar, + // we do not really care about its value at this point if(!('slide_num' in queryParams)) { // we're first requiring all the other possible queryParams and then set diff --git a/js/components/ascribe_uploader/react_s3_fine_uploader.js b/js/components/ascribe_uploader/react_s3_fine_uploader.js index c2ec9d56..54a7c39c 100644 --- a/js/components/ascribe_uploader/react_s3_fine_uploader.js +++ b/js/components/ascribe_uploader/react_s3_fine_uploader.js @@ -20,7 +20,6 @@ import AppConstants from '../../constants/application_constants'; import { computeHashOfFile, displayValidFilesFilter } from '../../utils/file_utils'; var ReactS3FineUploader = React.createClass({ - propTypes: { keyRoutine: React.PropTypes.shape({ url: React.PropTypes.string, @@ -125,6 +124,7 @@ var ReactS3FineUploader = React.createClass({ bucket: 'ascribe0' }, request: { + //endpoint: 'https://www.ascribe.io.global.prod.fastly.net', endpoint: 'https://ascribe0.s3.amazonaws.com', accessKey: 'AKIAIVCZJ33WSCBQ3QDA' }, @@ -235,6 +235,21 @@ var ReactS3FineUploader = React.createClass({ }; }, + // Resets the whole react fineuploader component to its initial state + reset() { + // Cancel all currently ongoing uploads + this.state.uploader.cancelAll(); + + // and reset component in general + this.state.uploader.reset(); + + // proclaim that upload is not ready + this.props.setIsUploadReady(false); + + // reset internal data structures of component + this.setState(this.getInitialState()); + }, + requestKey(fileId) { let filename = this.state.uploader.getName(fileId); let uuid = this.state.uploader.getUuid(fileId); @@ -349,62 +364,56 @@ var ReactS3FineUploader = React.createClass({ onComplete(id, name, res, xhr) { // there has been an issue with the server's connection - if(xhr.status === 0) { - - console.logGlobal(new Error('Complete was called but there wasn\t a success'), false, { + if((xhr && xhr.status === 0) || res.error) { + console.logGlobal(new Error(res.error || 'Complete was called but there wasn\t a success'), false, { files: this.state.filesToUpload, chunks: this.state.chunks }); + } else { + let files = this.state.filesToUpload; - return; - } + // Set the state of the completed file to 'upload successful' in order to + // remove it from the GUI + files[id].status = 'upload successful'; + files[id].key = this.state.uploader.getKey(id); - let files = this.state.filesToUpload; + let filesToUpload = React.addons.update(this.state.filesToUpload, { $set: files }); + this.setState({ filesToUpload }); - // Set the state of the completed file to 'upload successful' in order to - // remove it from the GUI - files[id].status = 'upload successful'; - files[id].key = this.state.uploader.getKey(id); - - let filesToUpload = React.addons.update(this.state.filesToUpload, { $set: files }); - - this.setState({ filesToUpload }); - - // Only after the blob has been created server-side, we can make the form submittable. - this.createBlob(files[id]) - .then(() => { - // since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey - // are optional, we'll only trigger them when they're actually defined - if(this.props.submitKey) { - this.props.submitKey(files[id].key); - } else { - console.warn('You didn\'t define submitKey in as a prop in react-s3-fine-uploader'); - } - - // for explanation, check comment of if statement above - if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) { - // also, lets check if after the completion of this upload, - // the form is ready for submission or not - if(this.props.isReadyForFormSubmission(this.state.filesToUpload)) { - // if so, set uploadstatus to true - this.props.setIsUploadReady(true); + // Only after the blob has been created server-side, we can make the form submittable. + this.createBlob(files[id]) + .then(() => { + // since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey + // are optional, we'll only trigger them when they're actually defined + if(this.props.submitKey) { + this.props.submitKey(files[id].key); } else { - this.props.setIsUploadReady(false); + console.warn('You didn\'t define submitKey in as a prop in react-s3-fine-uploader'); } - } else { - console.warn('You didn\'t define the functions isReadyForFormSubmission and/or setIsUploadReady in as a prop in react-s3-fine-uploader'); - } - }) - .catch((err) => { - console.logGlobal(err, false, { - files: this.state.filesToUpload, - chunks: this.state.chunks + + // for explanation, check comment of if statement above + if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) { + // also, lets check if after the completion of this upload, + // the form is ready for submission or not + if(this.props.isReadyForFormSubmission(this.state.filesToUpload)) { + // if so, set uploadstatus to true + this.props.setIsUploadReady(true); + } else { + this.props.setIsUploadReady(false); + } + } else { + console.warn('You didn\'t define the functions isReadyForFormSubmission and/or setIsUploadReady in as a prop in react-s3-fine-uploader'); + } + }) + .catch((err) => { + console.logGlobal(err, false, { + files: this.state.filesToUpload, + chunks: this.state.chunks + }); + let notification = new GlobalNotificationModel(err.message, 'danger', 5000); + GlobalNotificationActions.appendGlobalNotification(notification); }); - let notification = new GlobalNotificationModel(err.message, 'danger', 5000); - GlobalNotificationActions.appendGlobalNotification(notification); - }); - - + } }, onError(id, name, errorReason) { diff --git a/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js b/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js index 4b785fc2..822557db 100644 --- a/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js +++ b/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js @@ -90,14 +90,23 @@ let PieceContainer = React.createClass({ }, render() { - if('title' in this.state.piece) { + if(this.state.piece && this.state.piece.title) { + /* + + This really needs a refactor! + + - Tim + + */ // Only show the artist name if you are the participant or if you are a judge and the piece is shortlisted let artistName = ((this.state.currentUser.is_jury && !this.state.currentUser.is_judge) || (this.state.currentUser.is_judge && !this.state.piece.selected )) ?