From 418022dff506025313bd315d6a6756dca08ffe08 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 21 Dec 2015 11:40:59 +0100 Subject: [PATCH 1/4] wip --- .../further_details_fileuploader.js | 2 ++ .../ascribe_forms/form_register_piece.js | 10 ++++++++-- .../ascribe_forms/input_fineuploader.js | 3 +++ .../ascribe_uploader/react_s3_fine_uploader.js | 12 ++++++++---- .../pr_forms/pr_register_piece_form.js | 17 +++++++++++++---- js/constants/application_constants.js | 4 ++++ 6 files changed, 38 insertions(+), 10 deletions(-) diff --git a/js/components/ascribe_detail/further_details_fileuploader.js b/js/components/ascribe_detail/further_details_fileuploader.js index b044bbbc..acdbe9e0 100644 --- a/js/components/ascribe_detail/further_details_fileuploader.js +++ b/js/components/ascribe_detail/further_details_fileuploader.js @@ -20,6 +20,7 @@ let FurtherDetailsFileuploader = React.createClass({ otherData: React.PropTypes.arrayOf(React.PropTypes.object), setIsUploadReady: React.PropTypes.func, submitFile: React.PropTypes.func, + onValidationFailed: React.PropTypes.func, isReadyForFormSubmission: React.PropTypes.func, editable: React.PropTypes.bool, multiple: React.PropTypes.bool @@ -60,6 +61,7 @@ let FurtherDetailsFileuploader = React.createClass({ }} validation={AppConstants.fineUploader.validation.additionalData} submitFile={this.props.submitFile} + onValidationFailed={this.props.onValidationFailed} setIsUploadReady={this.props.setIsUploadReady} isReadyForFormSubmission={this.props.isReadyForFormSubmission} session={{ diff --git a/js/components/ascribe_forms/form_register_piece.js b/js/components/ascribe_forms/form_register_piece.js index 83d38b50..596f8a56 100644 --- a/js/components/ascribe_forms/form_register_piece.js +++ b/js/components/ascribe_forms/form_register_piece.js @@ -109,6 +109,11 @@ let RegisterPieceForm = React.createClass({ ); }, + handleThumbnailValidationFailed(thumbnailFile) { + // If the validation fails, set the thumbnail as submittable since its optional + this.refs.submitButton.setReadyStateForKey('thumbnailKeyReady', true); + }, + isThumbnailDialogExpanded() { const { enableSeparateThumbnail } = this.props; const { digitalWorkFile } = this.state; @@ -194,14 +199,15 @@ let RegisterPieceForm = React.createClass({ url: ApiUrls.blob_thumbnails }} handleChangedFile={this.handleChangedThumbnail} + onValidationFailed={this.handleThumbnailValidationFailed} isReadyForFormSubmission={formSubmissionValidation.fileOptional} keyRoutine={{ url: AppConstants.serverUrl + 's3/key/', fileClass: 'thumbnail' }} validation={{ - itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit, - sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit, + itemLimit: AppConstants.fineUploader.validation.workThumbnail.itemLimit, + sizeLimit: AppConstants.fineUploader.validation.workThumbnail.sizeLimit, allowedExtensions: ['png', 'jpg', 'jpeg', 'gif'] }} setIsUploadReady={this.setIsUploadReady('thumbnailKeyReady')} diff --git a/js/components/ascribe_forms/input_fineuploader.js b/js/components/ascribe_forms/input_fineuploader.js index db5bae05..fa9c72b6 100644 --- a/js/components/ascribe_forms/input_fineuploader.js +++ b/js/components/ascribe_forms/input_fineuploader.js @@ -52,6 +52,7 @@ const InputFineUploader = React.createClass({ plural: string }), handleChangedFile: func, + onValidationFailed: func, // Provided by `Property` onChange: React.PropTypes.func @@ -107,6 +108,7 @@ const InputFineUploader = React.createClass({ isFineUploaderActive, isReadyForFormSubmission, keyRoutine, + onValidationFailed, setIsUploadReady, uploadMethod, validation, @@ -127,6 +129,7 @@ const InputFineUploader = React.createClass({ createBlobRoutine={createBlobRoutine} validation={validation} submitFile={this.submitFile} + onValidationFailed={onValidationFailed} setIsUploadReady={setIsUploadReady} isReadyForFormSubmission={isReadyForFormSubmission} areAssetsDownloadable={areAssetsDownloadable} diff --git a/js/components/ascribe_uploader/react_s3_fine_uploader.js b/js/components/ascribe_uploader/react_s3_fine_uploader.js index eb211504..877146a5 100644 --- a/js/components/ascribe_uploader/react_s3_fine_uploader.js +++ b/js/components/ascribe_uploader/react_s3_fine_uploader.js @@ -50,6 +50,7 @@ const ReactS3FineUploader = React.createClass({ }), handleChangedFile: func, // is for when a file is dropped or selected submitFile: func, // is for when a file has been successfully uploaded, TODO: rename to handleSubmitFile + onValidationFailed: func, autoUpload: bool, debug: bool, objectProperties: shape({ @@ -523,13 +524,16 @@ const ReactS3FineUploader = React.createClass({ }, isFileValid(file) { - if(file.size > this.props.validation.sizeLimit) { + if (file.size > this.props.validation.sizeLimit) { + const fileSizeInMegaBytes = this.props.validation.sizeLimit / 1000000; - let fileSizeInMegaBytes = this.props.validation.sizeLimit / 1000000; - - let notification = new GlobalNotificationModel(getLangText('A file you submitted is bigger than ' + fileSizeInMegaBytes + 'MB.'), 'danger', 5000); + const notification = new GlobalNotificationModel(getLangText('A file you submitted is bigger than ' + fileSizeInMegaBytes + 'MB.'), 'danger', 5000); GlobalNotificationActions.appendGlobalNotification(notification); + if (typeof this.props.onValidationFailed === 'function') { + this.props.onValidationFailed(file); + } + return false; } else { return true; diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js index a8d946b5..6b67467e 100644 --- a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js +++ b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js @@ -139,7 +139,7 @@ const PRRegisterPieceForm = React.createClass({ }, /** - * This method is overloaded so that we can track the ready-state + * These two methods are overloaded so that we can track the ready-state * of each uploader in the component * @param {string} uploaderKey Name of the uploader's key to track */ @@ -151,6 +151,14 @@ const PRRegisterPieceForm = React.createClass({ }; }, + handleOptionalFileValidationFailed(uploaderKey) { + return () => { + this.setState({ + [uploaderKey]: true + }); + }; + }, + getSubmitButton() { const { digitalWorkKeyReady, thumbnailKeyReady, @@ -303,7 +311,7 @@ const PRRegisterPieceForm = React.createClass({ + label={getLangText('Featured Cover photo (max 2MB)')}> Date: Mon, 21 Dec 2015 11:44:13 +0100 Subject: [PATCH 2/4] Small cleanups and fixes for spacing, unused props, etc --- js/actions/contract_list_actions.js | 16 ++--- .../ascribe_settings/contract_settings.js | 36 ++++------ .../contract_settings_update_button.js | 68 +++++++++---------- js/fetchers/ownership_fetcher.js | 14 ++-- 4 files changed, 61 insertions(+), 73 deletions(-) diff --git a/js/actions/contract_list_actions.js b/js/actions/contract_list_actions.js index 1c5c0913..d368ac73 100644 --- a/js/actions/contract_list_actions.js +++ b/js/actions/contract_list_actions.js @@ -28,12 +28,10 @@ class ContractListActions { } - changeContract(contract){ + changeContract(contract) { return Q.Promise((resolve, reject) => { OwnershipFetcher.changeContract(contract) - .then((res) => { - resolve(res); - }) + .then(resolve) .catch((err)=> { console.logGlobal(err); reject(err); @@ -41,13 +39,11 @@ class ContractListActions { }); } - removeContract(contractId){ - return Q.Promise( (resolve, reject) => { + removeContract(contractId) { + return Q.Promise((resolve, reject) => { OwnershipFetcher.deleteContract(contractId) - .then((res) => { - resolve(res); - }) - .catch( (err) => { + .then(resolve) + .catch((err) => { console.logGlobal(err); reject(err); }); diff --git a/js/components/ascribe_settings/contract_settings.js b/js/components/ascribe_settings/contract_settings.js index 71d97542..be723295 100644 --- a/js/components/ascribe_settings/contract_settings.js +++ b/js/components/ascribe_settings/contract_settings.js @@ -28,11 +28,7 @@ import { mergeOptions, truncateTextAtCharIndex } from '../../utils/general_utils let ContractSettings = React.createClass({ - propTypes: { - location: React.PropTypes.object - }, - - getInitialState(){ + getInitialState() { return mergeOptions( ContractListStore.getState(), UserStore.getState() @@ -64,40 +60,39 @@ let ContractSettings = React.createClass({ ContractListActions.removeContract(contract.id) .then((response) => { ContractListActions.fetchContractList(true); - let notification = new GlobalNotificationModel(response.notification, 'success', 4000); + const notification = new GlobalNotificationModel(response.notification, 'success', 4000); GlobalNotificationActions.appendGlobalNotification(notification); }) .catch((err) => { - let notification = new GlobalNotificationModel(err, 'danger', 10000); + const notification = new GlobalNotificationModel(err, 'danger', 10000); GlobalNotificationActions.appendGlobalNotification(notification); }); }; }, - getPublicContracts(){ + getPublicContracts() { return this.state.contractList.filter((contract) => contract.is_public); }, - getPrivateContracts(){ + getPrivateContracts() { return this.state.contractList.filter((contract) => !contract.is_public); }, render() { - let publicContracts = this.getPublicContracts(); - let privateContracts = this.getPrivateContracts(); + const publicContracts = this.getPublicContracts(); + const privateContracts = this.getPrivateContracts(); let createPublicContractForm = null; setDocumentTitle(getLangText('Contracts settings')); - if(publicContracts.length === 0) { + if (publicContracts.length === 0) { createPublicContractForm = ( + }} /> ); } @@ -114,7 +109,7 @@ let ContractSettings = React.createClass({ {publicContracts.map((contract, i) => { return ( + contract={contract} /> + }} /> {privateContracts.map((contract, i) => { return ( + contract={contract} /> {/* So that ReactS3FineUploader is not complaining */}} - signature={{ - endpoint: AppConstants.serverUrl + 's3/signature/', - customHeaders: { - 'X-CSRFToken': getCookie(AppConstants.csrftoken) - } - }} - deleteFile={{ - enabled: true, - method: 'DELETE', - endpoint: AppConstants.serverUrl + 's3/delete', - customHeaders: { - 'X-CSRFToken': getCookie(AppConstants.csrftoken) - } - }} - fileClassToUpload={{ - singular: getLangText('UPDATE'), - plural: getLangText('UPDATE') - }} - isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile} - submitFile={this.submitFile} /> + fileInputElement={UploadButton({ showLabel: false })} + keyRoutine={{ + url: AppConstants.serverUrl + 's3/key/', + fileClass: 'contract' + }} + createBlobRoutine={{ + url: ApiUrls.blob_contracts + }} + validation={{ + itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit, + sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit, + allowedExtensions: ['pdf'] + }} + setIsUploadReady={() =>{/* So that ReactS3FineUploader is not complaining */}} + signature={{ + endpoint: AppConstants.serverUrl + 's3/signature/', + customHeaders: { + 'X-CSRFToken': getCookie(AppConstants.csrftoken) + } + }} + deleteFile={{ + enabled: true, + method: 'DELETE', + endpoint: AppConstants.serverUrl + 's3/delete', + customHeaders: { + 'X-CSRFToken': getCookie(AppConstants.csrftoken) + } + }} + fileClassToUpload={{ + singular: getLangText('UPDATE'), + plural: getLangText('UPDATE') + }} + isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile} + submitFile={this.submitFile} /> ); } }); diff --git a/js/fetchers/ownership_fetcher.js b/js/fetchers/ownership_fetcher.js index b0d88927..f498ffa1 100644 --- a/js/fetchers/ownership_fetcher.js +++ b/js/fetchers/ownership_fetcher.js @@ -15,7 +15,7 @@ let OwnershipFetcher = { /** * Fetch the contracts of the logged-in user from the API. */ - fetchContractList(isActive, isPublic, issuer){ + fetchContractList(isActive, isPublic, issuer) { let queryParams = { isActive, isPublic, @@ -28,7 +28,7 @@ let OwnershipFetcher = { /** * Create a contractagreement between the logged-in user and the email from the API with contract. */ - createContractAgreement(signee, contractObj){ + createContractAgreement(signee, contractObj) { return requests.post(ApiUrls.ownership_contract_agreements, { body: {signee: signee, contract: contractObj.id }}); }, @@ -44,23 +44,23 @@ let OwnershipFetcher = { return requests.get(ApiUrls.ownership_contract_agreements, queryParams); }, - confirmContractAgreement(contractAgreement){ + confirmContractAgreement(contractAgreement) { return requests.put(ApiUrls.ownership_contract_agreements_confirm, {contract_agreement_id: contractAgreement.id}); }, - denyContractAgreement(contractAgreement){ + denyContractAgreement(contractAgreement) { return requests.put(ApiUrls.ownership_contract_agreements_deny, {contract_agreement_id: contractAgreement.id}); }, - fetchLoanPieceRequestList(){ + fetchLoanPieceRequestList() { return requests.get(ApiUrls.ownership_loans_pieces_request); }, - changeContract(contractObj){ + changeContract(contractObj) { return requests.put(ApiUrls.ownership_contract, { body: contractObj, contract_id: contractObj.id }); }, - deleteContract(contractObjId){ + deleteContract(contractObjId) { return requests.delete(ApiUrls.ownership_contract, {contract_id: contractObjId}); } }; From 3467e9f526cdcbb4cb895656da34f6be31354287 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 21 Dec 2015 11:45:50 +0100 Subject: [PATCH 3/4] Make behaviour of ContractSettingsUpdateButton more robust MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also fixes the “Contract could not be updated” notification that would occur after every attempt to update the contract. --- .../contract_settings_update_button.js | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/js/components/ascribe_settings/contract_settings_update_button.js b/js/components/ascribe_settings/contract_settings_update_button.js index c09ba94f..f3e92c69 100644 --- a/js/components/ascribe_settings/contract_settings_update_button.js +++ b/js/components/ascribe_settings/contract_settings_update_button.js @@ -24,37 +24,41 @@ let ContractSettingsUpdateButton = React.createClass({ }, submitFile(file) { - let contract = this.props.contract; - // override the blob with the key's value - contract.blob = file.key; + const contract = Object.assign(this.props.contract, { blob: file.key }); // send it to the server ContractListActions .changeContract(contract) .then((res) => { - // Display feedback to the user - let notification = new GlobalNotificationModel(getLangText('Contract %s successfully updated', res.name), 'success', 5000); + const notification = new GlobalNotificationModel(getLangText('Contract %s successfully updated', contract.name), 'success', 5000); GlobalNotificationActions.appendGlobalNotification(notification); // and refresh the contract list to get the updated contracs - return ContractListActions.fetchContractList(true); - }) - .then(() => { - // Also, reset the fineuploader component so that the user can again 'update' his contract - this.refs.fineuploader.reset(); - }) - .catch((err) => { - console.logGlobal(err); - let notification = new GlobalNotificationModel(getLangText('Contract could not be updated'), 'success', 5000); + return ContractListActions + .fetchContractList(true) + // Also, reset the fineuploader component if fetch is successful so that the user can again 'update' his contract + .then(this.refs.fineuploader.reset) + .catch((err) => { + const notification = new GlobalNotificationModel(getLangText('Latest contract failed to load'), 'danger', 5000); + GlobalNotificationActions.appendGlobalNotification(notification); + + return Promise.reject(err); + }); + }, (err) => { + const notification = new GlobalNotificationModel(getLangText('Contract could not be updated'), 'danger', 5000); GlobalNotificationActions.appendGlobalNotification(notification); - }); + + return Promise.reject(err); + }) + .catch(console.logGlobal); }, render() { return ( Date: Mon, 21 Dec 2015 11:46:46 +0100 Subject: [PATCH 4/4] Add prop to hide label of UploadButton --- .../ascribe_upload_button/upload_button.js | 47 ++++++++++--------- sass/ascribe_uploader.scss | 23 +++++---- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/js/components/ascribe_uploader/ascribe_upload_button/upload_button.js b/js/components/ascribe_uploader/ascribe_upload_button/upload_button.js index 6612f968..ffd26a13 100644 --- a/js/components/ascribe_uploader/ascribe_upload_button/upload_button.js +++ b/js/components/ascribe_uploader/ascribe_upload_button/upload_button.js @@ -1,6 +1,7 @@ 'use strict'; import React from 'react'; +import classNames from 'classnames'; import { displayValidProgressFilesFilter } from '../react_s3_fine_uploader_utils'; import { getLangText } from '../../../utils/lang_utils'; @@ -9,7 +10,7 @@ import { truncateTextAtCharIndex } from '../../../utils/general_utils'; const { func, array, bool, shape, string } = React.PropTypes; -export default function UploadButton({ className = 'btn btn-default btn-sm' } = {}) { +export default function UploadButton({ className = 'btn btn-default btn-sm', showLabel = true } = {}) { return React.createClass({ displayName: 'UploadButton', @@ -119,28 +120,28 @@ export default function UploadButton({ className = 'btn btn-default btn-sm' } = }, getUploadedFileLabel() { - const uploadedFile = this.getUploadedFile(); - const uploadingFiles = this.getUploadingFiles(); + if (showLabel) { + const uploadedFile = this.getUploadedFile(); + const uploadingFiles = this.getUploadingFiles(); - if(uploadingFiles.length) { - return ( - - {' ' + truncateTextAtCharIndex(uploadingFiles[0].name, 40) + ' '} - [{getLangText('cancel upload')}] - - ); - } else if(uploadedFile) { - return ( - - - {' ' + truncateTextAtCharIndex(uploadedFile.name, 40) + ' '} - [{getLangText('remove')}] - - ); - } else { - return ( - {getLangText('No file chosen')} - ); + if (uploadingFiles.length) { + return ( + + {' ' + truncateTextAtCharIndex(uploadingFiles[0].name, 40) + ' '} + [{getLangText('cancel upload')}] + + ); + } else if (uploadedFile) { + return ( + + + {' ' + truncateTextAtCharIndex(uploadedFile.name, 40) + ' '} + [{getLangText('remove')}] + + ); + } else { + return {getLangText('No file chosen')}; + } } }, @@ -158,7 +159,7 @@ export default function UploadButton({ className = 'btn btn-default btn-sm' } = * Therefore the wrapping component needs to be an `anchor` tag instead of a `button` */ return ( -
+
{/* The button needs to be of `type="button"` as it would otherwise submit the form its in. diff --git a/sass/ascribe_uploader.scss b/sass/ascribe_uploader.scss index e6dd66cc..e6228916 100644 --- a/sass/ascribe_uploader.scss +++ b/sass/ascribe_uploader.scss @@ -29,7 +29,7 @@ .file-drag-and-drop-dialog { margin: 1.5em 0 1.5em 0; - + > p:first-child { font-size: 1.5em !important; margin-bottom: 0; @@ -189,15 +189,20 @@ height: 12px; } -.upload-button-wrapper { +.ascribe-upload-button { + display: inline-block; text-align: left; - .btn { - font-size: 1em; - margin-right: 1em; - } + &.ascribe-upload-button-has-label { + display: block; - span + .btn { - margin-left: 1em; + .btn { + font-size: 1em; + margin-right: 1em; + } + + span + .btn { + margin-left: 1em; + } } -} \ No newline at end of file +}