1
0
mirror of https://github.com/ascribe/onion.git synced 2024-06-28 00:28:00 +02:00

Merge remote-tracking branch 'remotes/origin/AD-456-ikonotv-branded-page-for-registra' into AD-943-add-custom-additional-fields

This commit is contained in:
diminator 2015-09-16 15:59:07 +02:00
commit b24d4608dd
26 changed files with 550 additions and 289 deletions

View File

@ -30,7 +30,7 @@ class ContractListActions {
changeContract(contract){
return Q.Promise((resolve, reject) => {
OwnershipFetcher.makeContractPublic(contract)
OwnershipFetcher.changeContract(contract)
.then((res) => {
resolve(res);
})

View File

@ -38,9 +38,9 @@ let FurtherDetails = React.createClass({
GlobalNotificationActions.appendGlobalNotification(notification);
},
submitKey(key){
submitFile(file){
this.setState({
otherDataKey: key
otherDataKey: file.key
});
},
@ -78,7 +78,7 @@ let FurtherDetails = React.createClass({
extraData={this.props.extraData} />
<Form>
<FurtherDetailsFileuploader
submitKey={this.submitKey}
submitFile={this.submitFile}
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
editable={this.props.editable}

View File

@ -17,7 +17,7 @@ let FurtherDetailsFileuploader = React.createClass({
pieceId: React.PropTypes.number,
otherData: React.PropTypes.arrayOf(React.PropTypes.object),
setIsUploadReady: React.PropTypes.func,
submitKey: React.PropTypes.func,
submitFile: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func,
editable: React.PropTypes.bool,
multiple: React.PropTypes.bool
@ -55,11 +55,8 @@ let FurtherDetailsFileuploader = React.createClass({
url: ApiUrls.blob_otherdatas,
pieceId: this.props.pieceId
}}
validation={{
itemLimit: 100000,
sizeLimit: '50000000'
}}
submitKey={this.props.submitKey}
validation={AppConstants.fineUploader.validation.additionalData}
submitFile={this.props.submitFile}
setIsUploadReady={this.props.setIsUploadReady}
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
session={{

View File

@ -4,45 +4,40 @@ import React from 'react';
import Form from '../ascribe_forms/form';
import Property from '../ascribe_forms/property';
import InputCheckbox from '../ascribe_forms/input_checkbox';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import ContractListActions from '../../actions/contract_list_actions';
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
import AppConstants from '../../constants/application_constants';
import ApiUrls from '../../constants/api_urls';
import InputFineUploader from './input_fineuploader';
import { getLangText } from '../../utils/lang_utils';
import { getCookie } from '../../utils/fetch_api_utils';
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils';
let CreateContractForm = React.createClass({
propTypes: {
isPublic: React.PropTypes.bool,
// 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
})
},
getInitialState() {
return {
contractKey: null,
isUploadReady: false
isUploadReady: false,
contractName: ''
};
},
getFormData(){
return {
blob: this.state.contractKey
};
},
submitKey(key) {
this.setState({
contractKey: key
});
},
setIsUploadReady(isReady) {
this.setState({
isUploadReady: isReady
@ -56,31 +51,25 @@ let CreateContractForm = React.createClass({
this.refs.form.reset();
},
submitFileName(fileName) {
this.setState({
contractName: fileName
});
this.refs.form.submit();
},
render() {
return (
<Form
ref='form'
url={ApiUrls.ownership_contract_list}
getFormData={this.getFormData}
handleSuccess={this.handleCreateSuccess}
buttons={
<button
type="submit"
className="btn ascribe-btn ascribe-btn-login"
disabled={!this.state.isUploadReady}>
{getLangText('Create new contract')}
</button>
}
spinner={
<span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</span>
}>
handleSuccess={this.handleCreateSuccess}>
<Property
label="Contract file">
<ReactS3FineUploader
ref='uploader'
name="blob"
label={getLangText('Contract file (*.pdf only, max. 50MB per contract)')}>
<InputFineUploader
submitFileName={this.submitFileName}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'contract'
@ -89,46 +78,30 @@ let CreateContractForm = React.createClass({
url: ApiUrls.blob_contracts
}}
validation={{
itemLimit: 100000,
sizeLimit: '50000000'
}}
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)
}
itemLimit: AppConstants.fineUploader.validation.additionalData.itemLimit,
sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit,
allowedExtensions: ['pdf']
}}
areAssetsDownloadable={true}
areAssetsEditable={true}
submitKey={this.submitKey}
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}/>
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
fileClassToUpload={this.props.fileClassToUpload}/>
</Property>
<Property
name='name'
label={getLangText('Contract name')}>
label={getLangText('Contract name')}
hidden={true}>
<input
type="text"
placeholder="(e.g. Contract - Loan agreement #1)"
required/>
value={this.state.contractName}/>
</Property>
<Property
name="is_public"
className="ascribe-settings-property-collapsible-toggle"
style={{paddingBottom: 0}}>
<InputCheckbox>
<span>
Make contract public (this will replace the current public contract)
</span>
</InputCheckbox>
hidden={true}>
<input
type="checkbox"
value={this.props.isPublic} />
</Property>
</Form>
);

View File

@ -10,6 +10,7 @@ import Property from './property';
import InputFineUploader from './input_fineuploader';
import ApiUrls from '../../constants/api_urls';
import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils';
import { mergeOptions } from '../../utils/general_utils';
@ -99,6 +100,14 @@ let RegisterPieceForm = React.createClass({
name="digital_work_key"
ignoreFocus={true}>
<InputFineUploader
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'digitalwork'
}}
createBlobRoutine={{
url: ApiUrls.blob_digitalworks
}}
validation={AppConstants.fineUploader.validation.registerWork}
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
isFineUploaderActive={this.props.isFineUploaderActive}

View File

@ -5,7 +5,6 @@ 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';
@ -13,7 +12,21 @@ let InputFileUploader = React.createClass({
propTypes: {
setIsUploadReady: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func,
submitFileName: React.PropTypes.func,
onClick: React.PropTypes.func,
keyRoutine: React.PropTypes.shape({
url: React.PropTypes.string,
fileClass: React.PropTypes.string
}),
createBlobRoutine: React.PropTypes.shape({
url: React.PropTypes.string
}),
validation: React.PropTypes.shape({
itemLimit: React.PropTypes.number,
sizeLimit: React.PropTypes.string,
allowedExtensions: React.PropTypes.arrayOf(React.PropTypes.string)
}),
// isFineUploaderActive is used to lock react fine uploader in case
// a user is actually not logged in already to prevent him from droping files
@ -24,7 +37,14 @@ let InputFileUploader = React.createClass({
enableLocalHashing: React.PropTypes.bool,
// provided by Property
disabled: React.PropTypes.bool
disabled: React.PropTypes.bool,
// 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
})
},
getInitialState() {
@ -33,10 +53,14 @@ let InputFileUploader = React.createClass({
};
},
submitKey(key){
submitFile(file){
this.setState({
value: key
value: file.key
});
if(typeof this.props.submitFileName === 'function') {
this.props.submitFileName(file.originalName);
}
},
reset() {
@ -56,18 +80,10 @@ let InputFileUploader = React.createClass({
<ReactS3FineUploader
ref="fineuploader"
onClick={this.props.onClick}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'digitalwork'
}}
createBlobRoutine={{
url: ApiUrls.blob_digitalworks
}}
submitKey={this.submitKey}
validation={{
itemLimit: 100000,
sizeLimit: '25000000000'
}}
keyRoutine={this.props.keyRoutine}
createBlobRoutine={this.props.createBlobRoutine}
validation={this.props.validation}
submitFile={this.submitFile}
setIsUploadReady={this.props.setIsUploadReady}
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
areAssetsDownloadable={false}
@ -87,7 +103,8 @@ let InputFileUploader = React.createClass({
}
}}
onInactive={this.props.onLoggedOut}
enableLocalHashing={this.props.enableLocalHashing} />
enableLocalHashing={this.props.enableLocalHashing}
fileClassToUpload={this.props.fileClassToUpload}/>
);
}
});

View File

@ -70,7 +70,7 @@ let Property = React.createClass({
// In order to set this.state.value from another component
// the state of value should only be set if its not undefined and
// actually references something
if(typeof childInput.getDOMNode().value !== 'undefined') {
if(childInput && typeof childInput.getDOMNode().value !== 'undefined') {
this.setState({
value: childInput.getDOMNode().value
});

View File

@ -9,12 +9,14 @@ import ContractListStore from '../../stores/contract_list_store';
import ContractListActions from '../../actions/contract_list_actions';
import ActionPanel from '../ascribe_panel/action_panel';
import ContractSettingsUpdateButton from './contract_settings_update_button';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getLangText } from '../../utils/lang_utils';
let ContractSettings = React.createClass({
propTypes: {
defaultExpanded: React.PropTypes.bool
@ -37,23 +39,6 @@ let ContractSettings = React.createClass({
this.setState(state);
},
makeContractPublic(contract) {
return () => {
contract.is_public = true;
ContractListActions.changeContract(contract)
.then(() => {
ContractListActions.fetchContractList(true);
let notification = getLangText('Contract %s is now public', contract.name);
notification = new GlobalNotificationModel(notification, 'success', 4000);
GlobalNotificationActions.appendGlobalNotification(notification);
})
.catch((err) => {
let notification = new GlobalNotificationModel(err, 'danger', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
});
};
},
removeContract(contract) {
return () => {
ContractListActions.removeContract(contract.id)
@ -80,81 +65,92 @@ let ContractSettings = React.createClass({
render() {
let publicContracts = this.getPublicContracts();
let privateContracts = this.getPrivateContracts();
let createPublicContractForm = null;
if(publicContracts.length === 0) {
createPublicContractForm = (
<CreateContractForm
isPublic={true}
fileClassToUpload={{
singular: 'new public contract',
plural: 'new public contracts'
}}/>
);
}
return (
<CollapsibleParagraph
title={getLangText('Contract Settings')}
title={getLangText('Contracts')}
show={true}
defaultExpanded={true}>
<CollapsibleParagraph
title={getLangText('List Contracts')}
title={getLangText('Public Contracts')}
show={true}
defaultExpanded={true}>
<CollapsibleParagraph
title={getLangText('Public Contracts')}
show={true}
defaultExpanded={true}>
{publicContracts.map((contract, i) => {
return (
<ActionPanel
key={i}
title={contract.name}
content={contract.name}
buttons={
<div className="pull-right">
<button className="btn btn-default btn-sm margin-left-2px">
UPDATE
</button>
<button
className="btn btn-default btn-sm margin-left-2px"
onClick={this.removeContract(contract)}>
REMOVE
</button>
</div>
}
leftColumnWidth="40%"
rightColumnWidth="60%"/>
);
})}
</CollapsibleParagraph>
<CollapsibleParagraph
title={getLangText('Private Contracts')}
show={true}
defaultExpanded={true}>
{privateContracts.map((contract, i) => {
return (
<ActionPanel
key={i}
title={contract.name}
content={contract.name}
buttons={
<div className="pull-right">
<button className="btn btn-default btn-sm margin-left-2px">
UPDATE
</button>
<button
className="btn btn-default btn-sm margin-left-2px"
onClick={this.removeContract(contract)}>
REMOVE
</button>
<button
className="btn btn-default btn-sm margin-left-2px"
onClick={this.makeContractPublic(contract)}>
MAKE PUBLIC
</button>
</div>
}
leftColumnWidth="40%"
rightColumnWidth="60%"/>
);
})}
</CollapsibleParagraph>
{createPublicContractForm}
{publicContracts.map((contract, i) => {
return (
<ActionPanel
key={i}
title={contract.name}
content={contract.name}
buttons={
<div className="pull-right">
<ContractSettingsUpdateButton contract={contract}/>
<a
className="btn btn-default btn-sm margin-left-2px"
href={contract.blob.url_safe}
target="_blank">
{getLangText('PREVIEW')}
</a>
<button
className="btn btn-default btn-sm margin-left-2px"
onClick={this.removeContract(contract)}>
{getLangText('REMOVE')}
</button>
</div>
}
leftColumnWidth="40%"
rightColumnWidth="60%"/>
);
})}
</CollapsibleParagraph>
<CollapsibleParagraph
title={getLangText('Create Contract')}
title={getLangText('Private Contracts')}
show={true}
defaultExpanded={true}>
<CreateContractForm />
<CreateContractForm
isPublic={false}
fileClassToUpload={{
singular: getLangText('new private contract'),
plural: getLangText('new private contracts')
}}/>
{privateContracts.map((contract, i) => {
return (
<ActionPanel
key={i}
title={contract.name}
content={contract.name}
buttons={
<div className="pull-right">
<ContractSettingsUpdateButton contract={contract} />
<a
className="btn btn-default btn-sm margin-left-2px"
href={contract.blob.url_safe}
target="_blank">
{getLangText('PREVIEW')}
</a>
<button
className="btn btn-default btn-sm margin-left-2px"
onClick={this.removeContract(contract)}>
{getLangText('REMOVE')}
</button>
</div>
}
leftColumnWidth="40%"
rightColumnWidth="60%"/>
);
})}
</CollapsibleParagraph>
</CollapsibleParagraph>
);

View File

@ -0,0 +1,98 @@
'use strict';
import React from 'react';
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
import UploadButton from '../ascribe_uploader/ascribe_upload_button/upload_button';
import AppConstants from '../../constants/application_constants';
import ApiUrls from '../../constants/api_urls';
import ContractListActions from '../../actions/contract_list_actions';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils';
import { getCookie } from '../../utils/fetch_api_utils';
import { getLangText } from '../../utils/lang_utils';
let ContractSettingsUpdateButton = React.createClass({
propTypes: {
contract: React.PropTypes.object
},
submitFile(file) {
let contract = this.props.contract;
// override the blob with the key's value
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);
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);
GlobalNotificationActions.appendGlobalNotification(notification);
});
},
render() {
return (
<ReactS3FineUploader
ref="fineuploader"
fileInputElement={UploadButton}
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}
/>
);
}
});
export default ContractSettingsUpdateButton;

View File

@ -26,8 +26,6 @@ let SettingsContainer = React.createClass({
<APISettings />
<BitcoinWalletSettings />
<ContractSettings />
<br />
<br />
</div>
);
}

View File

@ -6,20 +6,14 @@ import ProgressBar from 'react-bootstrap/lib/ProgressBar';
import FileDragAndDropDialog from './file_drag_and_drop_dialog';
import FileDragAndDropPreviewIterator from './file_drag_and_drop_preview_iterator';
import { getLangText } from '../../utils/lang_utils';
import { getLangText } from '../../../utils/lang_utils';
// Taken from: https://github.com/fedosejev/react-file-drag-and-drop
let FileDragAndDrop = React.createClass({
propTypes: {
className: React.PropTypes.string,
onDragStart: React.PropTypes.func,
onDrop: React.PropTypes.func.isRequired,
onDrag: React.PropTypes.func,
onDragEnter: React.PropTypes.func,
onLeave: React.PropTypes.func,
onDragLeave: React.PropTypes.func,
onDragOver: React.PropTypes.func,
onDragEnd: React.PropTypes.func,
onInactive: React.PropTypes.func,
filesToUpload: React.PropTypes.array,
handleDeleteFile: React.PropTypes.func,
@ -37,37 +31,16 @@ let FileDragAndDrop = React.createClass({
hashingProgress: React.PropTypes.number,
// sets the value of this.state.hashingProgress in reactfineuploader
// to -1 which is code for: aborted
handleCancelHashing: React.PropTypes.func
},
handleCancelHashing: React.PropTypes.func,
handleDragStart(event) {
if (typeof this.props.onDragStart === 'function') {
this.props.onDragStart(event);
}
},
// 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
}),
handleDrag(event) {
if (typeof this.props.onDrag === 'function') {
this.props.onDrag(event);
}
},
handleDragEnd(event) {
if (typeof this.props.onDragEnd === 'function') {
this.props.onDragEnd(event);
}
},
handleDragEnter(event) {
if (typeof this.props.onDragEnter === 'function') {
this.props.onDragEnter(event);
}
},
handleDragLeave(event) {
if (typeof this.props.onDragLeave === 'function') {
this.props.onDragLeave(event);
}
allowedExtensions: React.PropTypes.string
},
handleDragOver(event) {
@ -159,14 +132,27 @@ let FileDragAndDrop = React.createClass({
},
render: function () {
let { filesToUpload,
dropzoneInactive,
className,
hashingProgress,
handleCancelHashing,
multiple,
enableLocalHashing,
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 = this.props.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1).length > 0;
let className = hasFiles ? 'has-files ' : '';
className += this.props.dropzoneInactive ? 'inactive-dropzone' : 'active-dropzone';
className += this.props.className ? ' ' + this.props.className : '';
let hasFiles = filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1).length > 0;
let updatedClassName = hasFiles ? 'has-files ' : '';
updatedClassName += dropzoneInactive ? 'inactive-dropzone' : 'active-dropzone';
updatedClassName += ' file-drag-and-drop';
// if !== -2: triggers a FileDragAndDrop-global spinner
if(this.props.hashingProgress !== -2) {
if(hashingProgress !== -2) {
return (
<div className={className}>
<div className="file-drag-and-drop-hashing-dialog">
@ -184,29 +170,26 @@ let FileDragAndDrop = React.createClass({
} else {
return (
<div
className={className}
onDragStart={this.handleDragStart}
className={updatedClassName}
onDrag={this.handleDrop}
onDragEnter={this.handleDragEnter}
onDragLeave={this.handleDragLeave}
onDragOver={this.handleDragOver}
onDrop={this.handleDrop}
onDragEnd={this.handleDragEnd}>
onDrop={this.handleDrop}>
<FileDragAndDropDialog
multipleFiles={this.props.multiple}
multipleFiles={multiple}
hasFiles={hasFiles}
onClick={this.handleOnClick}
enableLocalHashing={this.props.enableLocalHashing}/>
enableLocalHashing={enableLocalHashing}
fileClassToUpload={fileClassToUpload}/>
<FileDragAndDropPreviewIterator
files={this.props.filesToUpload}
files={filesToUpload}
handleDeleteFile={this.handleDeleteFile}
handleCancelFile={this.handleCancelFile}
handlePauseFile={this.handlePauseFile}
handleResumeFile={this.handleResumeFile}
areAssetsDownloadable={this.props.areAssetsDownloadable}
areAssetsEditable={this.props.areAssetsEditable}/>
areAssetsDownloadable={areAssetsDownloadable}
areAssetsEditable={areAssetsEditable}/>
<input
multiple={this.props.multiple}
multiple={multiple}
ref="fileinput"
type="file"
style={{
@ -214,7 +197,8 @@ let FileDragAndDrop = React.createClass({
height: 0,
width: 0
}}
onChange={this.handleDrop} />
onChange={this.handleDrop}
accept={allowedExtensions}/>
</div>
);
}

View File

@ -3,7 +3,7 @@
import React from 'react';
import Router from 'react-router';
import { getLangText } from '../../utils/lang_utils';
import { getLangText } from '../../../utils/lang_utils';
let Link = Router.Link;
@ -12,7 +12,14 @@ let FileDragAndDropDialog = React.createClass({
hasFiles: React.PropTypes.bool,
multipleFiles: React.PropTypes.bool,
onClick: React.PropTypes.func,
enableLocalHashing: React.PropTypes.bool
enableLocalHashing: React.PropTypes.bool,
// 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
})
},
mixins: [Router.State],
@ -56,29 +63,29 @@ let FileDragAndDropDialog = React.createClass({
} else {
if(this.props.multipleFiles) {
return (
<div className="file-drag-and-drop-dialog">
<p>{getLangText('Drag files here')}</p>
<span className="file-drag-and-drop-dialog">
<p>{getLangText('Drag %s here', this.props.fileClassToUpload.plural)}</p>
<p>{getLangText('or')}</p>
<span
className="btn btn-default"
onClick={this.props.onClick}>
{getLangText('choose files to upload')}
{getLangText('choose %s to upload', this.props.fileClassToUpload.plural)}
</span>
</div>
</span>
);
} else {
let dialog = queryParams.method === 'hash' ? getLangText('choose a file to hash') : getLangText('choose a file to upload');
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);
return (
<div className="file-drag-and-drop-dialog">
<p>{getLangText('Drag a file here')}</p>
<span className="file-drag-and-drop-dialog">
<p>{getLangText('Drag a %s here', this.props.fileClassToUpload.singular)}</p>
<p>{getLangText('or')}</p>
<span
className="btn btn-default"
onClick={this.props.onClick}>
{dialog}
</span>
</div>
</span>
);
}
}

View File

@ -4,7 +4,9 @@ import React from 'react';
import FileDragAndDropPreviewImage from './file_drag_and_drop_preview_image';
import FileDragAndDropPreviewOther from './file_drag_and_drop_preview_other';
import { getLangText } from '../../utils/lang_utils.js';
import { getLangText } from '../../../utils/lang_utils';
let FileDragAndDropPreview = React.createClass({
@ -43,6 +45,7 @@ let FileDragAndDropPreview = React.createClass({
handleDownloadFile() {
if(this.props.file.s3Url) {
// This simply opens a new browser tab with the url provided
open(this.props.file.s3Url);
}
},

View File

@ -3,8 +3,8 @@
import React from 'react';
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils.js';
import AppConstants from '../../../constants/application_constants';
import { getLangText } from '../../../utils/lang_utils';
let FileDragAndDropPreviewImage = React.createClass({
propTypes: {

View File

@ -5,7 +5,7 @@ import React from 'react';
import FileDragAndDropPreview from './file_drag_and_drop_preview';
import FileDragAndDropPreviewProgress from './file_drag_and_drop_preview_progress';
import { displayValidFilesFilter } from './react_s3_fine_uploader_utils';
import { displayValidFilesFilter } from '../react_s3_fine_uploader_utils';
let FileDragAndDropPreviewIterator = React.createClass({

View File

@ -3,8 +3,8 @@
import React from 'react';
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils.js';
import AppConstants from '../../../constants/application_constants';
import { getLangText } from '../../../utils/lang_utils';
let FileDragAndDropPreviewOther = React.createClass({
propTypes: {
@ -61,7 +61,7 @@ let FileDragAndDropPreviewOther = React.createClass({
<div className="file-drag-and-drop-preview-table-wrapper">
<div className="file-drag-and-drop-preview-other">
{actionSymbol}
<span>{'.' + this.props.type}</span>
<p>{'.' + this.props.type}</p>
</div>
</div>
</div>

View File

@ -4,7 +4,8 @@ import React from 'react';
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
import { displayValidProgressFilesFilter } from './react_s3_fine_uploader_utils';
import { displayValidProgressFilesFilter } from '../react_s3_fine_uploader_utils';
import { getLangText } from '../../../utils/lang_utils';
let FileDragAndDropPreviewProgress = React.createClass({
@ -54,7 +55,7 @@ let FileDragAndDropPreviewProgress = React.createClass({
return (
<ProgressBar
now={Math.ceil(overallProgress)}
label="Overall progress: %(percent)s%"
label={getLangText('Overall progress%s', ': %(percent)s%')}
className="ascribe-progress-bar"
style={style} />
);

View File

@ -0,0 +1,103 @@
'use strict';
import React from 'react';
import { displayValidProgressFilesFilter } from '../react_s3_fine_uploader_utils';
import { getLangText } from '../../../utils/lang_utils';
let UploadButton = React.createClass({
propTypes: {
onDrop: React.PropTypes.func.isRequired,
filesToUpload: React.PropTypes.array,
multiple: React.PropTypes.bool,
// For simplification purposes we're just going to use this prop as a
// label for the upload button
fileClassToUpload: React.PropTypes.shape({
singular: React.PropTypes.string,
plural: React.PropTypes.string
}),
allowedExtensions: React.PropTypes.string
},
handleDrop(event) {
event.preventDefault();
event.stopPropagation();
let files = event.target.files;
if(typeof this.props.onDrop === 'function' && files) {
this.props.onDrop(files);
}
},
getUploadingFiles() {
return this.props.filesToUpload.filter((file) => file.status === 'uploading');
},
handleOnClick() {
let uploadingFiles = this.getUploadingFiles();
// We only want the button to be clickable if there are no files currently uploading
if(uploadingFiles.length === 0) {
// Firefox only recognizes the simulated mouse click if bubbles is set to true,
// but since Google Chrome propagates the event much further than needed, we
// need to stop propagation as soon as the event is created
var evt = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
});
evt.stopPropagation();
this.refs.fileinput.getDOMNode().dispatchEvent(evt);
}
},
getButtonLabel() {
let { filesToUpload, fileClassToUpload } = this.props;
// filter invalid files that might have been deleted or canceled...
filesToUpload = filesToUpload.filter(displayValidProgressFilesFilter);
// Depending on wether there is an upload going on or not we
// display the progress
if(filesToUpload.length > 0) {
return getLangText('Upload progress') + ': ' + Math.ceil(filesToUpload[0].progress) + '%';
} else {
return fileClassToUpload.singular;
}
},
render() {
let {
multiple,
fileClassToUpload,
allowedExtensions
} = this.props;
return (
<button
onClick={this.handleOnClick}
className="btn btn-default btn-sm margin-left-2px"
disabled={this.getUploadingFiles().length !== 0}>
{this.getButtonLabel()}
<input
multiple={multiple}
ref="fileinput"
type="file"
style={{
display: 'none',
height: 0,
width: 0
}}
onChange={this.handleDrop}
accept={allowedExtensions}/>
</button>
);
}
});
export default UploadButton;

View File

@ -1,13 +1,13 @@
'use strict';
import React from 'react/addons';
import fineUploader from 'fineUploader';
import Router from 'react-router';
import Q from 'q';
import S3Fetcher from '../../fetchers/s3_fetcher';
import fineUploader from 'fineUploader';
import FileDragAndDrop from './file_drag_and_drop';
import FileDragAndDrop from './ascribe_file_drag_and_drop/file_drag_and_drop';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
@ -15,10 +15,11 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
import AppConstants from '../../constants/application_constants';
import { computeHashOfFile } from '../../utils/file_utils';
import { displayValidFilesFilter } from './react_s3_fine_uploader_utils';
import { displayValidFilesFilter, transformAllowedExtensionsToInputAcceptProp } from './react_s3_fine_uploader_utils';
import { getCookie } from '../../utils/fetch_api_utils';
import { getLangText } from '../../utils/lang_utils';
let ReactS3FineUploader = React.createClass({
propTypes: {
keyRoutine: React.PropTypes.shape({
@ -36,7 +37,7 @@ let ReactS3FineUploader = React.createClass({
React.PropTypes.number
])
}),
submitKey: React.PropTypes.func,
submitFile: React.PropTypes.func,
autoUpload: React.PropTypes.bool,
debug: React.PropTypes.bool,
objectProperties: React.PropTypes.shape({
@ -83,7 +84,8 @@ let ReactS3FineUploader = React.createClass({
}),
validation: React.PropTypes.shape({
itemLimit: React.PropTypes.number,
sizeLimit: React.PropTypes.string
sizeLimit: React.PropTypes.string,
allowedExtensions: React.PropTypes.arrayOf(React.PropTypes.string)
}),
messages: React.PropTypes.shape({
unsupportedBrowser: React.PropTypes.string
@ -110,7 +112,22 @@ let ReactS3FineUploader = React.createClass({
enableLocalHashing: React.PropTypes.bool,
// automatically injected by React-Router
query: React.PropTypes.object
query: React.PropTypes.object,
// 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
}),
// Uploading functionality of react fineuploader is disconnected from its UI
// layer, which means that literally every (properly adjusted) react element
// can handle the UI handling.
fileInputElement: React.PropTypes.oneOfType([
React.PropTypes.func,
React.PropTypes.element
])
},
mixins: [Router.State],
@ -162,7 +179,12 @@ let ReactS3FineUploader = React.createClass({
return name;
},
multiple: false,
defaultErrorMessage: getLangText('Unexpected error. Please contact us if this happens repeatedly.')
defaultErrorMessage: getLangText('Unexpected error. Please contact us if this happens repeatedly.'),
fileClassToUpload: {
singular: getLangText('file'),
plural: getLangText('files')
},
fileInputElement: FileDragAndDrop
};
},
@ -386,12 +408,12 @@ let ReactS3FineUploader = React.createClass({
// 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
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitFile
// are optional, we'll only trigger them when they're actually defined
if(this.props.submitKey) {
this.props.submitKey(files[id].key);
if(this.props.submitFile) {
this.props.submitFile(files[id]);
} else {
console.warn('You didn\'t define submitKey in as a prop in react-s3-fine-uploader');
console.warn('You didn\'t define submitFile in as a prop in react-s3-fine-uploader');
}
// for explanation, check comment of if statement above
@ -426,7 +448,7 @@ let ReactS3FineUploader = React.createClass({
});
this.state.uploader.cancelAll();
let notification = new GlobalNotificationModel(this.props.defaultErrorMessage, 'danger', 5000);
let notification = new GlobalNotificationModel(errorReason || this.props.defaultErrorMessage, 'danger', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
@ -451,7 +473,7 @@ let ReactS3FineUploader = React.createClass({
let notification = new GlobalNotificationModel(getLangText('File upload canceled'), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitFile
// are optional, we'll only trigger them when they're actually defined
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
if(this.props.isReadyForFormSubmission(this.state.filesToUpload)) {
@ -518,7 +540,7 @@ let ReactS3FineUploader = React.createClass({
GlobalNotificationActions.appendGlobalNotification(notification);
}
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitFile
// are optional, we'll only trigger them when they're actually defined
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
// also, lets check if after the completion of this upload,
@ -818,27 +840,48 @@ let ReactS3FineUploader = React.createClass({
},
getAllowedExtensions() {
let { validation } = this.props;
if(validation && validation.allowedExtensions && validation.allowedExtensions.length > 0) {
return transformAllowedExtensionsToInputAcceptProp(validation.allowedExtensions);
} else {
return null;
}
},
render() {
return (
<div>
<FileDragAndDrop
className="file-drag-and-drop"
onDrop={this.handleUploadFile}
filesToUpload={this.state.filesToUpload}
handleDeleteFile={this.handleDeleteFile}
handleCancelFile={this.handleCancelFile}
handlePauseFile={this.handlePauseFile}
handleResumeFile={this.handleResumeFile}
handleCancelHashing={this.handleCancelHashing}
multiple={this.props.multiple}
areAssetsDownloadable={this.props.areAssetsDownloadable}
areAssetsEditable={this.props.areAssetsEditable}
onInactive={this.props.onInactive}
dropzoneInactive={this.isDropzoneInactive()}
hashingProgress={this.state.hashingProgress}
enableLocalHashing={this.props.enableLocalHashing} />
</div>
);
let {
multiple,
areAssetsDownloadable,
areAssetsEditable,
onInactive,
enableLocalHashing,
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.
return React.createElement(fileInputElement, {
onDrop: this.handleUploadFile,
filesToUpload: this.state.filesToUpload,
handleDeleteFile: this.handleDeleteFile,
handleCancelFile: this.handleCancelFile,
handlePauseFile: this.handlePauseFile,
handleResumeFile: this.handleResumeFile,
handleCancelHashing: this.handleCancelHashing,
multiple: multiple,
areAssetsDownloadable: areAssetsDownloadable,
areAssetsEditable: areAssetsEditable,
onInactive: onInactive,
dropzoneInactive: this.isDropzoneInactive(),
hashingProgress: this.state.hashingProgress,
enableLocalHashing: enableLocalHashing,
fileClassToUpload: fileClassToUpload,
allowedExtensions: this.getAllowedExtensions()
});
}
});

View File

@ -52,3 +52,22 @@ export function displayValidProgressFilesFilter(file) {
return file.status !== 'deleted' && file.status !== 'canceled' && file.status !== 'online';
}
/**
* Fineuploader allows to specify the file extensions that are allowed to upload.
* For our self defined input, we can reuse those declarations to restrict which files
* the user can pick from his hard drive.
*
* Takes an array of file extensions (['pdf', 'png', ...]) and transforms them into a string
* that can be passed into an html5 input via its 'accept' prop.
* @param {array} allowedExtensions Array of strings without a dot prefixed
* @return {string} Joined string (comma-separated) of the passed-in array
*/
export function transformAllowedExtensionsToInputAcceptProp(allowedExtensions) {
// add a dot in front of the extension
let prefixedAllowedExtensions = allowedExtensions.map((ext) => '.' + ext);
// generate a comma separated list to add them to the DOM element
// See: http://stackoverflow.com/questions/4328947/limit-file-format-when-using-input-type-file
return prefixedAllowedExtensions.join(', ');
}

View File

@ -112,7 +112,7 @@ let CylandAdditionalDataForm = React.createClass({
</Property>
<FurtherDetailsFileuploader
uploadStarted={this.uploadStarted}
submitKey={this.submitKey}
submitFile={this.submitFile}
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={formSubmissionValidation.fileOptional}
editable={!this.props.disabled}

View File

@ -57,6 +57,19 @@ let constants = {
// Source: http://www.w3schools.com/tags/att_input_type.asp
'possibleInputTypes': ['button', 'checkbox', 'color', 'date', 'datetime', 'datetime-local', 'email', 'file', 'hidden', 'image', 'month', 'number', 'password', 'radio', 'range', 'reset', 'search', 'submit', 'tel', 'text', 'time', 'url', 'week'],
'fineUploader': {
'validation': {
'additionalData': {
'itemLimit': 100,
'sizeLimit': '50000000'
},
'registerWork': {
'itemLimit': 1,
'sizeLimit': '25000000000'
}
}
},
// in case of whitelabel customization, we store stuff here
'whitelabel': {},
'raven': {

View File

@ -48,7 +48,7 @@ let OwnershipFetcher = {
return requests.get(ApiUrls.ownership_loans_pieces_request);
},
makeContractPublic(contractObj){
changeContract(contractObj){
return requests.put(ApiUrls.ownership_contract, { body: contractObj, contract_id: contractObj.id });
},

View File

@ -24,9 +24,8 @@
background: none;
border: 0;
width: 100%;
/* Shrink the size of the headline for a nested element */
.ascribe-collapsible-wrapper > .ascribe-collapsible-content {
.ascribe-collapsible-wrapper {
padding-left: 1em;
font-size: 95%;
}

View File

@ -9,6 +9,7 @@
text-align: center;
vertical-align: middle;
cursor: default !important;
padding: 1.5em 0 1.5em 0;
.file-drag-and-drop-dialog > p:first-child {
font-size: 1.5em !important;

View File

@ -486,4 +486,4 @@ hr {
.ascribe-progress-bar-xs {
height: 12px;
}
}