mirror of
https://github.com/ascribe/onion.git
synced 2025-02-14 21:10:27 +01:00
started using favicon setter package browser icons there, ikono icon will change
This commit is contained in:
commit
f033a878d0
224
.scss-lint.yml
Normal file
224
.scss-lint.yml
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
linters:
|
||||||
|
BangFormat:
|
||||||
|
enabled: true
|
||||||
|
space_before_bang: true
|
||||||
|
space_after_bang: false
|
||||||
|
|
||||||
|
BemDepth:
|
||||||
|
enabled: false
|
||||||
|
max_elements: 1
|
||||||
|
|
||||||
|
BorderZero:
|
||||||
|
enabled: true
|
||||||
|
convention: zero # or `none`
|
||||||
|
|
||||||
|
ColorKeyword:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
ColorVariable:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
Comment:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
DebugStatement:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
DeclarationOrder:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
DisableLinterReason:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
DuplicateProperty:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
ElsePlacement:
|
||||||
|
enabled: true
|
||||||
|
style: same_line # or 'new_line'
|
||||||
|
|
||||||
|
EmptyLineBetweenBlocks:
|
||||||
|
enabled: true
|
||||||
|
ignore_single_line_blocks: true
|
||||||
|
|
||||||
|
EmptyRule:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
ExtendDirective:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
FinalNewline:
|
||||||
|
enabled: false
|
||||||
|
present: true
|
||||||
|
|
||||||
|
HexLength:
|
||||||
|
enabled: true
|
||||||
|
style: short # or 'long'
|
||||||
|
|
||||||
|
HexNotation:
|
||||||
|
enabled: true
|
||||||
|
style: lowercase # or 'uppercase'
|
||||||
|
|
||||||
|
HexValidation:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
IdSelector:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
ImportantRule:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
ImportPath:
|
||||||
|
enabled: true
|
||||||
|
leading_underscore: false
|
||||||
|
filename_extension: false
|
||||||
|
|
||||||
|
Indentation:
|
||||||
|
enabled: true
|
||||||
|
allow_non_nested_indentation: false
|
||||||
|
character: space # or 'tab'
|
||||||
|
width: 4
|
||||||
|
|
||||||
|
LeadingZero:
|
||||||
|
enabled: true
|
||||||
|
style: exclude_zero # or 'include_zero'
|
||||||
|
|
||||||
|
MergeableSelector:
|
||||||
|
enabled: true
|
||||||
|
force_nesting: true
|
||||||
|
|
||||||
|
NameFormat:
|
||||||
|
enabled: true
|
||||||
|
allow_leading_underscore: true
|
||||||
|
convention: hyphenated_lowercase # or 'camel_case', or 'snake_case', or a regex pattern
|
||||||
|
|
||||||
|
NestingDepth:
|
||||||
|
enabled: true
|
||||||
|
max_depth: 3
|
||||||
|
ignore_parent_selectors: false
|
||||||
|
|
||||||
|
PlaceholderInExtend:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
PropertyCount:
|
||||||
|
enabled: false
|
||||||
|
include_nested: false
|
||||||
|
max_properties: 10
|
||||||
|
|
||||||
|
PropertySortOrder:
|
||||||
|
enabled: false
|
||||||
|
ignore_unspecified: false
|
||||||
|
min_properties: 2
|
||||||
|
separate_groups: false
|
||||||
|
|
||||||
|
PropertySpelling:
|
||||||
|
enabled: true
|
||||||
|
extra_properties: []
|
||||||
|
|
||||||
|
PropertyUnits:
|
||||||
|
enabled: true
|
||||||
|
global: [
|
||||||
|
'ch', 'em', 'ex', 'rem', # Font-relative lengths
|
||||||
|
'cm', 'in', 'mm', 'pc', 'pt', 'px', 'q', # Absolute lengths
|
||||||
|
'vh', 'vw', 'vmin', 'vmax', # Viewport-percentage lengths
|
||||||
|
'deg', 'grad', 'rad', 'turn', # Angle
|
||||||
|
'ms', 's', # Duration
|
||||||
|
'Hz', 'kHz', # Frequency
|
||||||
|
'dpi', 'dpcm', 'dppx', # Resolution
|
||||||
|
'%'] # Other
|
||||||
|
properties: {}
|
||||||
|
|
||||||
|
QualifyingElement:
|
||||||
|
enabled: true
|
||||||
|
allow_element_with_attribute: false
|
||||||
|
allow_element_with_class: false
|
||||||
|
allow_element_with_id: false
|
||||||
|
|
||||||
|
SelectorDepth:
|
||||||
|
enabled: true
|
||||||
|
max_depth: 3
|
||||||
|
|
||||||
|
SelectorFormat:
|
||||||
|
enabled: true
|
||||||
|
convention: hyphenated_lowercase # or 'strict_BEM', or 'hyphenated_BEM', or 'snake_case', or 'camel_case', or a regex pattern
|
||||||
|
|
||||||
|
Shorthand:
|
||||||
|
enabled: true
|
||||||
|
allowed_shorthands: [1, 2, 3]
|
||||||
|
|
||||||
|
SingleLinePerProperty:
|
||||||
|
enabled: true
|
||||||
|
allow_single_line_rule_sets: true
|
||||||
|
|
||||||
|
SingleLinePerSelector:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
SpaceAfterComma:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
SpaceAfterPropertyColon:
|
||||||
|
enabled: true
|
||||||
|
style: one_space # or 'no_space', or 'at_least_one_space', or 'aligned'
|
||||||
|
|
||||||
|
SpaceAfterPropertyName:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
SpaceAfterVariableName:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
SpaceAroundOperator:
|
||||||
|
enabled: true
|
||||||
|
style: one_space # or 'no_space'
|
||||||
|
|
||||||
|
SpaceBeforeBrace:
|
||||||
|
enabled: true
|
||||||
|
style: space # or 'new_line'
|
||||||
|
allow_single_line_padding: false
|
||||||
|
|
||||||
|
SpaceBetweenParens:
|
||||||
|
enabled: true
|
||||||
|
spaces: 0
|
||||||
|
|
||||||
|
StringQuotes:
|
||||||
|
enabled: true
|
||||||
|
style: single_quotes # or double_quotes
|
||||||
|
|
||||||
|
TrailingSemicolon:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
TrailingWhitespace:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
TrailingZero:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
TransitionAll:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
UnnecessaryMantissa:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
UnnecessaryParentReference:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
UrlFormat:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
UrlQuotes:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
VariableForProperty:
|
||||||
|
enabled: false
|
||||||
|
properties: []
|
||||||
|
|
||||||
|
VendorPrefix:
|
||||||
|
enabled: false
|
||||||
|
identifier_list: base
|
||||||
|
additional_identifiers: []
|
||||||
|
excluded_identifiers: []
|
||||||
|
|
||||||
|
ZeroUnit:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
Compass::*:
|
||||||
|
enabled: false
|
13
README.md
13
README.md
@ -32,8 +32,8 @@ Additionally, to work on the white labeling functionality, you need to edit your
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Code Conventions
|
JavaScript Code Conventions
|
||||||
================
|
===========================
|
||||||
For this project, we're using:
|
For this project, we're using:
|
||||||
|
|
||||||
* 4 Spaces
|
* 4 Spaces
|
||||||
@ -42,6 +42,15 @@ For this project, we're using:
|
|||||||
* We don't use camel case for file naming but in everything Javascript related
|
* We don't use camel case for file naming but in everything Javascript related
|
||||||
* We use `let` instead of `var`: [SA Post](http://stackoverflow.com/questions/762011/javascript-let-keyword-vs-var-keyword)
|
* We use `let` instead of `var`: [SA Post](http://stackoverflow.com/questions/762011/javascript-let-keyword-vs-var-keyword)
|
||||||
|
|
||||||
|
|
||||||
|
SCSS Code Conventions
|
||||||
|
=====================
|
||||||
|
Install [lint-scss](https://github.com/brigade/scss-lint), check the [editor integration docs](https://github.com/brigade/scss-lint#editor-integration) to integrate the lint in your editor.
|
||||||
|
|
||||||
|
Some interesting links:
|
||||||
|
* [Improving Sass code quality on theguardian.com](https://www.theguardian.com/info/developer-blog/2014/may/13/improving-sass-code-quality-on-theguardiancom)
|
||||||
|
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
===============
|
===============
|
||||||
We're using Facebook's jest to do testing as it integrates nicely with react.js as well.
|
We're using Facebook's jest to do testing as it integrates nicely with react.js as well.
|
||||||
|
113
js/actions/contract_agreement_list_actions.js
Normal file
113
js/actions/contract_agreement_list_actions.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import alt from '../alt';
|
||||||
|
import Q from 'q';
|
||||||
|
|
||||||
|
import OwnershipFetcher from '../fetchers/ownership_fetcher';
|
||||||
|
import ContractListActions from './contract_list_actions';
|
||||||
|
|
||||||
|
class ContractAgreementListActions {
|
||||||
|
constructor() {
|
||||||
|
this.generateActions(
|
||||||
|
'updateContractAgreementList',
|
||||||
|
'flushContractAgreementList'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchContractAgreementList(issuer, accepted, pending) {
|
||||||
|
this.actions.updateContractAgreementList(null);
|
||||||
|
return Q.Promise((resolve, reject) => {
|
||||||
|
OwnershipFetcher.fetchContractAgreementList(issuer, accepted, pending)
|
||||||
|
.then((contractAgreementList) => {
|
||||||
|
if (contractAgreementList.count > 0) {
|
||||||
|
this.actions.updateContractAgreementList(contractAgreementList.results);
|
||||||
|
resolve(contractAgreementList.results);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.logGlobal(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchAvailableContractAgreementList(issuer, createContractAgreement) {
|
||||||
|
return Q.Promise((resolve, reject) => {
|
||||||
|
OwnershipFetcher.fetchContractAgreementList(issuer, true, null)
|
||||||
|
.then((acceptedContractAgreementList) => {
|
||||||
|
// if there is at least an accepted contract agreement, we're going to
|
||||||
|
// use it
|
||||||
|
if(acceptedContractAgreementList.count > 0) {
|
||||||
|
this.actions.updateContractAgreementList(acceptedContractAgreementList.results);
|
||||||
|
} else {
|
||||||
|
// otherwise, we're looking for contract agreements that are still pending
|
||||||
|
//
|
||||||
|
// Normally nesting promises, but for this conditional one, it makes sense to not
|
||||||
|
// overcomplicate the method
|
||||||
|
OwnershipFetcher.fetchContractAgreementList(issuer, null, true)
|
||||||
|
.then((pendingContractAgreementList) => {
|
||||||
|
if(pendingContractAgreementList.count > 0) {
|
||||||
|
this.actions.updateContractAgreementList(pendingContractAgreementList.results);
|
||||||
|
} else {
|
||||||
|
// if there was neither a pending nor an active contractAgreement
|
||||||
|
// found and createContractAgreement is set to true, we create a
|
||||||
|
// new contract agreement
|
||||||
|
if(createContractAgreement) {
|
||||||
|
this.actions.createContractAgreementFromPublicContract(issuer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.logGlobal(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.logGlobal(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createContractAgreementFromPublicContract(issuer) {
|
||||||
|
ContractListActions.fetchContractList(null, null, issuer)
|
||||||
|
.then((publicContract) => {
|
||||||
|
// create an agreement with the public contract if there is one
|
||||||
|
if (publicContract && publicContract.length > 0) {
|
||||||
|
return this.actions.createContractAgreement(null, publicContract[0]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/*
|
||||||
|
contractAgreementList in the store is already set to null;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}).then((publicContracAgreement) => {
|
||||||
|
if (publicContracAgreement) {
|
||||||
|
this.actions.updateContractAgreementList([publicContracAgreement]);
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
console.logGlobal(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createContractAgreement(issuer, contract){
|
||||||
|
return Q.Promise((resolve, reject) => {
|
||||||
|
OwnershipFetcher.createContractAgreement(issuer, contract).then(
|
||||||
|
(contractAgreement) => {
|
||||||
|
resolve(contractAgreement);
|
||||||
|
}
|
||||||
|
).catch((err) => {
|
||||||
|
console.logGlobal(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createActions(ContractAgreementListActions);
|
58
js/actions/contract_list_actions.js
Normal file
58
js/actions/contract_list_actions.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import alt from '../alt';
|
||||||
|
import OwnershipFetcher from '../fetchers/ownership_fetcher';
|
||||||
|
import Q from 'q';
|
||||||
|
|
||||||
|
class ContractListActions {
|
||||||
|
constructor() {
|
||||||
|
this.generateActions(
|
||||||
|
'updateContractList',
|
||||||
|
'flushContractList'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchContractList(isActive, isPublic, issuer) {
|
||||||
|
return Q.Promise((resolve, reject) => {
|
||||||
|
OwnershipFetcher.fetchContractList(isActive, isPublic, issuer)
|
||||||
|
.then((contracts) => {
|
||||||
|
this.actions.updateContractList(contracts.results);
|
||||||
|
resolve(contracts.results);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.logGlobal(err);
|
||||||
|
this.actions.updateContractList([]);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
changeContract(contract){
|
||||||
|
return Q.Promise((resolve, reject) => {
|
||||||
|
OwnershipFetcher.changeContract(contract)
|
||||||
|
.then((res) => {
|
||||||
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch((err)=> {
|
||||||
|
console.logGlobal(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeContract(contractId){
|
||||||
|
return Q.Promise( (resolve, reject) => {
|
||||||
|
OwnershipFetcher.deleteContract(contractId)
|
||||||
|
.then((res) => {
|
||||||
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch( (err) => {
|
||||||
|
console.logGlobal(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createActions(ContractListActions);
|
@ -1,48 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
import alt from '../alt';
|
|
||||||
import OwnershipFetcher from '../fetchers/ownership_fetcher';
|
|
||||||
|
|
||||||
|
|
||||||
class LoanContractActions {
|
|
||||||
constructor() {
|
|
||||||
this.generateActions(
|
|
||||||
'updateLoanContract',
|
|
||||||
'flushLoanContract'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchLoanContract(email) {
|
|
||||||
if(email.match(/.+\@.+\..+/)) {
|
|
||||||
OwnershipFetcher.fetchLoanContract(email)
|
|
||||||
.then((contracts) => {
|
|
||||||
if (contracts && contracts.length > 0) {
|
|
||||||
this.actions.updateLoanContract({
|
|
||||||
contractKey: contracts[0].s3Key,
|
|
||||||
contractUrl: contracts[0].s3Url,
|
|
||||||
contractEmail: email
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.actions.updateLoanContract({
|
|
||||||
contractKey: null,
|
|
||||||
contractUrl: null,
|
|
||||||
contractEmail: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.logGlobal(err);
|
|
||||||
this.actions.updateLoanContract({
|
|
||||||
contractKey: null,
|
|
||||||
contractUrl: null,
|
|
||||||
contractEmail: null
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
/* No email was entered - Ignore and keep going*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default alt.createActions(LoanContractActions);
|
|
@ -1,27 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
import alt from '../alt';
|
|
||||||
import OwnershipFetcher from '../fetchers/ownership_fetcher';
|
|
||||||
|
|
||||||
|
|
||||||
class LoanContractListActions {
|
|
||||||
constructor() {
|
|
||||||
this.generateActions(
|
|
||||||
'updateLoanContractList',
|
|
||||||
'flushLoanContractList'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchLoanContractList() {
|
|
||||||
OwnershipFetcher.fetchLoanContractList()
|
|
||||||
.then((contracts) => {
|
|
||||||
this.actions.updateLoanContractList(contracts);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.logGlobal(err);
|
|
||||||
this.actions.updateLoanContractList([]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default alt.createActions(LoanContractListActions);
|
|
68
js/actions/notification_actions.js
Normal file
68
js/actions/notification_actions.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import alt from '../alt';
|
||||||
|
import Q from 'q';
|
||||||
|
|
||||||
|
import NotificationFetcher from '../fetchers/notification_fetcher';
|
||||||
|
|
||||||
|
class NotificationActions {
|
||||||
|
constructor() {
|
||||||
|
this.generateActions(
|
||||||
|
'updatePieceListNotifications',
|
||||||
|
'updateEditionListNotifications',
|
||||||
|
'updateEditionNotifications',
|
||||||
|
'updatePieceNotifications',
|
||||||
|
'updateContractAgreementListNotifications'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchPieceListNotifications() {
|
||||||
|
NotificationFetcher
|
||||||
|
.fetchPieceListNotifications()
|
||||||
|
.then((res) => {
|
||||||
|
this.actions.updatePieceListNotifications(res);
|
||||||
|
})
|
||||||
|
.catch((err) => console.logGlobal(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchPieceNotifications(pieceId) {
|
||||||
|
NotificationFetcher
|
||||||
|
.fetchPieceNotifications(pieceId)
|
||||||
|
.then((res) => {
|
||||||
|
this.actions.updatePieceNotifications(res);
|
||||||
|
})
|
||||||
|
.catch((err) => console.logGlobal(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchEditionListNotifications() {
|
||||||
|
NotificationFetcher
|
||||||
|
.fetchEditionListNotifications()
|
||||||
|
.then((res) => {
|
||||||
|
this.actions.updateEditionListNotifications(res);
|
||||||
|
})
|
||||||
|
.catch((err) => console.logGlobal(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchEditionNotifications(editionId) {
|
||||||
|
NotificationFetcher
|
||||||
|
.fetchEditionNotifications(editionId)
|
||||||
|
.then((res) => {
|
||||||
|
this.actions.updateEditionNotifications(res);
|
||||||
|
})
|
||||||
|
.catch((err) => console.logGlobal(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchContractAgreementListNotifications() {
|
||||||
|
return Q.Promise((resolve, reject) => {
|
||||||
|
NotificationFetcher
|
||||||
|
.fetchContractAgreementListNotifications()
|
||||||
|
.then((res) => {
|
||||||
|
this.actions.updateContractAgreementListNotifications(res);
|
||||||
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch((err) => console.logGlobal(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createActions(NotificationActions);
|
@ -57,7 +57,7 @@ class PieceListActions {
|
|||||||
PieceListFetcher
|
PieceListFetcher
|
||||||
.fetchRequestActions()
|
.fetchRequestActions()
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.actions.updatePieceListRequestActions(res.piece_ids);
|
this.actions.updatePieceListRequestActions(res);
|
||||||
})
|
})
|
||||||
.catch((err) => console.logGlobal(err));
|
.catch((err) => console.logGlobal(err));
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import EventActions from './actions/event_actions';
|
|||||||
import GoogleAnalyticsHandler from './third_party/ga';
|
import GoogleAnalyticsHandler from './third_party/ga';
|
||||||
import RavenHandler from './third_party/raven';
|
import RavenHandler from './third_party/raven';
|
||||||
import IntercomHandler from './third_party/intercom';
|
import IntercomHandler from './third_party/intercom';
|
||||||
|
import NotificationsHandler from './third_party/notifications';
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
||||||
initLogging();
|
initLogging();
|
||||||
@ -71,8 +72,10 @@ class AppGateway {
|
|||||||
subdomain = settings.subdomain;
|
subdomain = settings.subdomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.document.body.classList.add('client--' + subdomain);
|
||||||
|
|
||||||
EventActions.applicationWillBoot(settings);
|
EventActions.applicationWillBoot(settings);
|
||||||
Router.run(getRoutes(type, subdomain), Router.HistoryLocation, (App) => {
|
window.appRouter = Router.run(getRoutes(type, subdomain), Router.HistoryLocation, (App) => {
|
||||||
React.render(
|
React.render(
|
||||||
<App />,
|
<App />,
|
||||||
document.getElementById('main')
|
document.getElementById('main')
|
||||||
|
@ -20,21 +20,28 @@ let AclProxy = React.createClass({
|
|||||||
show: React.PropTypes.bool
|
show: React.PropTypes.bool
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
getChildren() {
|
||||||
if(this.props.show) {
|
if (React.Children.count(this.props.children) > 1){
|
||||||
|
/*
|
||||||
|
This might ruin styles for header items in the navbar etc
|
||||||
|
*/
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
/* can only do this when there is only 1 child, but will preserve styles */
|
||||||
|
return this.props.children;
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if(this.props.show) {
|
||||||
|
return this.getChildren();
|
||||||
} else {
|
} else {
|
||||||
if(this.props.aclObject) {
|
if(this.props.aclObject) {
|
||||||
if(this.props.aclObject[this.props.aclName]) {
|
if(this.props.aclObject[this.props.aclName]) {
|
||||||
return (
|
return this.getChildren();
|
||||||
<span>
|
|
||||||
{this.props.children}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
/* if(typeof this.props.aclObject[this.props.aclName] === 'undefined') {
|
/* if(typeof this.props.aclObject[this.props.aclName] === 'undefined') {
|
||||||
console.warn('The aclName you\'re filtering for was not present (or undefined) in the aclObject.');
|
console.warn('The aclName you\'re filtering for was not present (or undefined) in the aclObject.');
|
||||||
|
@ -21,7 +21,7 @@ let AccordionList = React.createClass({
|
|||||||
);
|
);
|
||||||
} else if(this.props.count === 0) {
|
} else if(this.props.count === 0) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="ascribe-accordion-list-placeholder">
|
||||||
<p className="text-center">{getLangText('We could not find any works related to you...')}</p>
|
<p className="text-center">{getLangText('We could not find any works related to you...')}</p>
|
||||||
<p className="text-center">{getLangText('To register one, click')} <a href="register_piece">{getLangText('here')}</a>!</p>
|
<p className="text-center">{getLangText('To register one, click')} <a href="register_piece">{getLangText('here')}</a>!</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,7 +6,6 @@ import classNames from 'classnames';
|
|||||||
import EditionListActions from '../../actions/edition_list_actions';
|
import EditionListActions from '../../actions/edition_list_actions';
|
||||||
import EditionListStore from '../../stores/edition_list_store';
|
import EditionListStore from '../../stores/edition_list_store';
|
||||||
|
|
||||||
import PieceListActions from '../../actions/piece_list_actions';
|
|
||||||
import PieceListStore from '../../stores/piece_list_store';
|
import PieceListStore from '../../stores/piece_list_store';
|
||||||
|
|
||||||
import Button from 'react-bootstrap/lib/Button';
|
import Button from 'react-bootstrap/lib/Button';
|
||||||
@ -16,6 +15,7 @@ import CreateEditionsButton from '../ascribe_buttons/create_editions_button';
|
|||||||
import { mergeOptions } from '../../utils/general_utils';
|
import { mergeOptions } from '../../utils/general_utils';
|
||||||
import { getLangText } from '../../utils/lang_utils';
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
|
|
||||||
|
|
||||||
let AccordionListItemEditionWidget = React.createClass({
|
let AccordionListItemEditionWidget = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
className: React.PropTypes.string,
|
className: React.PropTypes.string,
|
||||||
|
@ -160,7 +160,7 @@ let AccordionListItemTableEditions = React.createClass({
|
|||||||
let content = item.acl;
|
let content = item.acl;
|
||||||
return {
|
return {
|
||||||
'content': content,
|
'content': content,
|
||||||
'requestAction': item.request_action
|
'notifications': item.notifications
|
||||||
}; },
|
}; },
|
||||||
'acl',
|
'acl',
|
||||||
getLangText('Actions'),
|
getLangText('Actions'),
|
||||||
|
@ -61,12 +61,12 @@ let AccordionListItemWallet = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
getGlyphicon(){
|
getGlyphicon(){
|
||||||
if (this.props.content.requestAction && this.props.content.requestAction.length > 0) {
|
if ((this.props.content.notifications && this.props.content.notifications.length > 0)){
|
||||||
return (
|
return (
|
||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
delay={500}
|
delay={500}
|
||||||
placement="left"
|
placement="left"
|
||||||
overlay={<Tooltip>{getLangText('You have actions pending in one of your editions')}</Tooltip>}>
|
overlay={<Tooltip>{getLangText('You have actions pending')}</Tooltip>}>
|
||||||
<Glyphicon glyph='bell' color="green"/>
|
<Glyphicon glyph='bell' color="green"/>
|
||||||
</OverlayTrigger>);
|
</OverlayTrigger>);
|
||||||
}
|
}
|
||||||
|
@ -162,6 +162,7 @@ let AclButton = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if (this.props.availableAcls){
|
||||||
let shouldDisplay = this.props.availableAcls[this.props.action];
|
let shouldDisplay = this.props.availableAcls[this.props.action];
|
||||||
let aclProps = this.actionProperties();
|
let aclProps = this.actionProperties();
|
||||||
let buttonClassName = this.props.buttonAcceptClassName ? this.props.buttonAcceptClassName : '';
|
let buttonClassName = this.props.buttonAcceptClassName ? this.props.buttonAcceptClassName : '';
|
||||||
@ -178,6 +179,8 @@ let AclButton = React.createClass({
|
|||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default AclButton;
|
export default AclButton;
|
@ -17,7 +17,7 @@ const CollapsibleParagraph = React.createClass({
|
|||||||
|
|
||||||
getDefaultProps() {
|
getDefaultProps() {
|
||||||
return {
|
return {
|
||||||
show: false
|
show: true
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -38,14 +38,14 @@ const CollapsibleParagraph = React.createClass({
|
|||||||
if(this.props.show) {
|
if(this.props.show) {
|
||||||
return (
|
return (
|
||||||
<div className="ascribe-detail-header">
|
<div className="ascribe-detail-header">
|
||||||
<div className="ascribe-edition-collapsible-wrapper">
|
<div className="ascribe-collapsible-wrapper">
|
||||||
<div onClick={this.handleToggle}>
|
<div onClick={this.handleToggle}>
|
||||||
<span>{text} {this.props.title}</span>
|
<span>{text} {this.props.title}</span>
|
||||||
</div>
|
</div>
|
||||||
<Panel
|
<Panel
|
||||||
collapsible
|
collapsible
|
||||||
expanded={this.state.expanded}
|
expanded={this.state.expanded}
|
||||||
className="ascribe-edition-collapsible-content">
|
className="ascribe-collapsible-content">
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</Panel>
|
</Panel>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +25,7 @@ import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph
|
|||||||
import Form from './../ascribe_forms/form';
|
import Form from './../ascribe_forms/form';
|
||||||
import Property from './../ascribe_forms/property';
|
import Property from './../ascribe_forms/property';
|
||||||
import EditionDetailProperty from './detail_property';
|
import EditionDetailProperty from './detail_property';
|
||||||
|
import LicenseDetail from './license_detail';
|
||||||
import EditionFurtherDetails from './further_details';
|
import EditionFurtherDetails from './further_details';
|
||||||
|
|
||||||
import ListRequestActions from './../ascribe_forms/list_form_request_actions';
|
import ListRequestActions from './../ascribe_forms/list_form_request_actions';
|
||||||
@ -88,10 +88,8 @@ let Edition = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleDeleteSuccess(response) {
|
handleDeleteSuccess(response) {
|
||||||
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
|
this.refreshCollection();
|
||||||
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
|
|
||||||
|
|
||||||
EditionListActions.refreshEditionList({pieceId: this.props.edition.parent});
|
|
||||||
EditionListActions.closeAllEditionLists();
|
EditionListActions.closeAllEditionLists();
|
||||||
EditionListActions.clearAllEditionSelections();
|
EditionListActions.clearAllEditionSelections();
|
||||||
|
|
||||||
@ -101,6 +99,12 @@ let Edition = React.createClass({
|
|||||||
this.transitionTo('pieces');
|
this.transitionTo('pieces');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
refreshCollection() {
|
||||||
|
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
|
||||||
|
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
|
||||||
|
EditionListActions.refreshEditionList({pieceId: this.props.edition.parent});
|
||||||
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
@ -118,6 +122,7 @@ let Edition = React.createClass({
|
|||||||
</div>
|
</div>
|
||||||
<EditionSummary
|
<EditionSummary
|
||||||
handleSuccess={this.props.loadEdition}
|
handleSuccess={this.props.loadEdition}
|
||||||
|
refreshCollection={this.refreshCollection}
|
||||||
currentUser={this.state.currentUser}
|
currentUser={this.state.currentUser}
|
||||||
edition={this.props.edition}
|
edition={this.props.edition}
|
||||||
handleDeleteSuccess={this.handleDeleteSuccess}/>
|
handleDeleteSuccess={this.handleDeleteSuccess}/>
|
||||||
@ -152,8 +157,9 @@ let Edition = React.createClass({
|
|||||||
|
|
||||||
<CollapsibleParagraph
|
<CollapsibleParagraph
|
||||||
title="Notes"
|
title="Notes"
|
||||||
show={(this.state.currentUser.username && true || false) ||
|
show={!!(this.state.currentUser.username
|
||||||
(this.props.edition.acl.acl_edit || this.props.edition.public_note)}>
|
|| this.props.edition.acl.acl_edit
|
||||||
|
|| this.props.edition.public_note)}>
|
||||||
<Note
|
<Note
|
||||||
id={() => {return {'bitcoin_id': this.props.edition.bitcoin_id}; }}
|
id={() => {return {'bitcoin_id': this.props.edition.bitcoin_id}; }}
|
||||||
label={getLangText('Personal note (private)')}
|
label={getLangText('Personal note (private)')}
|
||||||
@ -205,14 +211,22 @@ let EditionSummary = React.createClass({
|
|||||||
edition: React.PropTypes.object,
|
edition: React.PropTypes.object,
|
||||||
handleSuccess: React.PropTypes.func,
|
handleSuccess: React.PropTypes.func,
|
||||||
currentUser: React.PropTypes.object,
|
currentUser: React.PropTypes.object,
|
||||||
handleDeleteSuccess: React.PropTypes.func
|
handleDeleteSuccess: React.PropTypes.func,
|
||||||
|
refreshCollection: React.PropTypes.func
|
||||||
},
|
},
|
||||||
|
|
||||||
getTransferWithdrawData(){
|
getTransferWithdrawData(){
|
||||||
return {'bitcoin_id': this.props.edition.bitcoin_id};
|
return {'bitcoin_id': this.props.edition.bitcoin_id};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleSuccess() {
|
||||||
|
this.props.refreshCollection();
|
||||||
|
this.props.handleSuccess();
|
||||||
|
},
|
||||||
|
|
||||||
showNotification(response){
|
showNotification(response){
|
||||||
this.props.handleSuccess();
|
this.props.handleSuccess();
|
||||||
|
|
||||||
if (response){
|
if (response){
|
||||||
let notification = new GlobalNotificationModel(response.notification, 'success');
|
let notification = new GlobalNotificationModel(response.notification, 'success');
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
@ -234,13 +248,15 @@ let EditionSummary = React.createClass({
|
|||||||
|
|
||||||
getActions(){
|
getActions(){
|
||||||
let actions = null;
|
let actions = null;
|
||||||
if (this.props.edition.request_action && this.props.edition.request_action.length > 0){
|
if (this.props.edition &&
|
||||||
|
this.props.edition.notifications &&
|
||||||
|
this.props.edition.notifications.length > 0){
|
||||||
actions = (
|
actions = (
|
||||||
<ListRequestActions
|
<ListRequestActions
|
||||||
pieceOrEditions={[this.props.edition]}
|
pieceOrEditions={[this.props.edition]}
|
||||||
currentUser={this.props.currentUser}
|
currentUser={this.props.currentUser}
|
||||||
handleSuccess={this.showNotification}
|
handleSuccess={this.showNotification}
|
||||||
requestActions={this.props.edition.request_action}/>);
|
notifications={this.props.edition.notifications}/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
@ -275,7 +291,7 @@ let EditionSummary = React.createClass({
|
|||||||
className="text-center ascribe-button-list"
|
className="text-center ascribe-button-list"
|
||||||
availableAcls={this.props.edition.acl}
|
availableAcls={this.props.edition.acl}
|
||||||
editions={[this.props.edition]}
|
editions={[this.props.edition]}
|
||||||
handleSuccess={this.props.handleSuccess}>
|
handleSuccess={this.handleSuccess}>
|
||||||
{withdrawButton}
|
{withdrawButton}
|
||||||
<DeleteButton
|
<DeleteButton
|
||||||
handleSuccess={this.props.handleDeleteSuccess}
|
handleSuccess={this.props.handleDeleteSuccess}
|
||||||
@ -300,12 +316,12 @@ let EditionSummary = React.createClass({
|
|||||||
<EditionDetailProperty
|
<EditionDetailProperty
|
||||||
label={getLangText('OWNER')}
|
label={getLangText('OWNER')}
|
||||||
value={ this.props.edition.owner } />
|
value={ this.props.edition.owner } />
|
||||||
|
<LicenseDetail license={this.props.edition.license_type}/>
|
||||||
{this.getStatus()}
|
{this.getStatus()}
|
||||||
{this.getActions()}
|
{this.getActions()}
|
||||||
<hr/>
|
<hr/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ import Edition from './edition';
|
|||||||
|
|
||||||
import AppConstants from '../../constants/application_constants';
|
import AppConstants from '../../constants/application_constants';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the component that implements resource/data specific functionality
|
* This is the component that implements resource/data specific functionality
|
||||||
*/
|
*/
|
||||||
@ -34,6 +36,15 @@ let EditionContainer = React.createClass({
|
|||||||
EditionActions.fetchOne(this.props.params.editionId);
|
EditionActions.fetchOne(this.props.params.editionId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// This is done to update the container when the user clicks on the prev or next
|
||||||
|
// button to update the URL parameter (and therefore to switch pieces)
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if(this.props.params.editionId !== nextProps.params.editionId) {
|
||||||
|
EditionActions.updateEdition({});
|
||||||
|
EditionActions.fetchOne(nextProps.params.editionId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
// Every time we're leaving the edition detail page,
|
// Every time we're leaving the edition detail page,
|
||||||
// just reset the edition that is saved in the edition store
|
// just reset the edition that is saved in the edition store
|
||||||
|
@ -15,7 +15,7 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
|
|||||||
|
|
||||||
import FurtherDetailsFileuploader from './further_details_fileuploader';
|
import FurtherDetailsFileuploader from './further_details_fileuploader';
|
||||||
|
|
||||||
import { isReadyForFormSubmission } from '../ascribe_uploader/react_s3_fine_uploader_utils';
|
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils';
|
||||||
|
|
||||||
let FurtherDetails = React.createClass({
|
let FurtherDetails = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
@ -38,9 +38,9 @@ let FurtherDetails = React.createClass({
|
|||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
},
|
},
|
||||||
|
|
||||||
submitKey(key){
|
submitFile(file){
|
||||||
this.setState({
|
this.setState({
|
||||||
otherDataKey: key
|
otherDataKey: file.key
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -78,9 +78,9 @@ let FurtherDetails = React.createClass({
|
|||||||
extraData={this.props.extraData} />
|
extraData={this.props.extraData} />
|
||||||
<Form>
|
<Form>
|
||||||
<FurtherDetailsFileuploader
|
<FurtherDetailsFileuploader
|
||||||
submitKey={this.submitKey}
|
submitFile={this.submitFile}
|
||||||
setIsUploadReady={this.setIsUploadReady}
|
setIsUploadReady={this.setIsUploadReady}
|
||||||
isReadyForFormSubmission={isReadyForFormSubmission}
|
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
|
||||||
editable={this.props.editable}
|
editable={this.props.editable}
|
||||||
overrideForm={true}
|
overrideForm={true}
|
||||||
pieceId={this.props.pieceId}
|
pieceId={this.props.pieceId}
|
||||||
|
@ -17,7 +17,7 @@ let FurtherDetailsFileuploader = React.createClass({
|
|||||||
pieceId: React.PropTypes.number,
|
pieceId: React.PropTypes.number,
|
||||||
otherData: React.PropTypes.arrayOf(React.PropTypes.object),
|
otherData: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||||
setIsUploadReady: React.PropTypes.func,
|
setIsUploadReady: React.PropTypes.func,
|
||||||
submitKey: React.PropTypes.func,
|
submitFile: React.PropTypes.func,
|
||||||
isReadyForFormSubmission: React.PropTypes.func,
|
isReadyForFormSubmission: React.PropTypes.func,
|
||||||
editable: React.PropTypes.bool,
|
editable: React.PropTypes.bool,
|
||||||
multiple: React.PropTypes.bool
|
multiple: React.PropTypes.bool
|
||||||
@ -55,11 +55,8 @@ let FurtherDetailsFileuploader = React.createClass({
|
|||||||
url: ApiUrls.blob_otherdatas,
|
url: ApiUrls.blob_otherdatas,
|
||||||
pieceId: this.props.pieceId
|
pieceId: this.props.pieceId
|
||||||
}}
|
}}
|
||||||
validation={{
|
validation={AppConstants.fineUploader.validation.additionalData}
|
||||||
itemLimit: 100000,
|
submitFile={this.props.submitFile}
|
||||||
sizeLimit: '50000000'
|
|
||||||
}}
|
|
||||||
submitKey={this.props.submitKey}
|
|
||||||
setIsUploadReady={this.props.setIsUploadReady}
|
setIsUploadReady={this.props.setIsUploadReady}
|
||||||
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
|
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
|
||||||
session={{
|
session={{
|
||||||
|
31
js/components/ascribe_detail/license_detail.js
Normal file
31
js/components/ascribe_detail/license_detail.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import DetailProperty from './detail_property';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the component that implements display-specific functionality
|
||||||
|
*/
|
||||||
|
let LicenseDetail = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
license: React.PropTypes.object
|
||||||
|
},
|
||||||
|
render () {
|
||||||
|
if (this.props.license.code === 'default') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<DetailProperty
|
||||||
|
label="LICENSE"
|
||||||
|
value={
|
||||||
|
<a href={this.props.license.url} target="_blank">
|
||||||
|
{ this.props.license.code.toUpperCase() + ': ' + this.props.license.name}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default LicenseDetail;
|
@ -39,19 +39,18 @@ let Note = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!!this.props.currentUser.username && this.props.show) {
|
if ((!!this.props.currentUser.username && this.props.editable || !this.props.editable ) && this.props.show) {
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
url={this.props.url}
|
url={this.props.url}
|
||||||
getFormData={this.props.id}
|
getFormData={this.props.id}
|
||||||
handleSuccess={this.showNotification}>
|
handleSuccess={this.showNotification}
|
||||||
|
disabled={!this.props.editable}>
|
||||||
<Property
|
<Property
|
||||||
name='note'
|
name='note'
|
||||||
label={this.props.label}
|
label={this.props.label}>
|
||||||
editable={this.props.editable}>
|
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={this.props.editable}
|
|
||||||
defaultValue={this.props.defaultValue}
|
defaultValue={this.props.defaultValue}
|
||||||
placeholder={this.props.placeholder}/>
|
placeholder={this.props.placeholder}/>
|
||||||
</Property>
|
</Property>
|
||||||
@ -63,4 +62,4 @@ let Note = React.createClass({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Note
|
export default Note;
|
@ -19,6 +19,7 @@ import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph
|
|||||||
import FurtherDetails from './further_details';
|
import FurtherDetails from './further_details';
|
||||||
|
|
||||||
import DetailProperty from './detail_property';
|
import DetailProperty from './detail_property';
|
||||||
|
import LicenseDetail from './license_detail';
|
||||||
import HistoryIterator from './history_iterator';
|
import HistoryIterator from './history_iterator';
|
||||||
|
|
||||||
import AclButtonList from './../ascribe_buttons/acl_button_list';
|
import AclButtonList from './../ascribe_buttons/acl_button_list';
|
||||||
@ -174,15 +175,14 @@ let PieceContainer = React.createClass({
|
|||||||
|
|
||||||
getActions() {
|
getActions() {
|
||||||
if (this.state.piece &&
|
if (this.state.piece &&
|
||||||
this.state.piece.request_action &&
|
this.state.piece.notifications &&
|
||||||
this.state.piece.request_action.length > 0) {
|
this.state.piece.notifications.length > 0) {
|
||||||
return (
|
return (
|
||||||
<ListRequestActions
|
<ListRequestActions
|
||||||
pieceOrEditions={this.state.piece}
|
pieceOrEditions={this.state.piece}
|
||||||
currentUser={this.state.currentUser}
|
currentUser={this.state.currentUser}
|
||||||
handleSuccess={this.loadPiece}
|
handleSuccess={this.loadPiece}
|
||||||
requestActions={this.state.piece.request_action}/>
|
notifications={this.state.piece.notifications}/>);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return (
|
return (
|
||||||
@ -225,6 +225,7 @@ let PieceContainer = React.createClass({
|
|||||||
<div className="ascribe-detail-header">
|
<div className="ascribe-detail-header">
|
||||||
<DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } />
|
<DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } />
|
||||||
<DetailProperty label={getLangText('ID')} value={ this.state.piece.bitcoin_id } ellipsis={true} />
|
<DetailProperty label={getLangText('ID')} value={ this.state.piece.bitcoin_id } ellipsis={true} />
|
||||||
|
<LicenseDetail license={this.state.piece.license_type} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
buttons={this.getActions()}>
|
buttons={this.getActions()}>
|
||||||
@ -238,12 +239,11 @@ let PieceContainer = React.createClass({
|
|||||||
</CollapsibleParagraph>
|
</CollapsibleParagraph>
|
||||||
<CollapsibleParagraph
|
<CollapsibleParagraph
|
||||||
title={getLangText('Notes')}
|
title={getLangText('Notes')}
|
||||||
show={(this.state.currentUser.username && true || false) ||
|
show={!!(this.state.currentUser.username || this.state.piece.public_note)}>
|
||||||
(this.state.piece.public_note)}>
|
|
||||||
<Note
|
<Note
|
||||||
id={this.getId}
|
id={this.getId}
|
||||||
label={getLangText('Personal note (private)')}
|
label={getLangText('Personal note (private)')}
|
||||||
defaultValue={this.state.piece.private_note ? this.state.piece.private_note : null}
|
defaultValue={this.state.piece.private_note || null}
|
||||||
placeholder={getLangText('Enter your comments ...')}
|
placeholder={getLangText('Enter your comments ...')}
|
||||||
editable={true}
|
editable={true}
|
||||||
successMessage={getLangText('Private note saved')}
|
successMessage={getLangText('Private note saved')}
|
||||||
|
@ -1,141 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import LoanContractListActions from '../../actions/loan_contract_list_actions';
|
|
||||||
import LoanContractListStore from '../../stores/loan_contract_list_store';
|
|
||||||
|
|
||||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
|
||||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
|
||||||
|
|
||||||
import Form from './form';
|
|
||||||
import Property from './property';
|
|
||||||
import PropertyCollapsible from './property_collapsible';
|
|
||||||
import InputTextAreaToggable from './input_textarea_toggable';
|
|
||||||
|
|
||||||
import ApiUrls from '../../constants/api_urls';
|
|
||||||
|
|
||||||
import { getLangText } from '../../utils/lang_utils';
|
|
||||||
import { mergeOptions } from '../../utils/general_utils';
|
|
||||||
|
|
||||||
|
|
||||||
let ContractForm = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
handleSuccess: React.PropTypes.func
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState() {
|
|
||||||
return mergeOptions(
|
|
||||||
LoanContractListStore.getState(),
|
|
||||||
{
|
|
||||||
selectedContract: 0
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
LoanContractListStore.listen(this.onChange);
|
|
||||||
LoanContractListActions.fetchLoanContractList();
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
LoanContractListStore.unlisten(this.onChange);
|
|
||||||
},
|
|
||||||
|
|
||||||
onChange(state) {
|
|
||||||
this.setState(state);
|
|
||||||
},
|
|
||||||
|
|
||||||
onContractChange(event){
|
|
||||||
this.setState({selectedContract: event.target.selectedIndex});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSubmitSuccess(response) {
|
|
||||||
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
|
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
|
||||||
},
|
|
||||||
|
|
||||||
getContracts() {
|
|
||||||
if (this.state.contractList && this.state.contractList.length > 0) {
|
|
||||||
return (
|
|
||||||
<Property
|
|
||||||
name='contract'
|
|
||||||
label={getLangText('Contract Type')}
|
|
||||||
onChange={this.onContractChange}
|
|
||||||
footer={
|
|
||||||
<a
|
|
||||||
className="pull-right"
|
|
||||||
href={this.state.contractList[this.state.selectedContract].s3UrlSafe}
|
|
||||||
target="_blank">
|
|
||||||
{getLangText('Learn more')}
|
|
||||||
</a>
|
|
||||||
}>
|
|
||||||
<select name="contract">
|
|
||||||
{this.state.contractList.map((contract, i) => {
|
|
||||||
return (
|
|
||||||
<option
|
|
||||||
name={i}
|
|
||||||
key={i}
|
|
||||||
value={ contract.name }>
|
|
||||||
{ contract.name }
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</select>
|
|
||||||
</Property>);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
className="ascribe-form-bordered ascribe-form-wrapper"
|
|
||||||
ref='form'
|
|
||||||
url={ApiUrls.ownership_loans_contract}
|
|
||||||
handleSuccess={this.props.handleSuccess}
|
|
||||||
buttons={<button
|
|
||||||
type="submit"
|
|
||||||
className="btn ascribe-btn ascribe-btn-login">
|
|
||||||
{getLangText('Send loan request')}
|
|
||||||
</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>
|
|
||||||
}>
|
|
||||||
<div className="ascribe-form-header">
|
|
||||||
<h3>{getLangText('Contract form')}</h3>
|
|
||||||
</div>
|
|
||||||
<Property
|
|
||||||
name='artist_name'
|
|
||||||
label={getLangText('Artist Name')}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder={getLangText('(e.g. Andy Warhol)')}
|
|
||||||
required/>
|
|
||||||
</Property>
|
|
||||||
<Property
|
|
||||||
name='artist_email'
|
|
||||||
label={getLangText('Artist Email')}>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
placeholder={getLangText('(e.g. andy@warhol.co.uk)')}
|
|
||||||
required/>
|
|
||||||
</Property>
|
|
||||||
{this.getContracts()}
|
|
||||||
<PropertyCollapsible
|
|
||||||
name='appendix'
|
|
||||||
checkboxLabel={getLangText('Add appendix to the contract')}>
|
|
||||||
<span>{getLangText('Appendix')}</span>
|
|
||||||
<InputTextAreaToggable
|
|
||||||
rows={1}
|
|
||||||
editable={true}
|
|
||||||
placeholder={getLangText('This will be appended to the contract selected above')}/>
|
|
||||||
</PropertyCollapsible>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ContractForm;
|
|
@ -40,6 +40,12 @@ let CreateEditionsForm = React.createClass({
|
|||||||
url={ApiUrls.editions}
|
url={ApiUrls.editions}
|
||||||
getFormData={this.getFormData}
|
getFormData={this.getFormData}
|
||||||
handleSuccess={this.handleSuccess}
|
handleSuccess={this.handleSuccess}
|
||||||
|
buttons={
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn ascribe-btn ascribe-btn-login">
|
||||||
|
{getLangText('Create editions')}
|
||||||
|
</button>}
|
||||||
spinner={
|
spinner={
|
||||||
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
|
<button 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" />
|
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
|
||||||
|
@ -100,6 +100,20 @@ let Form = React.createClass({
|
|||||||
.catch(this.handleError);
|
.catch(this.handleError);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
put() {
|
||||||
|
requests
|
||||||
|
.put(this.props.url, { body: this.getFormData() })
|
||||||
|
.then(this.handleSuccess)
|
||||||
|
.catch(this.handleError);
|
||||||
|
},
|
||||||
|
|
||||||
|
patch() {
|
||||||
|
requests
|
||||||
|
.patch(this.props.url, { body: this.getFormData() })
|
||||||
|
.then(this.handleSuccess)
|
||||||
|
.catch(this.handleError);
|
||||||
|
},
|
||||||
|
|
||||||
delete() {
|
delete() {
|
||||||
requests
|
requests
|
||||||
.delete(this.props.url, this.getFormData())
|
.delete(this.props.url, this.getFormData())
|
||||||
@ -189,7 +203,7 @@ let Form = React.createClass({
|
|||||||
}
|
}
|
||||||
let buttons = null;
|
let buttons = null;
|
||||||
|
|
||||||
if (this.state.edited){
|
if (this.state.edited && !this.props.disabled){
|
||||||
buttons = (
|
buttons = (
|
||||||
<div className="row" style={{margin: 0}}>
|
<div className="row" style={{margin: 0}}>
|
||||||
<p className="pull-right">
|
<p className="pull-right">
|
||||||
|
@ -56,10 +56,10 @@ let ConsignForm = React.createClass({
|
|||||||
<Property
|
<Property
|
||||||
name='consign_message'
|
name='consign_message'
|
||||||
label={getLangText('Personal Message')}
|
label={getLangText('Personal Message')}
|
||||||
editable={true}>
|
editable={true}
|
||||||
|
overrideForm={true}>
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={true}
|
|
||||||
defaultValue={this.props.message}
|
defaultValue={this.props.message}
|
||||||
placeholder={getLangText('Enter a message...')}
|
placeholder={getLangText('Enter a message...')}
|
||||||
required="required"/>
|
required="required"/>
|
||||||
|
149
js/components/ascribe_forms/form_contract_agreement.js
Normal file
149
js/components/ascribe_forms/form_contract_agreement.js
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Router from 'react-router';
|
||||||
|
|
||||||
|
import ContractListActions from '../../actions/contract_list_actions';
|
||||||
|
import ContractListStore from '../../stores/contract_list_store';
|
||||||
|
|
||||||
|
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||||
|
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||||
|
|
||||||
|
import Form from './form';
|
||||||
|
import Property from './property';
|
||||||
|
import PropertyCollapsible from './property_collapsible';
|
||||||
|
import InputTextAreaToggable from './input_textarea_toggable';
|
||||||
|
|
||||||
|
import ApiUrls from '../../constants/api_urls';
|
||||||
|
|
||||||
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
|
import { mergeOptions } from '../../utils/general_utils';
|
||||||
|
|
||||||
|
|
||||||
|
let ContractAgreementForm = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
handleSuccess: React.PropTypes.func
|
||||||
|
},
|
||||||
|
|
||||||
|
mixins: [Router.Navigation, Router.State],
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return mergeOptions(
|
||||||
|
ContractListStore.getState(),
|
||||||
|
{
|
||||||
|
selectedContract: 0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
ContractListStore.listen(this.onChange);
|
||||||
|
ContractListActions.fetchContractList(true, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
ContractListStore.unlisten(this.onChange);
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange(state) {
|
||||||
|
this.setState(state);
|
||||||
|
},
|
||||||
|
|
||||||
|
onContractChange(event){
|
||||||
|
this.setState({selectedContract: event.target.selectedIndex});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSubmitSuccess() {
|
||||||
|
let notification = 'Contract agreement send';
|
||||||
|
notification = new GlobalNotificationModel(notification, 'success', 10000);
|
||||||
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
|
this.transitionTo('pieces');
|
||||||
|
},
|
||||||
|
|
||||||
|
getFormData(){
|
||||||
|
return {'appendix': {'default': this.refs.form.refs.appendix.state.value}};
|
||||||
|
},
|
||||||
|
|
||||||
|
getContracts() {
|
||||||
|
if (this.state.contractList && this.state.contractList.length > 0) {
|
||||||
|
let contractList = this.state.contractList;
|
||||||
|
return (
|
||||||
|
<Property
|
||||||
|
name='contract'
|
||||||
|
label={getLangText('Contract Type')}
|
||||||
|
onChange={this.onContractChange}>
|
||||||
|
<select name="contract">
|
||||||
|
{contractList.map((contract, i) => {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
name={i}
|
||||||
|
key={i}
|
||||||
|
value={ contract.id }>
|
||||||
|
{ contract.name }
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</Property>);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.contractList && this.state.contractList.length > 0) {
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
className="ascribe-form-bordered ascribe-form-wrapper"
|
||||||
|
ref='form'
|
||||||
|
url={ApiUrls.ownership_contract_agreements}
|
||||||
|
getFormData={this.getFormData}
|
||||||
|
handleSuccess={this.handleSubmitSuccess}
|
||||||
|
buttons={<button
|
||||||
|
type="submit"
|
||||||
|
className="btn ascribe-btn ascribe-btn-login">
|
||||||
|
{getLangText('Send 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>
|
||||||
|
}>
|
||||||
|
<div className="ascribe-form-header">
|
||||||
|
<h3>{getLangText('Contract form')}</h3>
|
||||||
|
</div>
|
||||||
|
<Property
|
||||||
|
name='signee'
|
||||||
|
label={getLangText('Artist Email')}>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
placeholder={getLangText('(e.g. andy@warhol.co.uk)')}
|
||||||
|
required/>
|
||||||
|
</Property>
|
||||||
|
{this.getContracts()}
|
||||||
|
<PropertyCollapsible
|
||||||
|
name='appendix'
|
||||||
|
checkboxLabel={getLangText('Add appendix to the contract')}>
|
||||||
|
<span>{getLangText('Appendix')}</span>
|
||||||
|
{/* We're using disabled on a form here as PropertyCollapsible currently
|
||||||
|
does not support the disabled + overrideForm functionality */}
|
||||||
|
<InputTextAreaToggable
|
||||||
|
rows={1}
|
||||||
|
disabled={false}
|
||||||
|
placeholder={getLangText('This will be appended to the contract selected above')}/>
|
||||||
|
</PropertyCollapsible>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p className="text-center">
|
||||||
|
{getLangText('No contracts uploaded yet, please go to the ')}
|
||||||
|
<a href="settings">{getLangText('settings page')}</a>
|
||||||
|
{getLangText(' and create them.')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ContractAgreementForm;
|
80
js/components/ascribe_forms/form_copyright_association.js
Normal file
80
js/components/ascribe_forms/form_copyright_association.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||||
|
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||||
|
|
||||||
|
import Form from './form';
|
||||||
|
import Property from './property';
|
||||||
|
|
||||||
|
import ApiUrls from '../../constants/api_urls';
|
||||||
|
import AppConstants from '../../constants/application_constants';
|
||||||
|
|
||||||
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
|
|
||||||
|
let CopyrightAssociationForm = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
currentUser: React.PropTypes.object
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSubmitSuccess(){
|
||||||
|
let notification = getLangText('Copyright association updated');
|
||||||
|
notification = new GlobalNotificationModel(notification, 'success', 10000);
|
||||||
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
|
},
|
||||||
|
|
||||||
|
getProfileFormData(){
|
||||||
|
return {email: this.props.currentUser.email};
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let selectedState;
|
||||||
|
let selectDefaultValue = ' -- ' + getLangText('select an association') + ' -- ';
|
||||||
|
|
||||||
|
if (this.props.currentUser && this.props.currentUser.profile
|
||||||
|
&& this.props.currentUser.profile.copyright_association) {
|
||||||
|
selectedState = AppConstants.copyrightAssociations.indexOf(this.props.currentUser.profile.copyright_association);
|
||||||
|
selectedState = selectedState !== -1 ? AppConstants.copyrightAssociations[selectedState] : selectDefaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.currentUser && this.props.currentUser.email){
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
ref='form'
|
||||||
|
url={ApiUrls.users_profile}
|
||||||
|
getFormData={this.getProfileFormData}
|
||||||
|
handleSuccess={this.handleSubmitSuccess}>
|
||||||
|
<Property
|
||||||
|
name="copyright_association"
|
||||||
|
className="ascribe-settings-property-collapsible-toggle"
|
||||||
|
label={getLangText('Copyright Association')}
|
||||||
|
style={{paddingBottom: 0}}>
|
||||||
|
<select defaultValue={selectedState} name="contract">
|
||||||
|
<option
|
||||||
|
name={0}
|
||||||
|
key={0}
|
||||||
|
value={selectDefaultValue}>
|
||||||
|
{selectDefaultValue}
|
||||||
|
</option>
|
||||||
|
{AppConstants.copyrightAssociations.map((association, i) => {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
name={i + 1}
|
||||||
|
key={i + 1}
|
||||||
|
value={association}>
|
||||||
|
{ association }
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</Property>
|
||||||
|
<hr />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default CopyrightAssociationForm;
|
111
js/components/ascribe_forms/form_create_contract.js
Normal file
111
js/components/ascribe_forms/form_create_contract.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Form from '../ascribe_forms/form';
|
||||||
|
import Property from '../ascribe_forms/property';
|
||||||
|
|
||||||
|
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||||
|
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||||
|
|
||||||
|
import ContractListActions from '../../actions/contract_list_actions';
|
||||||
|
|
||||||
|
import AppConstants from '../../constants/application_constants';
|
||||||
|
import ApiUrls from '../../constants/api_urls';
|
||||||
|
|
||||||
|
import InputFineUploader from './input_fineuploader';
|
||||||
|
|
||||||
|
import { getLangText } from '../../utils/lang_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 {
|
||||||
|
isUploadReady: false,
|
||||||
|
contractName: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
setIsUploadReady(isReady) {
|
||||||
|
this.setState({
|
||||||
|
isUploadReady: isReady
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCreateSuccess(response) {
|
||||||
|
ContractListActions.fetchContractList(true);
|
||||||
|
let notification = new GlobalNotificationModel(getLangText('Contract %s successfully created', response.name), 'success', 5000);
|
||||||
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
|
this.refs.form.reset();
|
||||||
|
},
|
||||||
|
|
||||||
|
submitFileName(fileName) {
|
||||||
|
this.setState({
|
||||||
|
contractName: fileName
|
||||||
|
});
|
||||||
|
|
||||||
|
this.refs.form.submit();
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
ref='form'
|
||||||
|
url={ApiUrls.ownership_contract_list}
|
||||||
|
handleSuccess={this.handleCreateSuccess}>
|
||||||
|
<Property
|
||||||
|
name="blob"
|
||||||
|
label={getLangText('Contract file (*.pdf only, max. 50MB per contract)')}>
|
||||||
|
<InputFineUploader
|
||||||
|
submitFileName={this.submitFileName}
|
||||||
|
keyRoutine={{
|
||||||
|
url: AppConstants.serverUrl + 's3/key/',
|
||||||
|
fileClass: 'contract'
|
||||||
|
}}
|
||||||
|
createBlobRoutine={{
|
||||||
|
url: ApiUrls.blob_contracts
|
||||||
|
}}
|
||||||
|
validation={{
|
||||||
|
itemLimit: AppConstants.fineUploader.validation.additionalData.itemLimit,
|
||||||
|
sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit,
|
||||||
|
allowedExtensions: ['pdf']
|
||||||
|
}}
|
||||||
|
areAssetsDownloadable={true}
|
||||||
|
areAssetsEditable={true}
|
||||||
|
setIsUploadReady={this.setIsUploadReady}
|
||||||
|
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
|
||||||
|
fileClassToUpload={this.props.fileClassToUpload}/>
|
||||||
|
</Property>
|
||||||
|
<Property
|
||||||
|
name='name'
|
||||||
|
label={getLangText('Contract name')}
|
||||||
|
hidden={true}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={this.state.contractName}/>
|
||||||
|
</Property>
|
||||||
|
<Property
|
||||||
|
name="is_public"
|
||||||
|
hidden={true}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
value={this.props.isPublic} />
|
||||||
|
</Property>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default CreateContractForm;
|
@ -12,11 +12,12 @@ import InputTextAreaToggable from './input_textarea_toggable';
|
|||||||
import InputDate from './input_date';
|
import InputDate from './input_date';
|
||||||
import InputCheckbox from './input_checkbox';
|
import InputCheckbox from './input_checkbox';
|
||||||
|
|
||||||
import LoanContractStore from '../../stores/loan_contract_store';
|
import ContractAgreementListStore from '../../stores/contract_agreement_list_store';
|
||||||
import LoanContractActions from '../../actions/loan_contract_actions';
|
import ContractAgreementListActions from '../../actions/contract_agreement_list_actions';
|
||||||
|
|
||||||
import AppConstants from '../../constants/application_constants';
|
import AppConstants from '../../constants/application_constants';
|
||||||
|
|
||||||
|
import { mergeOptions } from '../../utils/general_utils';
|
||||||
import { getLangText } from '../../utils/lang_utils';
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
|
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ let LoanForm = React.createClass({
|
|||||||
url: React.PropTypes.string,
|
url: React.PropTypes.string,
|
||||||
id: React.PropTypes.object,
|
id: React.PropTypes.object,
|
||||||
message: React.PropTypes.string,
|
message: React.PropTypes.string,
|
||||||
|
createPublicContractAgreement: React.PropTypes.bool,
|
||||||
handleSuccess: React.PropTypes.func
|
handleSuccess: React.PropTypes.func
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -43,45 +45,99 @@ let LoanForm = React.createClass({
|
|||||||
showPersonalMessage: true,
|
showPersonalMessage: true,
|
||||||
showEndDate: true,
|
showEndDate: true,
|
||||||
showStartDate: true,
|
showStartDate: true,
|
||||||
showPassword: true
|
showPassword: true,
|
||||||
|
createPublicContractAgreement: true
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return LoanContractStore.getState();
|
return ContractAgreementListStore.getState();
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
LoanContractStore.listen(this.onChange);
|
ContractAgreementListStore.listen(this.onChange);
|
||||||
LoanContractActions.flushLoanContract.defer();
|
this.getContractAgreementsOrCreatePublic(this.props.email);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method needs to be in form_loan as some whitelabel pages (Cyland) load
|
||||||
|
* the loanee's email async!
|
||||||
|
*
|
||||||
|
* SO LEAVE IT IN!
|
||||||
|
*/
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if(nextProps && nextProps.email && this.props.email !== nextProps.email) {
|
||||||
|
this.getContractAgreementsOrCreatePublic(nextProps.email);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
LoanContractStore.unlisten(this.onChange);
|
ContractAgreementListStore.unlisten(this.onChange);
|
||||||
},
|
},
|
||||||
|
|
||||||
onChange(state) {
|
onChange(state) {
|
||||||
this.setState(state);
|
this.setState(state);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getContractAgreementsOrCreatePublic(email){
|
||||||
|
ContractAgreementListActions.flushContractAgreementList.defer();
|
||||||
|
if (email) {
|
||||||
|
// fetch the available contractagreements (pending/accepted)
|
||||||
|
ContractAgreementListActions.fetchAvailableContractAgreementList(email, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
getFormData(){
|
getFormData(){
|
||||||
return this.props.id;
|
return mergeOptions(
|
||||||
|
this.props.id,
|
||||||
|
this.getContractAgreementId()
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleOnChange(event) {
|
handleOnChange(event) {
|
||||||
// event.target.value is the submitted email of the loanee
|
// event.target.value is the submitted email of the loanee
|
||||||
if(event && event.target && event.target.value && event.target.value.match(/.*@.*/)) {
|
if(event && event.target && event.target.value && event.target.value.match(/.*@.*\..*/)) {
|
||||||
LoanContractActions.fetchLoanContract(event.target.value);
|
this.getContractAgreementsOrCreatePublic(event.target.value);
|
||||||
} else {
|
} else {
|
||||||
LoanContractActions.flushLoanContract();
|
ContractAgreementListActions.flushContractAgreementList();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getContractAgreementId() {
|
||||||
|
if (this.state.contractAgreementList && this.state.contractAgreementList.length > 0) {
|
||||||
|
return {'contract_agreement_id': this.state.contractAgreementList[0].id};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
getContractCheckbox() {
|
getContractCheckbox() {
|
||||||
if(this.state.contractKey && this.state.contractUrl) {
|
if(this.state.contractAgreementList && this.state.contractAgreementList.length > 0) {
|
||||||
// we need to define a key on the InputCheckboxes as otherwise
|
// we need to define a key on the InputCheckboxes as otherwise
|
||||||
// react is not rerendering them on a store switch and is keeping
|
// react is not rerendering them on a store switch and is keeping
|
||||||
// the default value of the component (which is in that case true)
|
// the default value of the component (which is in that case true)
|
||||||
|
let contractAgreement = this.state.contractAgreementList[0];
|
||||||
|
let contract = contractAgreement.contract;
|
||||||
|
|
||||||
|
if(contractAgreement.datetime_accepted) {
|
||||||
|
return (
|
||||||
|
<Property
|
||||||
|
name="terms"
|
||||||
|
label={getLangText('Loan Contract')}
|
||||||
|
hidden={false}
|
||||||
|
className="notification-contract-pdf">
|
||||||
|
<embed
|
||||||
|
className="loan-form"
|
||||||
|
src={contract.blob.url_safe}
|
||||||
|
alt="pdf"
|
||||||
|
pluginspage="http://www.adobe.com/products/acrobat/readstep2.html"/>
|
||||||
|
{/* We still need to send the server information that we're accepting */}
|
||||||
|
<InputCheckbox
|
||||||
|
style={{'display': 'none'}}
|
||||||
|
key="terms_implicitly"
|
||||||
|
defaultChecked={true} />
|
||||||
|
</Property>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
return (
|
return (
|
||||||
<Property
|
<Property
|
||||||
name="terms"
|
name="terms"
|
||||||
@ -92,13 +148,14 @@ let LoanForm = React.createClass({
|
|||||||
defaultChecked={false}>
|
defaultChecked={false}>
|
||||||
<span>
|
<span>
|
||||||
{getLangText('I agree to the')}
|
{getLangText('I agree to the')}
|
||||||
<a href={this.state.contractUrl} target="_blank">
|
<a href={contract.blob.url_safe} target="_blank">
|
||||||
{getLangText('terms of')} {this.state.contractEmail}
|
{getLangText('terms of ')} {contract.issuer}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</InputCheckbox>
|
</InputCheckbox>
|
||||||
</Property>
|
</Property>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Property
|
<Property
|
||||||
@ -113,6 +170,22 @@ let LoanForm = React.createClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getAppendix() {
|
||||||
|
if(this.state.contractAgreementList && this.state.contractAgreementList.length > 0) {
|
||||||
|
let appendix = this.state.contractAgreementList[0].appendix;
|
||||||
|
if (appendix && appendix.default) {
|
||||||
|
return (
|
||||||
|
<Property
|
||||||
|
name='appendix'
|
||||||
|
label={getLangText('Appendix')}>
|
||||||
|
<pre className="ascribe-pre">{appendix.default}</pre>
|
||||||
|
</Property>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
getButtons() {
|
getButtons() {
|
||||||
if(this.props.loanHeading) {
|
if(this.props.loanHeading) {
|
||||||
return (
|
return (
|
||||||
@ -157,8 +230,8 @@ let LoanForm = React.createClass({
|
|||||||
<Property
|
<Property
|
||||||
name='loanee'
|
name='loanee'
|
||||||
label={getLangText('Loanee Email')}
|
label={getLangText('Loanee Email')}
|
||||||
onChange={this.handleOnChange}
|
|
||||||
editable={!this.props.email}
|
editable={!this.props.email}
|
||||||
|
onChange={this.handleOnChange}
|
||||||
overrideForm={!!this.props.email}>
|
overrideForm={!!this.props.email}>
|
||||||
<input
|
<input
|
||||||
value={this.props.email}
|
value={this.props.email}
|
||||||
@ -200,14 +273,16 @@ let LoanForm = React.createClass({
|
|||||||
name='loan_message'
|
name='loan_message'
|
||||||
label={getLangText('Personal Message')}
|
label={getLangText('Personal Message')}
|
||||||
editable={true}
|
editable={true}
|
||||||
|
overrideForm={true}
|
||||||
hidden={!this.props.showPersonalMessage}>
|
hidden={!this.props.showPersonalMessage}>
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={true}
|
|
||||||
defaultValue={this.props.message}
|
defaultValue={this.props.message}
|
||||||
placeholder={getLangText('Enter a message...')}
|
placeholder={getLangText('Enter a message...')}
|
||||||
required={this.props.showPersonalMessage ? 'required' : ''}/>
|
required={this.props.showPersonalMessage ? 'required' : ''}/>
|
||||||
</Property>
|
</Property>
|
||||||
|
{this.getContractCheckbox()}
|
||||||
|
{this.getAppendix()}
|
||||||
<Property
|
<Property
|
||||||
name='password'
|
name='password'
|
||||||
label={getLangText('Password')}
|
label={getLangText('Password')}
|
||||||
@ -217,7 +292,6 @@ let LoanForm = React.createClass({
|
|||||||
placeholder={getLangText('Enter your password')}
|
placeholder={getLangText('Enter your password')}
|
||||||
required={this.props.showPassword ? 'required' : ''}/>
|
required={this.props.showPassword ? 'required' : ''}/>
|
||||||
</Property>
|
</Property>
|
||||||
{this.getContractCheckbox()}
|
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
@ -18,7 +18,7 @@ let LoanRequestAnswerForm = React.createClass({
|
|||||||
url: React.PropTypes.string,
|
url: React.PropTypes.string,
|
||||||
id: React.PropTypes.object,
|
id: React.PropTypes.object,
|
||||||
message: React.PropTypes.string,
|
message: React.PropTypes.string,
|
||||||
handleSuccess: React.PropTypes.func.required
|
handleSuccess: React.PropTypes.func.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps() {
|
getDefaultProps() {
|
||||||
|
@ -27,7 +27,7 @@ let LoginForm = React.createClass({
|
|||||||
onLogin: React.PropTypes.func
|
onLogin: React.PropTypes.func
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [Router.Navigation],
|
mixins: [Router.Navigation, Router.State],
|
||||||
|
|
||||||
getDefaultProps() {
|
getDefaultProps() {
|
||||||
return {
|
return {
|
||||||
@ -95,6 +95,7 @@ let LoginForm = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
let email = this.getQuery().email || null;
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
className="ascribe-form-bordered"
|
className="ascribe-form-bordered"
|
||||||
@ -122,7 +123,8 @@ let LoginForm = React.createClass({
|
|||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
placeholder={getLangText('Enter your email')}
|
placeholder={getLangText('Enter your email')}
|
||||||
name="username"
|
name="email"
|
||||||
|
defaultValue={email}
|
||||||
required/>
|
required/>
|
||||||
</Property>
|
</Property>
|
||||||
<Property
|
<Property
|
||||||
|
@ -41,14 +41,13 @@ let PieceExtraDataForm = React.createClass({
|
|||||||
ref='form'
|
ref='form'
|
||||||
url={url}
|
url={url}
|
||||||
handleSuccess={this.props.handleSuccess}
|
handleSuccess={this.props.handleSuccess}
|
||||||
getFormData={this.getFormData}>
|
getFormData={this.getFormData}
|
||||||
|
disabled={!this.props.editable}>
|
||||||
<Property
|
<Property
|
||||||
name={this.props.name}
|
name={this.props.name}
|
||||||
label={this.props.title}
|
label={this.props.title}>
|
||||||
editable={this.props.editable}>
|
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={this.props.editable}
|
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
placeholder={getLangText('Fill in%s', ' ') + this.props.title}
|
placeholder={getLangText('Fill in%s', ' ') + this.props.title}
|
||||||
required="required"/>
|
required="required"/>
|
||||||
|
@ -10,10 +10,11 @@ import Property from './property';
|
|||||||
import InputFineUploader from './input_fineuploader';
|
import InputFineUploader from './input_fineuploader';
|
||||||
|
|
||||||
import ApiUrls from '../../constants/api_urls';
|
import ApiUrls from '../../constants/api_urls';
|
||||||
|
import AppConstants from '../../constants/application_constants';
|
||||||
|
|
||||||
import { getLangText } from '../../utils/lang_utils';
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
import { mergeOptions } from '../../utils/general_utils';
|
import { mergeOptions } from '../../utils/general_utils';
|
||||||
import { isReadyForFormSubmission } from '../ascribe_uploader/react_s3_fine_uploader_utils';
|
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils';
|
||||||
|
|
||||||
|
|
||||||
let RegisterPieceForm = React.createClass({
|
let RegisterPieceForm = React.createClass({
|
||||||
@ -99,11 +100,19 @@ let RegisterPieceForm = React.createClass({
|
|||||||
name="digital_work_key"
|
name="digital_work_key"
|
||||||
ignoreFocus={true}>
|
ignoreFocus={true}>
|
||||||
<InputFineUploader
|
<InputFineUploader
|
||||||
|
keyRoutine={{
|
||||||
|
url: AppConstants.serverUrl + 's3/key/',
|
||||||
|
fileClass: 'digitalwork'
|
||||||
|
}}
|
||||||
|
createBlobRoutine={{
|
||||||
|
url: ApiUrls.blob_digitalworks
|
||||||
|
}}
|
||||||
|
validation={AppConstants.fineUploader.validation.registerWork}
|
||||||
setIsUploadReady={this.setIsUploadReady}
|
setIsUploadReady={this.setIsUploadReady}
|
||||||
isReadyForFormSubmission={isReadyForFormSubmission}
|
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
|
||||||
isFineUploaderActive={this.props.isFineUploaderActive}
|
isFineUploaderActive={this.props.isFineUploaderActive}
|
||||||
onLoggedOut={this.props.onLoggedOut}
|
onLoggedOut={this.props.onLoggedOut}
|
||||||
editable={this.props.isFineUploaderEditable}
|
disabled={!this.props.isFineUploaderEditable}
|
||||||
enableLocalHashing={enableLocalHashing}/>
|
enableLocalHashing={enableLocalHashing}/>
|
||||||
</Property>
|
</Property>
|
||||||
<Property
|
<Property
|
||||||
|
@ -6,6 +6,8 @@ import AclButton from './../ascribe_buttons/acl_button';
|
|||||||
import ActionPanel from '../ascribe_panel/action_panel';
|
import ActionPanel from '../ascribe_panel/action_panel';
|
||||||
import Form from './form';
|
import Form from './form';
|
||||||
|
|
||||||
|
import NotificationActions from '../../actions/notification_actions';
|
||||||
|
|
||||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||||
|
|
||||||
@ -20,8 +22,7 @@ let RequestActionForm = React.createClass({
|
|||||||
React.PropTypes.object,
|
React.PropTypes.object,
|
||||||
React.PropTypes.array
|
React.PropTypes.array
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
requestAction: React.PropTypes.string,
|
notifications: React.PropTypes.object,
|
||||||
requestUser: React.PropTypes.string,
|
|
||||||
currentUser: React.PropTypes.object,
|
currentUser: React.PropTypes.object,
|
||||||
handleSuccess: React.PropTypes.func
|
handleSuccess: React.PropTypes.func
|
||||||
},
|
},
|
||||||
@ -33,19 +34,19 @@ let RequestActionForm = React.createClass({
|
|||||||
getUrls() {
|
getUrls() {
|
||||||
let urls = {};
|
let urls = {};
|
||||||
|
|
||||||
if (this.props.requestAction === 'consign'){
|
if (this.props.notifications.action === 'consign'){
|
||||||
urls.accept = ApiUrls.ownership_consigns_confirm;
|
urls.accept = ApiUrls.ownership_consigns_confirm;
|
||||||
urls.deny = ApiUrls.ownership_consigns_deny;
|
urls.deny = ApiUrls.ownership_consigns_deny;
|
||||||
} else if (this.props.requestAction === 'unconsign'){
|
} else if (this.props.notifications.action === 'unconsign'){
|
||||||
urls.accept = ApiUrls.ownership_unconsigns;
|
urls.accept = ApiUrls.ownership_unconsigns;
|
||||||
urls.deny = ApiUrls.ownership_unconsigns_deny;
|
urls.deny = ApiUrls.ownership_unconsigns_deny;
|
||||||
} else if (this.props.requestAction === 'loan' && !this.isPiece()){
|
} else if (this.props.notifications.action === 'loan' && !this.isPiece()){
|
||||||
urls.accept = ApiUrls.ownership_loans_confirm;
|
urls.accept = ApiUrls.ownership_loans_confirm;
|
||||||
urls.deny = ApiUrls.ownership_loans_deny;
|
urls.deny = ApiUrls.ownership_loans_deny;
|
||||||
} else if (this.props.requestAction === 'loan' && this.isPiece()){
|
} else if (this.props.notifications.action === 'loan' && this.isPiece()){
|
||||||
urls.accept = ApiUrls.ownership_loans_pieces_confirm;
|
urls.accept = ApiUrls.ownership_loans_pieces_confirm;
|
||||||
urls.deny = ApiUrls.ownership_loans_pieces_deny;
|
urls.deny = ApiUrls.ownership_loans_pieces_deny;
|
||||||
} else if (this.props.requestAction === 'loan_request' && this.isPiece()){
|
} else if (this.props.notifications.action === 'loan_request' && this.isPiece()){
|
||||||
urls.accept = ApiUrls.ownership_loans_pieces_request_confirm;
|
urls.accept = ApiUrls.ownership_loans_pieces_request_confirm;
|
||||||
urls.deny = ApiUrls.ownership_loans_pieces_request_deny;
|
urls.deny = ApiUrls.ownership_loans_pieces_request_deny;
|
||||||
}
|
}
|
||||||
@ -68,30 +69,36 @@ let RequestActionForm = React.createClass({
|
|||||||
return () => {
|
return () => {
|
||||||
let message = getLangText('You have successfully') + ' ' + option + ' the ' + action + ' request ' + getLangText('from') + ' ' + owner;
|
let message = getLangText('You have successfully') + ' ' + option + ' the ' + action + ' request ' + getLangText('from') + ' ' + owner;
|
||||||
|
|
||||||
let notification = new GlobalNotificationModel(message, 'success');
|
let notifications = new GlobalNotificationModel(message, 'success');
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
GlobalNotificationActions.appendGlobalNotification(notifications);
|
||||||
|
|
||||||
|
this.handleSuccess();
|
||||||
|
|
||||||
if(this.props.handleSuccess) {
|
|
||||||
this.props.handleSuccess();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getContent() {
|
handleSuccess() {
|
||||||
let pieceOrEditionStr = this.isPiece() ? getLangText('this work%s', '.') : getLangText('this edition%s', '.');
|
if (this.isPiece()){
|
||||||
let message = this.props.requestUser + ' ' + getLangText('requests you') + ' ' + this.props.requestAction + ' ' + pieceOrEditionStr;
|
NotificationActions.fetchPieceListNotifications();
|
||||||
if (this.props.requestAction === 'loan_request'){
|
|
||||||
message = this.props.requestUser + ' ' + getLangText('requests you to loan') + ' ' + pieceOrEditionStr;
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
NotificationActions.fetchEditionListNotifications();
|
||||||
|
}
|
||||||
|
if(this.props.handleSuccess) {
|
||||||
|
this.props.handleSuccess();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getContent() {
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{message}
|
{this.props.notifications.action_str + ' by ' + this.props.notifications.by}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
getAcceptButtonForm(urls) {
|
getAcceptButtonForm(urls) {
|
||||||
if(this.props.requestAction === 'unconsign') {
|
if(this.props.notifications.action === 'unconsign') {
|
||||||
return (
|
return (
|
||||||
<AclButton
|
<AclButton
|
||||||
availableAcls={{'acl_unconsign': true}}
|
availableAcls={{'acl_unconsign': true}}
|
||||||
@ -99,9 +106,9 @@ let RequestActionForm = React.createClass({
|
|||||||
buttonAcceptClassName='inline pull-right btn-sm ascribe-margin-1px'
|
buttonAcceptClassName='inline pull-right btn-sm ascribe-margin-1px'
|
||||||
pieceOrEditions={this.props.pieceOrEditions}
|
pieceOrEditions={this.props.pieceOrEditions}
|
||||||
currentUser={this.props.currentUser}
|
currentUser={this.props.currentUser}
|
||||||
handleSuccess={this.props.handleSuccess} />
|
handleSuccess={this.handleSuccess} />
|
||||||
);
|
);
|
||||||
} else if(this.props.requestAction === 'loan_request') {
|
} else if(this.props.notifications.action === 'loan_request') {
|
||||||
return (
|
return (
|
||||||
<AclButton
|
<AclButton
|
||||||
availableAcls={{'acl_loan_request': true}}
|
availableAcls={{'acl_loan_request': true}}
|
||||||
@ -110,7 +117,7 @@ let RequestActionForm = React.createClass({
|
|||||||
buttonAcceptClassName='inline pull-right btn-sm ascribe-margin-1px'
|
buttonAcceptClassName='inline pull-right btn-sm ascribe-margin-1px'
|
||||||
pieceOrEditions={this.props.pieceOrEditions}
|
pieceOrEditions={this.props.pieceOrEditions}
|
||||||
currentUser={this.props.currentUser}
|
currentUser={this.props.currentUser}
|
||||||
handleSuccess={this.props.handleSuccess} />
|
handleSuccess={this.handleSuccess} />
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
@ -118,7 +125,7 @@ let RequestActionForm = React.createClass({
|
|||||||
url={urls.accept}
|
url={urls.accept}
|
||||||
getFormData={this.getFormData}
|
getFormData={this.getFormData}
|
||||||
handleSuccess={
|
handleSuccess={
|
||||||
this.showNotification(getLangText('accepted'), this.props.requestAction, this.props.requestUser)
|
this.showNotification(getLangText('accepted'), this.props.notifications.action, this.props.notifications.by)
|
||||||
}
|
}
|
||||||
isInline={true}
|
isInline={true}
|
||||||
className='inline pull-right'>
|
className='inline pull-right'>
|
||||||
@ -143,7 +150,7 @@ let RequestActionForm = React.createClass({
|
|||||||
isInline={true}
|
isInline={true}
|
||||||
getFormData={this.getFormData}
|
getFormData={this.getFormData}
|
||||||
handleSuccess={
|
handleSuccess={
|
||||||
this.showNotification(getLangText('denied'), this.props.requestAction, this.props.requestUser)
|
this.showNotification(getLangText('denied'), this.props.notifications.action, this.props.notifications.by)
|
||||||
}
|
}
|
||||||
className='inline pull-right'>
|
className='inline pull-right'>
|
||||||
<button
|
<button
|
||||||
|
@ -60,10 +60,10 @@ let ShareForm = React.createClass({
|
|||||||
<Property
|
<Property
|
||||||
name='share_message'
|
name='share_message'
|
||||||
label='Personal Message'
|
label='Personal Message'
|
||||||
editable={true}>
|
editable={true}
|
||||||
|
overrideForm={true}>
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={true}
|
|
||||||
defaultValue={this.props.message}
|
defaultValue={this.props.message}
|
||||||
placeholder={getLangText('Enter a message...')}
|
placeholder={getLangText('Enter a message...')}
|
||||||
required="required"/>
|
required="required"/>
|
||||||
|
@ -66,17 +66,24 @@ let SignupForm = React.createClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getFormData() {
|
||||||
|
if (this.getQuery().token){
|
||||||
|
return {token: this.getQuery().token};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' +
|
let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' +
|
||||||
getLangText('This password is securing your digital property like a bank account') + '.\n ' +
|
getLangText('This password is securing your digital property like a bank account') + '.\n ' +
|
||||||
getLangText('Store it in a safe place') + '!';
|
getLangText('Store it in a safe place') + '!';
|
||||||
let email = this.getQuery().email ? this.getQuery().email : null;
|
let email = this.getQuery().email || null;
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
className="ascribe-form-bordered"
|
className="ascribe-form-bordered"
|
||||||
ref='form'
|
ref='form'
|
||||||
url={ApiUrls.users_signup}
|
url={ApiUrls.users_signup}
|
||||||
getFormData={this.getQuery}
|
getFormData={this.getFormData}
|
||||||
handleSuccess={this.handleSuccess}
|
handleSuccess={this.handleSuccess}
|
||||||
buttons={
|
buttons={
|
||||||
<button type="submit" className="btn ascribe-btn ascribe-btn-login">
|
<button type="submit" className="btn ascribe-btn ascribe-btn-login">
|
||||||
@ -127,7 +134,7 @@ let SignupForm = React.createClass({
|
|||||||
style={{paddingBottom: 0}}>
|
style={{paddingBottom: 0}}>
|
||||||
<InputCheckbox>
|
<InputCheckbox>
|
||||||
<span>
|
<span>
|
||||||
{' ' + getLangText('I agree to the Terms of Service') + ' '}
|
{' ' + getLangText('I agree to the Terms of Service of ascribe') + ' '}
|
||||||
(<a href="https://www.ascribe.io/terms" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
|
(<a href="https://www.ascribe.io/terms" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
|
||||||
{getLangText('read')}
|
{getLangText('read')}
|
||||||
</a>)
|
</a>)
|
||||||
|
@ -45,20 +45,20 @@ let PieceSubmitToPrizeForm = React.createClass({
|
|||||||
<Property
|
<Property
|
||||||
name='artist_statement'
|
name='artist_statement'
|
||||||
label={getLangText('Artist statement')}
|
label={getLangText('Artist statement')}
|
||||||
editable={true}>
|
editable={true}
|
||||||
|
overrideForm={true}>
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={true}
|
|
||||||
placeholder={getLangText('Enter your statement')}
|
placeholder={getLangText('Enter your statement')}
|
||||||
required="required"/>
|
required="required"/>
|
||||||
</Property>
|
</Property>
|
||||||
<Property
|
<Property
|
||||||
name='work_description'
|
name='work_description'
|
||||||
label={getLangText('Work description')}
|
label={getLangText('Work description')}
|
||||||
editable={true}>
|
editable={true}
|
||||||
|
overrideForm={true}>
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={true}
|
|
||||||
placeholder={getLangText('Enter the description for your work')}
|
placeholder={getLangText('Enter the description for your work')}
|
||||||
required="required"/>
|
required="required"/>
|
||||||
</Property>
|
</Property>
|
||||||
|
@ -61,10 +61,10 @@ let TransferForm = React.createClass({
|
|||||||
<Property
|
<Property
|
||||||
name='transfer_message'
|
name='transfer_message'
|
||||||
label={getLangText('Personal Message')}
|
label={getLangText('Personal Message')}
|
||||||
editable={true}>
|
editable={true}
|
||||||
|
overrideForm={true}>
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={true}
|
|
||||||
defaultValue={this.props.message}
|
defaultValue={this.props.message}
|
||||||
placeholder={getLangText('Enter a message...')}
|
placeholder={getLangText('Enter a message...')}
|
||||||
required="required"/>
|
required="required"/>
|
||||||
|
@ -50,10 +50,10 @@ let UnConsignForm = React.createClass({
|
|||||||
<Property
|
<Property
|
||||||
name='unconsign_message'
|
name='unconsign_message'
|
||||||
label={getLangText('Personal Message')}
|
label={getLangText('Personal Message')}
|
||||||
editable={true}>
|
editable={true}
|
||||||
|
overrideForm={true}>
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={true}
|
|
||||||
defaultValue={this.props.message}
|
defaultValue={this.props.message}
|
||||||
placeholder={getLangText('Enter a message...')}
|
placeholder={getLangText('Enter a message...')}
|
||||||
required="required"/>
|
required="required"/>
|
||||||
|
@ -50,10 +50,10 @@ let UnConsignRequestForm = React.createClass({
|
|||||||
<Property
|
<Property
|
||||||
name='unconsign_request_message'
|
name='unconsign_request_message'
|
||||||
label={getLangText('Personal Message')}
|
label={getLangText('Personal Message')}
|
||||||
editable={true}>
|
editable={true}
|
||||||
|
overrideForm={true}>
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={true}
|
|
||||||
defaultValue={this.props.message}
|
defaultValue={this.props.message}
|
||||||
placeholder={getLangText('Enter a message...')}
|
placeholder={getLangText('Enter a message...')}
|
||||||
required="required"/>
|
required="required"/>
|
||||||
|
@ -25,7 +25,10 @@ let InputCheckbox = React.createClass({
|
|||||||
|
|
||||||
// provided by Property
|
// provided by Property
|
||||||
disabled: React.PropTypes.bool,
|
disabled: React.PropTypes.bool,
|
||||||
onChange: React.PropTypes.func
|
onChange: React.PropTypes.func,
|
||||||
|
|
||||||
|
// can be used to style the component from the outside
|
||||||
|
style: React.PropTypes.object
|
||||||
},
|
},
|
||||||
|
|
||||||
// As HTML inputs, we're setting the default value for an input to checked === false
|
// As HTML inputs, we're setting the default value for an input to checked === false
|
||||||
@ -98,6 +101,7 @@ let InputCheckbox = React.createClass({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
|
style={this.props.style}
|
||||||
onClick={this.onChange}>
|
onClick={this.onChange}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
@ -5,26 +5,48 @@ import React from 'react';
|
|||||||
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
|
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
|
||||||
|
|
||||||
import AppConstants from '../../constants/application_constants';
|
import AppConstants from '../../constants/application_constants';
|
||||||
import ApiUrls from '../../constants/api_urls';
|
|
||||||
|
|
||||||
import { getCookie } from '../../utils/fetch_api_utils';
|
import { getCookie } from '../../utils/fetch_api_utils';
|
||||||
|
|
||||||
let InputFileUploader = React.createClass({
|
let InputFineUploader = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
setIsUploadReady: React.PropTypes.func,
|
setIsUploadReady: React.PropTypes.func,
|
||||||
isReadyForFormSubmission: React.PropTypes.func,
|
isReadyForFormSubmission: React.PropTypes.func,
|
||||||
|
submitFileName: React.PropTypes.func,
|
||||||
|
|
||||||
|
areAssetsDownloadable: React.PropTypes.bool,
|
||||||
|
|
||||||
onClick: 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
|
// isFineUploaderActive is used to lock react fine uploader in case
|
||||||
// a user is actually not logged in already to prevent him from droping files
|
// a user is actually not logged in already to prevent him from droping files
|
||||||
// before login in
|
// before login in
|
||||||
isFineUploaderActive: React.PropTypes.bool,
|
isFineUploaderActive: React.PropTypes.bool,
|
||||||
onLoggedOut: React.PropTypes.func,
|
onLoggedOut: React.PropTypes.func,
|
||||||
editable: React.PropTypes.bool,
|
|
||||||
enableLocalHashing: React.PropTypes.bool,
|
enableLocalHashing: React.PropTypes.bool,
|
||||||
|
|
||||||
// provided by Property
|
// 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() {
|
getInitialState() {
|
||||||
@ -33,10 +55,14 @@ let InputFileUploader = React.createClass({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
submitKey(key){
|
submitFile(file){
|
||||||
this.setState({
|
this.setState({
|
||||||
value: key
|
value: file.key
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(typeof this.props.submitFileName === 'function') {
|
||||||
|
this.props.submitFileName(file.originalName);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
@ -56,21 +82,13 @@ let InputFileUploader = React.createClass({
|
|||||||
<ReactS3FineUploader
|
<ReactS3FineUploader
|
||||||
ref="fineuploader"
|
ref="fineuploader"
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
keyRoutine={{
|
keyRoutine={this.props.keyRoutine}
|
||||||
url: AppConstants.serverUrl + 's3/key/',
|
createBlobRoutine={this.props.createBlobRoutine}
|
||||||
fileClass: 'digitalwork'
|
validation={this.props.validation}
|
||||||
}}
|
submitFile={this.submitFile}
|
||||||
createBlobRoutine={{
|
|
||||||
url: ApiUrls.blob_digitalworks
|
|
||||||
}}
|
|
||||||
submitKey={this.submitKey}
|
|
||||||
validation={{
|
|
||||||
itemLimit: 100000,
|
|
||||||
sizeLimit: '25000000000'
|
|
||||||
}}
|
|
||||||
setIsUploadReady={this.props.setIsUploadReady}
|
setIsUploadReady={this.props.setIsUploadReady}
|
||||||
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
|
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
|
||||||
areAssetsDownloadable={false}
|
areAssetsDownloadable={this.props.areAssetsDownloadable}
|
||||||
areAssetsEditable={editable}
|
areAssetsEditable={editable}
|
||||||
signature={{
|
signature={{
|
||||||
endpoint: AppConstants.serverUrl + 's3/signature/',
|
endpoint: AppConstants.serverUrl + 's3/signature/',
|
||||||
@ -87,9 +105,10 @@ let InputFileUploader = React.createClass({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onInactive={this.props.onLoggedOut}
|
onInactive={this.props.onLoggedOut}
|
||||||
enableLocalHashing={this.props.enableLocalHashing} />
|
enableLocalHashing={this.props.enableLocalHashing}
|
||||||
|
fileClassToUpload={this.props.fileClassToUpload}/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default InputFileUploader;
|
export default InputFineUploader;
|
@ -7,7 +7,7 @@ import TextareaAutosize from 'react-textarea-autosize';
|
|||||||
|
|
||||||
let InputTextAreaToggable = React.createClass({
|
let InputTextAreaToggable = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
editable: React.PropTypes.bool.isRequired,
|
disabled: React.PropTypes.bool,
|
||||||
rows: React.PropTypes.number.isRequired,
|
rows: React.PropTypes.number.isRequired,
|
||||||
required: React.PropTypes.string,
|
required: React.PropTypes.string,
|
||||||
defaultValue: React.PropTypes.string
|
defaultValue: React.PropTypes.string
|
||||||
@ -15,10 +15,26 @@ let InputTextAreaToggable = React.createClass({
|
|||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return {
|
return {
|
||||||
value: this.props.defaultValue
|
value: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setState({
|
||||||
|
value: this.props.defaultValue
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
// If the initial value of state.value is null, we want to set props.defaultValue
|
||||||
|
// as a value. In all other cases TextareaAutosize.onChange is updating.handleChange already
|
||||||
|
if(this.state.value === null && this.props.defaultValue) {
|
||||||
|
this.setState({
|
||||||
|
value: this.props.defaultValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
handleChange(event) {
|
handleChange(event) {
|
||||||
this.setState({value: event.target.value});
|
this.setState({value: event.target.value});
|
||||||
this.props.onChange(event);
|
this.props.onChange(event);
|
||||||
@ -28,7 +44,7 @@ let InputTextAreaToggable = React.createClass({
|
|||||||
let className = 'form-control ascribe-textarea';
|
let className = 'form-control ascribe-textarea';
|
||||||
let textarea = null;
|
let textarea = null;
|
||||||
|
|
||||||
if(this.props.editable) {
|
if(!this.props.disabled) {
|
||||||
className = className + ' ascribe-textarea-editable';
|
className = className + ' ascribe-textarea-editable';
|
||||||
textarea = (
|
textarea = (
|
||||||
<TextareaAutosize
|
<TextareaAutosize
|
||||||
|
@ -12,20 +12,19 @@ let ListRequestActions = React.createClass({
|
|||||||
]).isRequired,
|
]).isRequired,
|
||||||
currentUser: React.PropTypes.object.isRequired,
|
currentUser: React.PropTypes.object.isRequired,
|
||||||
handleSuccess: React.PropTypes.func.isRequired,
|
handleSuccess: React.PropTypes.func.isRequired,
|
||||||
requestActions: React.PropTypes.array.isRequired
|
notifications: React.PropTypes.array.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
if (this.props.requestActions &&
|
if (this.props.notifications &&
|
||||||
this.props.requestActions.length > 0) {
|
this.props.notifications.length > 0) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{this.props.requestActions.map((requestAction) =>
|
{this.props.notifications.map((notification) =>
|
||||||
<RequestActionForm
|
<RequestActionForm
|
||||||
currentUser={this.props.currentUser}
|
currentUser={this.props.currentUser}
|
||||||
pieceOrEditions={ this.props.pieceOrEditions }
|
pieceOrEditions={ this.props.pieceOrEditions }
|
||||||
requestAction={requestAction.action}
|
notifications={notification}
|
||||||
requestUser={requestAction.by}
|
|
||||||
handleSuccess={this.props.handleSuccess}/>)}
|
handleSuccess={this.props.handleSuccess}/>)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -70,7 +70,7 @@ let Property = React.createClass({
|
|||||||
// In order to set this.state.value from another component
|
// In order to set this.state.value from another component
|
||||||
// the state of value should only be set if its not undefined and
|
// the state of value should only be set if its not undefined and
|
||||||
// actually references something
|
// actually references something
|
||||||
if(typeof childInput.getDOMNode().value !== 'undefined') {
|
if(childInput && typeof childInput.getDOMNode().value !== 'undefined') {
|
||||||
this.setState({
|
this.setState({
|
||||||
value: childInput.getDOMNode().value
|
value: childInput.getDOMNode().value
|
||||||
});
|
});
|
||||||
|
@ -12,7 +12,10 @@ let ActionPanel = React.createClass({
|
|||||||
]),
|
]),
|
||||||
buttons: React.PropTypes.element,
|
buttons: React.PropTypes.element,
|
||||||
onClick: React.PropTypes.func,
|
onClick: React.PropTypes.func,
|
||||||
ignoreFocus: React.PropTypes.bool
|
ignoreFocus: React.PropTypes.bool,
|
||||||
|
|
||||||
|
leftColumnWidth: React.PropTypes.string,
|
||||||
|
rightColumnWidth: React.PropTypes.string
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
@ -41,14 +44,21 @@ let ActionPanel = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
|
let { leftColumnWidth, rightColumnWidth } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames('ascribe-panel-wrapper', {'is-focused': this.state.isFocused})}>
|
<div className={classnames('ascribe-panel-wrapper', {'is-focused': this.state.isFocused})}>
|
||||||
<div className="ascribe-panel-table">
|
<div
|
||||||
|
className="ascribe-panel-table"
|
||||||
|
style={{width: leftColumnWidth}}>
|
||||||
<div className="ascribe-panel-content">
|
<div className="ascribe-panel-content">
|
||||||
{this.props.content}
|
{this.props.content}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ascribe-panel-table">
|
<div
|
||||||
|
className="ascribe-panel-table"
|
||||||
|
style={{width: rightColumnWidth}}>
|
||||||
<div className="ascribe-panel-content">
|
<div className="ascribe-panel-content">
|
||||||
{this.props.buttons}
|
{this.props.buttons}
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,7 +14,20 @@ let PieceListToolbar = React.createClass({
|
|||||||
propTypes: {
|
propTypes: {
|
||||||
className: React.PropTypes.string,
|
className: React.PropTypes.string,
|
||||||
searchFor: React.PropTypes.func,
|
searchFor: React.PropTypes.func,
|
||||||
filterParams: React.PropTypes.array,
|
filterParams: React.PropTypes.arrayOf(
|
||||||
|
React.PropTypes.shape({
|
||||||
|
label: React.PropTypes.string,
|
||||||
|
items: React.PropTypes.arrayOf(
|
||||||
|
React.PropTypes.oneOfType([
|
||||||
|
React.PropTypes.string,
|
||||||
|
React.PropTypes.shape({
|
||||||
|
key: React.PropTypes.string,
|
||||||
|
label: React.PropTypes.string
|
||||||
|
})
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
),
|
||||||
filterBy: React.PropTypes.object,
|
filterBy: React.PropTypes.object,
|
||||||
applyFilterBy: React.PropTypes.func,
|
applyFilterBy: React.PropTypes.func,
|
||||||
orderParams: React.PropTypes.array,
|
orderParams: React.PropTypes.array,
|
||||||
|
@ -3,20 +3,26 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||||
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
|
||||||
|
|
||||||
import { getLangText } from '../../utils/lang_utils.js';
|
import { getLangText } from '../../utils/lang_utils.js';
|
||||||
|
|
||||||
|
|
||||||
let PieceListToolbarFilterWidgetFilter = React.createClass({
|
let PieceListToolbarFilterWidgetFilter = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
// An array of either strings (which represent acl enums) or objects of the form
|
filterParams: React.PropTypes.arrayOf(
|
||||||
//
|
React.PropTypes.shape({
|
||||||
// {
|
label: React.PropTypes.string,
|
||||||
// key: <acl enum>,
|
items: React.PropTypes.arrayOf(
|
||||||
// label: <a human readable string>
|
React.PropTypes.oneOfType([
|
||||||
// }
|
React.PropTypes.string,
|
||||||
//
|
React.PropTypes.shape({
|
||||||
filterParams: React.PropTypes.arrayOf(React.PropTypes.any).isRequired,
|
key: React.PropTypes.string,
|
||||||
|
label: React.PropTypes.string
|
||||||
|
})
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
filterBy: React.PropTypes.object,
|
filterBy: React.PropTypes.object,
|
||||||
applyFilterBy: React.PropTypes.func
|
applyFilterBy: React.PropTypes.func
|
||||||
},
|
},
|
||||||
@ -79,23 +85,38 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({
|
|||||||
<DropdownButton
|
<DropdownButton
|
||||||
title={filterIcon}
|
title={filterIcon}
|
||||||
className="ascribe-piece-list-toolbar-filter-widget">
|
className="ascribe-piece-list-toolbar-filter-widget">
|
||||||
<li style={{'textAlign': 'center'}}>
|
{/* We iterate over filterParams, to receive the label and then for each
|
||||||
<em>{getLangText('Show works I can')}:</em>
|
label also iterate over its items, to get all filterable options */}
|
||||||
|
{this.props.filterParams.map(({ label, items }, i) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<li
|
||||||
|
style={{'textAlign': 'center'}}
|
||||||
|
key={i}>
|
||||||
|
<em>{label}:</em>
|
||||||
</li>
|
</li>
|
||||||
{this.props.filterParams.map((param, i) => {
|
{items.map((param, j) => {
|
||||||
let label;
|
|
||||||
|
|
||||||
|
// As can be seen in the PropTypes, a param can either
|
||||||
|
// be a string or an object of the shape:
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// key: <String>,
|
||||||
|
// label: <String>
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This is why we need to distinguish between both here.
|
||||||
if(typeof param !== 'string') {
|
if(typeof param !== 'string') {
|
||||||
label = param.label;
|
label = param.label;
|
||||||
param = param.key;
|
param = param.key;
|
||||||
} else {
|
} else {
|
||||||
param = param;
|
param = param;
|
||||||
label = param.split('_')[1];
|
label = param.split('acl_')[1].replace(/_/g, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<li
|
||||||
key={i}
|
key={j}
|
||||||
onClick={this.filterBy(param)}
|
onClick={this.filterBy(param)}
|
||||||
className="filter-widget-item">
|
className="filter-widget-item">
|
||||||
<div className="checkbox-line">
|
<div className="checkbox-line">
|
||||||
@ -107,7 +128,10 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={this.props.filterBy[param]} />
|
checked={this.props.filterBy[param]} />
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||||
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
|
||||||
|
|
||||||
import { getLangText } from '../../utils/lang_utils.js';
|
import { getLangText } from '../../utils/lang_utils.js';
|
||||||
|
|
||||||
@ -62,7 +61,8 @@ let PieceListToolbarOrderWidget = React.createClass({
|
|||||||
</li>
|
</li>
|
||||||
{this.props.orderParams.map((param) => {
|
{this.props.orderParams.map((param) => {
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<div>
|
||||||
|
<li
|
||||||
key={param}
|
key={param}
|
||||||
onClick={this.orderBy(param)}
|
onClick={this.orderBy(param)}
|
||||||
className="filter-widget-item">
|
className="filter-widget-item">
|
||||||
@ -75,7 +75,8 @@ let PieceListToolbarOrderWidget = React.createClass({
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={param.indexOf(this.props.orderBy) > -1} />
|
checked={param.indexOf(this.props.orderBy) > -1} />
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</li>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import PrizeListActions from '../../actions/prize_list_actions';
|
|
||||||
import PrizeListStore from '../../stores/prize_list_store';
|
|
||||||
|
|
||||||
import Table from '../ascribe_table/table';
|
|
||||||
import TableItem from '../ascribe_table/table_item';
|
|
||||||
import TableItemText from '../ascribe_table/table_item_text';
|
|
||||||
|
|
||||||
import { ColumnModel} from '../ascribe_table/models/table_models';
|
|
||||||
import { getLangText } from '../../utils/lang_utils';
|
|
||||||
|
|
||||||
let PrizesDashboard = React.createClass({
|
|
||||||
|
|
||||||
getInitialState() {
|
|
||||||
return PrizeListStore.getState();
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
PrizeListStore.listen(this.onChange);
|
|
||||||
PrizeListActions.fetchPrizeList();
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
PrizeListStore.unlisten(this.onChange);
|
|
||||||
},
|
|
||||||
|
|
||||||
onChange(state) {
|
|
||||||
this.setState(state);
|
|
||||||
},
|
|
||||||
|
|
||||||
getColumnList() {
|
|
||||||
return [
|
|
||||||
new ColumnModel(
|
|
||||||
(item) => {
|
|
||||||
return {
|
|
||||||
'content': item.name
|
|
||||||
}; },
|
|
||||||
'name',
|
|
||||||
getLangText('Name'),
|
|
||||||
TableItemText,
|
|
||||||
6,
|
|
||||||
false,
|
|
||||||
null
|
|
||||||
),
|
|
||||||
new ColumnModel(
|
|
||||||
(item) => {
|
|
||||||
return {
|
|
||||||
'content': item.domain
|
|
||||||
}; },
|
|
||||||
'domain',
|
|
||||||
getLangText('Domain'),
|
|
||||||
TableItemText,
|
|
||||||
1,
|
|
||||||
false,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Table
|
|
||||||
responsive
|
|
||||||
className="ascribe-table"
|
|
||||||
columnList={this.getColumnList()}
|
|
||||||
itemList={this.state.prizeList}>
|
|
||||||
{this.state.prizeList.map((item, i) => {
|
|
||||||
return (
|
|
||||||
<TableItem
|
|
||||||
className="ascribe-table-item-selectable"
|
|
||||||
key={i}/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Table>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default PrizesDashboard;
|
|
106
js/components/ascribe_settings/account_settings.js
Normal file
106
js/components/ascribe_settings/account_settings.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||||
|
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||||
|
|
||||||
|
import Form from '../ascribe_forms/form';
|
||||||
|
import Property from '../ascribe_forms/property';
|
||||||
|
import InputCheckbox from '../ascribe_forms/input_checkbox';
|
||||||
|
import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph';
|
||||||
|
|
||||||
|
import AclProxy from '../acl_proxy';
|
||||||
|
|
||||||
|
import CopyrightAssociationForm from '../ascribe_forms/form_copyright_association';
|
||||||
|
|
||||||
|
import ApiUrls from '../../constants/api_urls';
|
||||||
|
import AppConstants from '../../constants/application_constants';
|
||||||
|
|
||||||
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
|
|
||||||
|
let AccountSettings = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
currentUser: React.PropTypes.object.isRequired,
|
||||||
|
loadUser: React.PropTypes.func.isRequired,
|
||||||
|
whitelabel: React.PropTypes.object.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSuccess(){
|
||||||
|
this.props.loadUser();
|
||||||
|
let notification = new GlobalNotificationModel(getLangText('Settings succesfully updated'), 'success', 5000);
|
||||||
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
|
},
|
||||||
|
|
||||||
|
getFormDataProfile(){
|
||||||
|
return {'email': this.props.currentUser.email};
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||||
|
let profile = null;
|
||||||
|
|
||||||
|
if (this.props.currentUser.username) {
|
||||||
|
content = (
|
||||||
|
<Form
|
||||||
|
url={ApiUrls.users_username}
|
||||||
|
handleSuccess={this.handleSuccess}>
|
||||||
|
<Property
|
||||||
|
name='username'
|
||||||
|
label={getLangText('Username')}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
defaultValue={this.props.currentUser.username}
|
||||||
|
placeholder={getLangText('Enter your username')}
|
||||||
|
required/>
|
||||||
|
</Property>
|
||||||
|
<Property
|
||||||
|
name='email'
|
||||||
|
label={getLangText('Email')}
|
||||||
|
overrideForm={true}
|
||||||
|
editable={false}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
defaultValue={this.props.currentUser.email}
|
||||||
|
placeholder={getLangText('Enter your username')}
|
||||||
|
required/>
|
||||||
|
</Property>
|
||||||
|
<hr />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
profile = (
|
||||||
|
<AclProxy
|
||||||
|
aclObject={this.props.whitelabel}
|
||||||
|
aclName="acl_view_settings_account_hash">
|
||||||
|
<Form
|
||||||
|
url={ApiUrls.users_profile}
|
||||||
|
handleSuccess={this.handleSuccess}
|
||||||
|
getFormData={this.getFormDataProfile}>
|
||||||
|
<Property
|
||||||
|
name="hash_locally"
|
||||||
|
className="ascribe-settings-property-collapsible-toggle"
|
||||||
|
style={{paddingBottom: 0}}>
|
||||||
|
<InputCheckbox
|
||||||
|
defaultChecked={this.props.currentUser.profile.hash_locally}>
|
||||||
|
<span>
|
||||||
|
{' ' + getLangText('Enable hash option, e.g. slow connections or to keep piece private')}
|
||||||
|
</span>
|
||||||
|
</InputCheckbox>
|
||||||
|
</Property>
|
||||||
|
</Form>
|
||||||
|
</AclProxy>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<CollapsibleParagraph
|
||||||
|
title={getLangText('Account')}
|
||||||
|
defaultExpanded={true}>
|
||||||
|
{content}
|
||||||
|
<CopyrightAssociationForm currentUser={this.props.currentUser}/>
|
||||||
|
{profile}
|
||||||
|
</CollapsibleParagraph>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AccountSettings;
|
123
js/components/ascribe_settings/api_settings.js
Normal file
123
js/components/ascribe_settings/api_settings.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import ApplicationStore from '../../stores/application_store';
|
||||||
|
import ApplicationActions from '../../actions/application_actions';
|
||||||
|
|
||||||
|
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||||
|
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||||
|
|
||||||
|
import Form from '../ascribe_forms/form';
|
||||||
|
import Property from '../ascribe_forms/property';
|
||||||
|
|
||||||
|
import ActionPanel from '../ascribe_panel/action_panel';
|
||||||
|
import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph';
|
||||||
|
|
||||||
|
import ApiUrls from '../../constants/api_urls';
|
||||||
|
import AppConstants from '../../constants/application_constants';
|
||||||
|
|
||||||
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
|
|
||||||
|
|
||||||
|
let APISettings = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
defaultExpanded: React.PropTypes.bool
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return ApplicationStore.getState();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
ApplicationStore.listen(this.onChange);
|
||||||
|
ApplicationActions.fetchApplication();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
ApplicationStore.unlisten(this.onChange);
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange(state) {
|
||||||
|
this.setState(state);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCreateSuccess() {
|
||||||
|
ApplicationActions.fetchApplication();
|
||||||
|
let notification = new GlobalNotificationModel(getLangText('Application successfully created'), 'success', 5000);
|
||||||
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleTokenRefresh(event) {
|
||||||
|
let applicationName = event.target.getAttribute('data-id');
|
||||||
|
ApplicationActions.refreshApplicationToken(applicationName);
|
||||||
|
|
||||||
|
let notification = new GlobalNotificationModel(getLangText('Token refreshed'), 'success', 2000);
|
||||||
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
|
},
|
||||||
|
|
||||||
|
getApplications(){
|
||||||
|
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||||
|
|
||||||
|
if (this.state.applications.length > -1) {
|
||||||
|
content = this.state.applications.map(function(app, i) {
|
||||||
|
return (
|
||||||
|
<ActionPanel
|
||||||
|
name={app.name}
|
||||||
|
key={i}
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
<div className='ascribe-panel-title'>
|
||||||
|
{app.name}
|
||||||
|
</div>
|
||||||
|
<div className="ascribe-panel-subtitle">
|
||||||
|
{'Bearer ' + app.bearer_token.token}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
buttons={
|
||||||
|
<div className="pull-right">
|
||||||
|
<div className="pull-right">
|
||||||
|
<button
|
||||||
|
className="pull-right btn btn-default btn-sm"
|
||||||
|
onClick={this.handleTokenRefresh}
|
||||||
|
data-id={app.name}>
|
||||||
|
{getLangText('REFRESH')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}/>
|
||||||
|
);
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<CollapsibleParagraph
|
||||||
|
title={getLangText('API Integration')}
|
||||||
|
defaultExpanded={this.props.defaultExpanded}>
|
||||||
|
<Form
|
||||||
|
url={ApiUrls.applications}
|
||||||
|
handleSuccess={this.handleCreateSuccess}>
|
||||||
|
<Property
|
||||||
|
name='name'
|
||||||
|
label={getLangText('Application Name')}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={getLangText('Enter the name of your app')}
|
||||||
|
required/>
|
||||||
|
</Property>
|
||||||
|
<hr />
|
||||||
|
</Form>
|
||||||
|
<pre>
|
||||||
|
Usage: curl <url> -H 'Authorization: Bearer <token>'
|
||||||
|
</pre>
|
||||||
|
{this.getApplications()}
|
||||||
|
</CollapsibleParagraph>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default APISettings;
|
71
js/components/ascribe_settings/bitcoin_wallet_settings.js
Normal file
71
js/components/ascribe_settings/bitcoin_wallet_settings.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import WalletSettingsStore from '../../stores/wallet_settings_store';
|
||||||
|
import WalletSettingsActions from '../../actions/wallet_settings_actions';
|
||||||
|
|
||||||
|
import Form from '../ascribe_forms/form';
|
||||||
|
import Property from '../ascribe_forms/property';
|
||||||
|
|
||||||
|
import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph';
|
||||||
|
|
||||||
|
import AppConstants from '../../constants/application_constants';
|
||||||
|
|
||||||
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
|
|
||||||
|
|
||||||
|
let BitcoinWalletSettings = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
defaultExpanded: React.PropTypes.bool
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return WalletSettingsStore.getState();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
WalletSettingsStore.listen(this.onChange);
|
||||||
|
WalletSettingsActions.fetchWalletSettings();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
WalletSettingsStore.unlisten(this.onChange);
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange(state) {
|
||||||
|
this.setState(state);
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||||
|
|
||||||
|
if (this.state.walletSettings.btc_public_key) {
|
||||||
|
content = (
|
||||||
|
<Form >
|
||||||
|
<Property
|
||||||
|
name='btc_public_key'
|
||||||
|
label={getLangText('Bitcoin public key')}
|
||||||
|
editable={false}>
|
||||||
|
<pre className="ascribe-pre">{this.state.walletSettings.btc_public_key}</pre>
|
||||||
|
</Property>
|
||||||
|
<Property
|
||||||
|
name='btc_root_address'
|
||||||
|
label={getLangText('Root Address')}
|
||||||
|
editable={false}>
|
||||||
|
<pre className="ascribe-pre">{this.state.walletSettings.btc_root_address}</pre>
|
||||||
|
</Property>
|
||||||
|
<hr />
|
||||||
|
</Form>);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<CollapsibleParagraph
|
||||||
|
title={getLangText('Crypto Wallet')}
|
||||||
|
defaultExpanded={this.props.defaultExpanded}>
|
||||||
|
{content}
|
||||||
|
</CollapsibleParagraph>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default BitcoinWalletSettings;
|
186
js/components/ascribe_settings/contract_settings.js
Normal file
186
js/components/ascribe_settings/contract_settings.js
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph';
|
||||||
|
import CreateContractForm from '../ascribe_forms/form_create_contract';
|
||||||
|
|
||||||
|
import ContractListStore from '../../stores/contract_list_store';
|
||||||
|
import ContractListActions from '../../actions/contract_list_actions';
|
||||||
|
|
||||||
|
import UserStore from '../../stores/user_store';
|
||||||
|
import UserActions from '../../actions/user_actions';
|
||||||
|
|
||||||
|
import WhitelabelStore from '../../stores/whitelabel_store';
|
||||||
|
import WhitelabelActions from '../../actions/whitelabel_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 AclProxy from '../acl_proxy';
|
||||||
|
|
||||||
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
|
import { mergeOptions, truncateTextAtCharIndex } from '../../utils/general_utils';
|
||||||
|
|
||||||
|
|
||||||
|
let ContractSettings = React.createClass({
|
||||||
|
getInitialState(){
|
||||||
|
return mergeOptions(
|
||||||
|
ContractListStore.getState(),
|
||||||
|
UserStore.getState()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
ContractListStore.listen(this.onChange);
|
||||||
|
UserStore.listen(this.onChange);
|
||||||
|
WhitelabelStore.listen(this.onChange);
|
||||||
|
|
||||||
|
WhitelabelActions.fetchWhitelabel();
|
||||||
|
UserActions.fetchCurrentUser();
|
||||||
|
ContractListActions.fetchContractList(true);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
WhitelabelStore.unlisten(this.onChange);
|
||||||
|
UserStore.unlisten(this.onChange);
|
||||||
|
ContractListStore.unlisten(this.onChange);
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange(state) {
|
||||||
|
this.setState(state);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeContract(contract) {
|
||||||
|
return () => {
|
||||||
|
ContractListActions.removeContract(contract.id)
|
||||||
|
.then((response) => {
|
||||||
|
ContractListActions.fetchContractList(true);
|
||||||
|
let notification = new GlobalNotificationModel(response.notification, 'success', 4000);
|
||||||
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
let notification = new GlobalNotificationModel(err, 'danger', 10000);
|
||||||
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getPublicContracts(){
|
||||||
|
return this.state.contractList.filter((contract) => contract.is_public);
|
||||||
|
},
|
||||||
|
|
||||||
|
getPrivateContracts(){
|
||||||
|
return this.state.contractList.filter((contract) => !contract.is_public);
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let publicContracts = this.getPublicContracts();
|
||||||
|
let privateContracts = this.getPrivateContracts();
|
||||||
|
let createPublicContractForm = null;
|
||||||
|
|
||||||
|
if(publicContracts.length === 0) {
|
||||||
|
createPublicContractForm = (
|
||||||
|
<CreateContractForm
|
||||||
|
isPublic={true}
|
||||||
|
fileClassToUpload={{
|
||||||
|
singular: 'new contract',
|
||||||
|
plural: 'new contracts'
|
||||||
|
}}/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="settings-container">
|
||||||
|
<CollapsibleParagraph
|
||||||
|
title={getLangText('Contracts')}
|
||||||
|
defaultExpanded={true}>
|
||||||
|
<AclProxy
|
||||||
|
aclName="acl_edit_public_contract"
|
||||||
|
aclObject={this.state.currentUser.acl}>
|
||||||
|
<div>
|
||||||
|
{createPublicContractForm}
|
||||||
|
{publicContracts.map((contract, i) => {
|
||||||
|
return (
|
||||||
|
<ActionPanel
|
||||||
|
key={i}
|
||||||
|
title={contract.name}
|
||||||
|
content={truncateTextAtCharIndex(contract.name, 120, '(...).pdf')}
|
||||||
|
buttons={
|
||||||
|
<div className="pull-right">
|
||||||
|
<AclProxy
|
||||||
|
aclObject={this.state.whitelabel}
|
||||||
|
aclName="acl_update_public_contract">
|
||||||
|
<ContractSettingsUpdateButton contract={contract}/>
|
||||||
|
</AclProxy>
|
||||||
|
<a
|
||||||
|
className="btn btn-default btn-sm margin-left-2px"
|
||||||
|
href={contract.blob.url_safe}
|
||||||
|
target="_blank">
|
||||||
|
{getLangText('PREVIEW')}
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
className="btn btn-danger btn-sm margin-left-2px"
|
||||||
|
onClick={this.removeContract(contract)}>
|
||||||
|
{getLangText('REMOVE')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
leftColumnWidth="40%"
|
||||||
|
rightColumnWidth="60%"/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</AclProxy>
|
||||||
|
<AclProxy
|
||||||
|
aclName="acl_edit_private_contract"
|
||||||
|
aclObject={this.state.currentUser.acl}>
|
||||||
|
<div>
|
||||||
|
<CreateContractForm
|
||||||
|
isPublic={false}
|
||||||
|
fileClassToUpload={{
|
||||||
|
singular: getLangText('new contract'),
|
||||||
|
plural: getLangText('new contracts')
|
||||||
|
}}/>
|
||||||
|
{privateContracts.map((contract, i) => {
|
||||||
|
return (
|
||||||
|
<ActionPanel
|
||||||
|
key={i}
|
||||||
|
title={contract.name}
|
||||||
|
content={truncateTextAtCharIndex(contract.name, 120, '(...).pdf')}
|
||||||
|
buttons={
|
||||||
|
<div className="pull-right">
|
||||||
|
<AclProxy
|
||||||
|
aclObject={this.state.whitelabel}
|
||||||
|
aclName="acl_update_private_contract">
|
||||||
|
<ContractSettingsUpdateButton contract={contract}/>
|
||||||
|
</AclProxy>
|
||||||
|
<a
|
||||||
|
className="btn btn-default btn-sm margin-left-2px"
|
||||||
|
href={contract.blob.url_safe}
|
||||||
|
target="_blank">
|
||||||
|
{getLangText('PREVIEW')}
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
className="btn btn-danger btn-sm margin-left-2px"
|
||||||
|
onClick={this.removeContract(contract)}>
|
||||||
|
{getLangText('REMOVE')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
leftColumnWidth="60%"
|
||||||
|
rightColumnWidth="40%"/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</AclProxy>
|
||||||
|
</CollapsibleParagraph>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ContractSettings;
|
@ -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;
|
84
js/components/ascribe_settings/settings_container.js
Normal file
84
js/components/ascribe_settings/settings_container.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Router from 'react-router';
|
||||||
|
|
||||||
|
import UserStore from '../../stores/user_store';
|
||||||
|
import UserActions from '../../actions/user_actions';
|
||||||
|
|
||||||
|
import WhitelabelStore from '../../stores/whitelabel_store';
|
||||||
|
import WhitelabelActions from '../../actions/whitelabel_actions';
|
||||||
|
|
||||||
|
import AccountSettings from './account_settings';
|
||||||
|
import BitcoinWalletSettings from './bitcoin_wallet_settings';
|
||||||
|
import APISettings from './api_settings';
|
||||||
|
|
||||||
|
import AclProxy from '../acl_proxy';
|
||||||
|
|
||||||
|
import { mergeOptions } from '../../utils/general_utils';
|
||||||
|
|
||||||
|
|
||||||
|
let SettingsContainer = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
children: React.PropTypes.oneOfType([
|
||||||
|
React.PropTypes.arrayOf(React.PropTypes.element),
|
||||||
|
React.PropTypes.element])
|
||||||
|
},
|
||||||
|
|
||||||
|
mixins: [Router.Navigation],
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return mergeOptions(
|
||||||
|
UserStore.getState(),
|
||||||
|
WhitelabelStore.getState()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
UserStore.listen(this.onChange);
|
||||||
|
WhitelabelStore.listen(this.onChange);
|
||||||
|
|
||||||
|
WhitelabelActions.fetchWhitelabel();
|
||||||
|
UserActions.fetchCurrentUser();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
WhitelabelStore.unlisten(this.onChange);
|
||||||
|
UserStore.unlisten(this.onChange);
|
||||||
|
},
|
||||||
|
|
||||||
|
loadUser(){
|
||||||
|
UserActions.fetchCurrentUser();
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange(state) {
|
||||||
|
this.setState(state);
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.currentUser && this.state.currentUser.username) {
|
||||||
|
return (
|
||||||
|
<div className="settings-container">
|
||||||
|
<AccountSettings
|
||||||
|
currentUser={this.state.currentUser}
|
||||||
|
loadUser={this.loadUser}
|
||||||
|
whitelabel={this.state.whitelabel}/>
|
||||||
|
{this.props.children}
|
||||||
|
<AclProxy
|
||||||
|
aclObject={this.state.whitelabel}
|
||||||
|
aclName="acl_view_settings_api">
|
||||||
|
<APISettings />
|
||||||
|
</AclProxy>
|
||||||
|
<AclProxy
|
||||||
|
aclObject={this.state.whitelabel}
|
||||||
|
aclName="acl_view_settings_bitcoin">
|
||||||
|
<BitcoinWalletSettings />
|
||||||
|
</AclProxy>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default SettingsContainer;
|
@ -178,7 +178,7 @@ let SlidesContainer = React.createClass({
|
|||||||
let breadcrumbs = [];
|
let breadcrumbs = [];
|
||||||
|
|
||||||
ReactAddons.Children.map(this.props.children, (child, i) => {
|
ReactAddons.Children.map(this.props.children, (child, i) => {
|
||||||
if(i >= this.state.startFrom && child.props['data-slide-title']) {
|
if(child && i >= this.state.startFrom && child.props['data-slide-title']) {
|
||||||
breadcrumbs.push(child.props['data-slide-title']);
|
breadcrumbs.push(child.props['data-slide-title']);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -229,7 +229,7 @@ let SlidesContainer = React.createClass({
|
|||||||
|
|
||||||
// since the default parameter of startFrom is -1, we do not need to check
|
// since the default parameter of startFrom is -1, we do not need to check
|
||||||
// if its actually present in the url bar, as it will just not match
|
// if its actually present in the url bar, as it will just not match
|
||||||
if(i >= this.state.startFrom) {
|
if(child && i >= this.state.startFrom) {
|
||||||
return ReactAddons.addons.cloneWithProps(child, {
|
return ReactAddons.addons.cloneWithProps(child, {
|
||||||
className: 'ascribe-slide',
|
className: 'ascribe-slide',
|
||||||
style: {
|
style: {
|
||||||
|
@ -6,15 +6,15 @@ import React from 'react';
|
|||||||
let TableItemAclFiltered = React.createClass({
|
let TableItemAclFiltered = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
content: React.PropTypes.object,
|
content: React.PropTypes.object,
|
||||||
requestAction: React.PropTypes.string
|
notifications: React.PropTypes.string
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var availableAcls = ['acl_consign', 'acl_loan', 'acl_transfer', 'acl_view', 'acl_share', 'acl_unshare', 'acl_delete'];
|
var availableAcls = ['acl_consign', 'acl_loan', 'acl_transfer', 'acl_view', 'acl_share', 'acl_unshare', 'acl_delete'];
|
||||||
if (this.props.requestAction && this.props.requestAction.length > 0){
|
if (this.props.notifications && this.props.notifications.length > 0){
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{this.props.requestAction[0].action + ' request pending'}
|
{this.props.notifications[0].action_str}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,20 +6,14 @@ import ProgressBar from 'react-bootstrap/lib/ProgressBar';
|
|||||||
import FileDragAndDropDialog from './file_drag_and_drop_dialog';
|
import FileDragAndDropDialog from './file_drag_and_drop_dialog';
|
||||||
import FileDragAndDropPreviewIterator from './file_drag_and_drop_preview_iterator';
|
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
|
// Taken from: https://github.com/fedosejev/react-file-drag-and-drop
|
||||||
let FileDragAndDrop = React.createClass({
|
let FileDragAndDrop = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
className: React.PropTypes.string,
|
|
||||||
onDragStart: React.PropTypes.func,
|
|
||||||
onDrop: React.PropTypes.func.isRequired,
|
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,
|
onDragOver: React.PropTypes.func,
|
||||||
onDragEnd: React.PropTypes.func,
|
|
||||||
onInactive: React.PropTypes.func,
|
onInactive: React.PropTypes.func,
|
||||||
filesToUpload: React.PropTypes.array,
|
filesToUpload: React.PropTypes.array,
|
||||||
handleDeleteFile: React.PropTypes.func,
|
handleDeleteFile: React.PropTypes.func,
|
||||||
@ -37,37 +31,16 @@ let FileDragAndDrop = React.createClass({
|
|||||||
hashingProgress: React.PropTypes.number,
|
hashingProgress: React.PropTypes.number,
|
||||||
// sets the value of this.state.hashingProgress in reactfineuploader
|
// sets the value of this.state.hashingProgress in reactfineuploader
|
||||||
// to -1 which is code for: aborted
|
// to -1 which is code for: aborted
|
||||||
handleCancelHashing: React.PropTypes.func
|
handleCancelHashing: React.PropTypes.func,
|
||||||
},
|
|
||||||
|
|
||||||
handleDragStart(event) {
|
// A class of a file the user has to upload
|
||||||
if (typeof this.props.onDragStart === 'function') {
|
// Needs to be defined both in singular as well as in plural
|
||||||
this.props.onDragStart(event);
|
fileClassToUpload: React.PropTypes.shape({
|
||||||
}
|
singular: React.PropTypes.string,
|
||||||
},
|
plural: React.PropTypes.string
|
||||||
|
}),
|
||||||
|
|
||||||
handleDrag(event) {
|
allowedExtensions: React.PropTypes.string
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleDragOver(event) {
|
handleDragOver(event) {
|
||||||
@ -159,14 +132,27 @@ let FileDragAndDrop = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render: function () {
|
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
|
// 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 hasFiles = filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1).length > 0;
|
||||||
let className = hasFiles ? 'has-files ' : '';
|
let updatedClassName = hasFiles ? 'has-files ' : '';
|
||||||
className += this.props.dropzoneInactive ? 'inactive-dropzone' : 'active-dropzone';
|
updatedClassName += dropzoneInactive ? 'inactive-dropzone' : 'active-dropzone';
|
||||||
className += this.props.className ? ' ' + this.props.className : '';
|
updatedClassName += ' file-drag-and-drop';
|
||||||
|
|
||||||
// if !== -2: triggers a FileDragAndDrop-global spinner
|
// if !== -2: triggers a FileDragAndDrop-global spinner
|
||||||
if(this.props.hashingProgress !== -2) {
|
if(hashingProgress !== -2) {
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<div className="file-drag-and-drop-hashing-dialog">
|
<div className="file-drag-and-drop-hashing-dialog">
|
||||||
@ -184,29 +170,26 @@ let FileDragAndDrop = React.createClass({
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={className}
|
className={updatedClassName}
|
||||||
onDragStart={this.handleDragStart}
|
|
||||||
onDrag={this.handleDrop}
|
onDrag={this.handleDrop}
|
||||||
onDragEnter={this.handleDragEnter}
|
|
||||||
onDragLeave={this.handleDragLeave}
|
|
||||||
onDragOver={this.handleDragOver}
|
onDragOver={this.handleDragOver}
|
||||||
onDrop={this.handleDrop}
|
onDrop={this.handleDrop}>
|
||||||
onDragEnd={this.handleDragEnd}>
|
|
||||||
<FileDragAndDropDialog
|
<FileDragAndDropDialog
|
||||||
multipleFiles={this.props.multiple}
|
multipleFiles={multiple}
|
||||||
hasFiles={hasFiles}
|
hasFiles={hasFiles}
|
||||||
onClick={this.handleOnClick}
|
onClick={this.handleOnClick}
|
||||||
enableLocalHashing={this.props.enableLocalHashing}/>
|
enableLocalHashing={enableLocalHashing}
|
||||||
|
fileClassToUpload={fileClassToUpload}/>
|
||||||
<FileDragAndDropPreviewIterator
|
<FileDragAndDropPreviewIterator
|
||||||
files={this.props.filesToUpload}
|
files={filesToUpload}
|
||||||
handleDeleteFile={this.handleDeleteFile}
|
handleDeleteFile={this.handleDeleteFile}
|
||||||
handleCancelFile={this.handleCancelFile}
|
handleCancelFile={this.handleCancelFile}
|
||||||
handlePauseFile={this.handlePauseFile}
|
handlePauseFile={this.handlePauseFile}
|
||||||
handleResumeFile={this.handleResumeFile}
|
handleResumeFile={this.handleResumeFile}
|
||||||
areAssetsDownloadable={this.props.areAssetsDownloadable}
|
areAssetsDownloadable={areAssetsDownloadable}
|
||||||
areAssetsEditable={this.props.areAssetsEditable}/>
|
areAssetsEditable={areAssetsEditable}/>
|
||||||
<input
|
<input
|
||||||
multiple={this.props.multiple}
|
multiple={multiple}
|
||||||
ref="fileinput"
|
ref="fileinput"
|
||||||
type="file"
|
type="file"
|
||||||
style={{
|
style={{
|
||||||
@ -214,7 +197,8 @@ let FileDragAndDrop = React.createClass({
|
|||||||
height: 0,
|
height: 0,
|
||||||
width: 0
|
width: 0
|
||||||
}}
|
}}
|
||||||
onChange={this.handleDrop} />
|
onChange={this.handleDrop}
|
||||||
|
accept={allowedExtensions}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -3,7 +3,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Router from 'react-router';
|
import Router from 'react-router';
|
||||||
|
|
||||||
import { getLangText } from '../../utils/lang_utils';
|
import { getLangText } from '../../../utils/lang_utils';
|
||||||
|
|
||||||
let Link = Router.Link;
|
let Link = Router.Link;
|
||||||
|
|
||||||
@ -12,7 +12,14 @@ let FileDragAndDropDialog = React.createClass({
|
|||||||
hasFiles: React.PropTypes.bool,
|
hasFiles: React.PropTypes.bool,
|
||||||
multipleFiles: React.PropTypes.bool,
|
multipleFiles: React.PropTypes.bool,
|
||||||
onClick: React.PropTypes.func,
|
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],
|
mixins: [Router.State],
|
||||||
@ -56,29 +63,29 @@ let FileDragAndDropDialog = React.createClass({
|
|||||||
} else {
|
} else {
|
||||||
if(this.props.multipleFiles) {
|
if(this.props.multipleFiles) {
|
||||||
return (
|
return (
|
||||||
<div className="file-drag-and-drop-dialog">
|
<span className="file-drag-and-drop-dialog">
|
||||||
<p>{getLangText('Drag files here')}</p>
|
<p>{getLangText('Drag %s here', this.props.fileClassToUpload.plural)}</p>
|
||||||
<p>{getLangText('or')}</p>
|
<p>{getLangText('or')}</p>
|
||||||
<span
|
<span
|
||||||
className="btn btn-default"
|
className="btn btn-default"
|
||||||
onClick={this.props.onClick}>
|
onClick={this.props.onClick}>
|
||||||
{getLangText('choose files to upload')}
|
{getLangText('choose %s to upload', this.props.fileClassToUpload.plural)}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
} else {
|
} 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 (
|
return (
|
||||||
<div className="file-drag-and-drop-dialog">
|
<span className="file-drag-and-drop-dialog">
|
||||||
<p>{getLangText('Drag a file here')}</p>
|
<p>{getLangText('Drag a %s here', this.props.fileClassToUpload.singular)}</p>
|
||||||
<p>{getLangText('or')}</p>
|
<p>{getLangText('or')}</p>
|
||||||
<span
|
<span
|
||||||
className="btn btn-default"
|
className="btn btn-default"
|
||||||
onClick={this.props.onClick}>
|
onClick={this.props.onClick}>
|
||||||
{dialog}
|
{dialog}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,7 +4,9 @@ import React from 'react';
|
|||||||
|
|
||||||
import FileDragAndDropPreviewImage from './file_drag_and_drop_preview_image';
|
import FileDragAndDropPreviewImage from './file_drag_and_drop_preview_image';
|
||||||
import FileDragAndDropPreviewOther from './file_drag_and_drop_preview_other';
|
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({
|
let FileDragAndDropPreview = React.createClass({
|
||||||
|
|
||||||
@ -43,6 +45,7 @@ let FileDragAndDropPreview = React.createClass({
|
|||||||
|
|
||||||
handleDownloadFile() {
|
handleDownloadFile() {
|
||||||
if(this.props.file.s3Url) {
|
if(this.props.file.s3Url) {
|
||||||
|
// This simply opens a new browser tab with the url provided
|
||||||
open(this.props.file.s3Url);
|
open(this.props.file.s3Url);
|
||||||
}
|
}
|
||||||
},
|
},
|
@ -3,8 +3,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
|
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
|
||||||
|
|
||||||
import AppConstants from '../../constants/application_constants';
|
import AppConstants from '../../../constants/application_constants';
|
||||||
import { getLangText } from '../../utils/lang_utils.js';
|
import { getLangText } from '../../../utils/lang_utils';
|
||||||
|
|
||||||
let FileDragAndDropPreviewImage = React.createClass({
|
let FileDragAndDropPreviewImage = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
@ -5,7 +5,7 @@ import React from 'react';
|
|||||||
import FileDragAndDropPreview from './file_drag_and_drop_preview';
|
import FileDragAndDropPreview from './file_drag_and_drop_preview';
|
||||||
import FileDragAndDropPreviewProgress from './file_drag_and_drop_preview_progress';
|
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({
|
let FileDragAndDropPreviewIterator = React.createClass({
|
@ -3,8 +3,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
|
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
|
||||||
|
|
||||||
import AppConstants from '../../constants/application_constants';
|
import AppConstants from '../../../constants/application_constants';
|
||||||
import { getLangText } from '../../utils/lang_utils.js';
|
import { getLangText } from '../../../utils/lang_utils';
|
||||||
|
|
||||||
let FileDragAndDropPreviewOther = React.createClass({
|
let FileDragAndDropPreviewOther = React.createClass({
|
||||||
propTypes: {
|
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-table-wrapper">
|
||||||
<div className="file-drag-and-drop-preview-other">
|
<div className="file-drag-and-drop-preview-other">
|
||||||
{actionSymbol}
|
{actionSymbol}
|
||||||
<span>{'.' + this.props.type}</span>
|
<p>{'.' + this.props.type}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -4,7 +4,8 @@ import React from 'react';
|
|||||||
|
|
||||||
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
|
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({
|
let FileDragAndDropPreviewProgress = React.createClass({
|
||||||
@ -54,7 +55,7 @@ let FileDragAndDropPreviewProgress = React.createClass({
|
|||||||
return (
|
return (
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
now={Math.ceil(overallProgress)}
|
now={Math.ceil(overallProgress)}
|
||||||
label="Overall progress: %(percent)s%"
|
label={getLangText('Overall progress%s', ': %(percent)s%')}
|
||||||
className="ascribe-progress-bar"
|
className="ascribe-progress-bar"
|
||||||
style={style} />
|
style={style} />
|
||||||
);
|
);
|
@ -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;
|
@ -7,7 +7,7 @@ import Q from 'q';
|
|||||||
|
|
||||||
import S3Fetcher from '../../fetchers/s3_fetcher';
|
import S3Fetcher from '../../fetchers/s3_fetcher';
|
||||||
|
|
||||||
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 GlobalNotificationModel from '../../models/global_notification_model';
|
||||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||||
@ -15,12 +15,12 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
|
|||||||
import AppConstants from '../../constants/application_constants';
|
import AppConstants from '../../constants/application_constants';
|
||||||
|
|
||||||
import { computeHashOfFile } from '../../utils/file_utils';
|
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 { getCookie } from '../../utils/fetch_api_utils';
|
||||||
import { getLangText } from '../../utils/lang_utils';
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
|
|
||||||
|
|
||||||
var ReactS3FineUploader = React.createClass({
|
let ReactS3FineUploader = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
keyRoutine: React.PropTypes.shape({
|
keyRoutine: React.PropTypes.shape({
|
||||||
url: React.PropTypes.string,
|
url: React.PropTypes.string,
|
||||||
@ -37,7 +37,7 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
React.PropTypes.number
|
React.PropTypes.number
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
submitKey: React.PropTypes.func,
|
submitFile: React.PropTypes.func,
|
||||||
autoUpload: React.PropTypes.bool,
|
autoUpload: React.PropTypes.bool,
|
||||||
debug: React.PropTypes.bool,
|
debug: React.PropTypes.bool,
|
||||||
objectProperties: React.PropTypes.shape({
|
objectProperties: React.PropTypes.shape({
|
||||||
@ -84,7 +84,8 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
}),
|
}),
|
||||||
validation: React.PropTypes.shape({
|
validation: React.PropTypes.shape({
|
||||||
itemLimit: React.PropTypes.number,
|
itemLimit: React.PropTypes.number,
|
||||||
sizeLimit: React.PropTypes.string
|
sizeLimit: React.PropTypes.string,
|
||||||
|
allowedExtensions: React.PropTypes.arrayOf(React.PropTypes.string)
|
||||||
}),
|
}),
|
||||||
messages: React.PropTypes.shape({
|
messages: React.PropTypes.shape({
|
||||||
unsupportedBrowser: React.PropTypes.string
|
unsupportedBrowser: React.PropTypes.string
|
||||||
@ -111,7 +112,22 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
enableLocalHashing: React.PropTypes.bool,
|
enableLocalHashing: React.PropTypes.bool,
|
||||||
|
|
||||||
// automatically injected by React-Router
|
// 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],
|
mixins: [Router.State],
|
||||||
@ -163,7 +179,12 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
return name;
|
return name;
|
||||||
},
|
},
|
||||||
multiple: false,
|
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
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -313,6 +334,9 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
} else if(res.digitalwork) {
|
} else if(res.digitalwork) {
|
||||||
file.s3Url = res.digitalwork.url_safe;
|
file.s3Url = res.digitalwork.url_safe;
|
||||||
file.s3UrlSafe = res.digitalwork.url_safe;
|
file.s3UrlSafe = res.digitalwork.url_safe;
|
||||||
|
} else if(res.contractblob) {
|
||||||
|
file.s3Url = res.contractblob.url_safe;
|
||||||
|
file.s3UrlSafe = res.contractblob.url_safe;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(getLangText('Could not find a url to download.'));
|
throw new Error(getLangText('Could not find a url to download.'));
|
||||||
}
|
}
|
||||||
@ -384,12 +408,12 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
// Only after the blob has been created server-side, we can make the form submittable.
|
// Only after the blob has been created server-side, we can make the form submittable.
|
||||||
this.createBlob(files[id])
|
this.createBlob(files[id])
|
||||||
.then(() => {
|
.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
|
// are optional, we'll only trigger them when they're actually defined
|
||||||
if(this.props.submitKey) {
|
if(this.props.submitFile) {
|
||||||
this.props.submitKey(files[id].key);
|
this.props.submitFile(files[id]);
|
||||||
} else {
|
} 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
|
// for explanation, check comment of if statement above
|
||||||
@ -424,7 +448,7 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
});
|
});
|
||||||
this.state.uploader.cancelAll();
|
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);
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -449,7 +473,7 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
let notification = new GlobalNotificationModel(getLangText('File upload canceled'), 'success', 5000);
|
let notification = new GlobalNotificationModel(getLangText('File upload canceled'), 'success', 5000);
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
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
|
// are optional, we'll only trigger them when they're actually defined
|
||||||
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
|
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
|
||||||
if(this.props.isReadyForFormSubmission(this.state.filesToUpload)) {
|
if(this.props.isReadyForFormSubmission(this.state.filesToUpload)) {
|
||||||
@ -516,7 +540,7 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
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
|
// are optional, we'll only trigger them when they're actually defined
|
||||||
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
|
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
|
||||||
// also, lets check if after the completion of this upload,
|
// also, lets check if after the completion of this upload,
|
||||||
@ -541,7 +565,7 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
this.setStatusOfFile(fileId, 'deleted');
|
this.setStatusOfFile(fileId, 'deleted');
|
||||||
|
|
||||||
// In some instances (when the file was already uploaded and is just displayed to the user
|
// In some instances (when the file was already uploaded and is just displayed to the user
|
||||||
// - for example in the loan contract or additional files dialog)
|
// - for example in the contract or additional files dialog)
|
||||||
// fineuploader does not register an id on the file (we do, don't be confused by this!).
|
// fineuploader does not register an id on the file (we do, don't be confused by this!).
|
||||||
// Since you can only delete a file by its id, we have to implement this method ourselves
|
// Since you can only delete a file by its id, we have to implement this method ourselves
|
||||||
//
|
//
|
||||||
@ -816,27 +840,48 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getAllowedExtensions() {
|
||||||
|
let { validation } = this.props;
|
||||||
|
|
||||||
|
if(validation && validation.allowedExtensions && validation.allowedExtensions.length > 0) {
|
||||||
|
return transformAllowedExtensionsToInputAcceptProp(validation.allowedExtensions);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
let {
|
||||||
<div>
|
multiple,
|
||||||
<FileDragAndDrop
|
areAssetsDownloadable,
|
||||||
className="file-drag-and-drop"
|
areAssetsEditable,
|
||||||
onDrop={this.handleUploadFile}
|
onInactive,
|
||||||
filesToUpload={this.state.filesToUpload}
|
enableLocalHashing,
|
||||||
handleDeleteFile={this.handleDeleteFile}
|
fileClassToUpload,
|
||||||
handleCancelFile={this.handleCancelFile}
|
validation,
|
||||||
handlePauseFile={this.handlePauseFile}
|
fileInputElement
|
||||||
handleResumeFile={this.handleResumeFile}
|
} = this.props;
|
||||||
handleCancelHashing={this.handleCancelHashing}
|
|
||||||
multiple={this.props.multiple}
|
// Here we initialize the template that has been either provided from the outside
|
||||||
areAssetsDownloadable={this.props.areAssetsDownloadable}
|
// or the default input that is FileDragAndDrop.
|
||||||
areAssetsEditable={this.props.areAssetsEditable}
|
return React.createElement(fileInputElement, {
|
||||||
onInactive={this.props.onInactive}
|
onDrop: this.handleUploadFile,
|
||||||
dropzoneInactive={this.isDropzoneInactive()}
|
filesToUpload: this.state.filesToUpload,
|
||||||
hashingProgress={this.state.hashingProgress}
|
handleDeleteFile: this.handleDeleteFile,
|
||||||
enableLocalHashing={this.props.enableLocalHashing} />
|
handleCancelFile: this.handleCancelFile,
|
||||||
</div>
|
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()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,38 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
export const formSubmissionValidation = {
|
||||||
|
/**
|
||||||
|
* Returns a boolean if there has been at least one file uploaded
|
||||||
|
* successfully without it being deleted or canceled.
|
||||||
|
* @param {array of files} files provided by react fine uploader
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
atLeastOneUploadedFile(files) {
|
||||||
|
files = files.filter((file) => file.status !== 'deleted' && file.status !== 'canceled');
|
||||||
|
if (files.length > 0 && files[0].status === 'upload successful') {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File submission for the form is optional, but if the user decides to submit a file
|
||||||
|
* the form is not ready until there are no more files currently uploading.
|
||||||
|
* @param {array of files} files files provided by react fine uploader
|
||||||
|
* @return {boolean} [description]
|
||||||
|
*/
|
||||||
|
fileOptional(files) {
|
||||||
|
let uploadingFiles = files.filter((file) => file.status === 'submitting');
|
||||||
|
|
||||||
|
if (uploadingFiles.length === 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter function for filtering all deleted and canceled files
|
* Filter function for filtering all deleted and canceled files
|
||||||
* @param {object} file A file from filesToUpload that has status as a prop.
|
* @param {object} file A file from filesToUpload that has status as a prop.
|
||||||
@ -9,20 +42,6 @@ export function displayValidFilesFilter(file) {
|
|||||||
return file.status !== 'deleted' && file.status !== 'canceled';
|
return file.status !== 'deleted' && file.status !== 'canceled';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a boolean if there has been at least one file uploaded
|
|
||||||
* successfully without it being deleted or canceled.
|
|
||||||
* @param {array of files} files provided by react fine uploader
|
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
|
||||||
export function isReadyForFormSubmission(files) {
|
|
||||||
files = files.filter(displayValidFilesFilter);
|
|
||||||
if (files.length > 0 && files[0].status === 'upload successful') {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter function for which files to integrate in the progress process
|
* Filter function for which files to integrate in the progress process
|
||||||
@ -32,3 +51,23 @@ export function isReadyForFormSubmission(files) {
|
|||||||
export function displayValidProgressFilesFilter(file) {
|
export function displayValidProgressFilesFilter(file) {
|
||||||
return file.status !== 'deleted' && file.status !== 'canceled' && file.status !== 'online';
|
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(', ');
|
||||||
|
}
|
||||||
|
@ -84,10 +84,11 @@ let CoaVerifyForm = React.createClass({
|
|||||||
</Property>
|
</Property>
|
||||||
<Property
|
<Property
|
||||||
name='signature'
|
name='signature'
|
||||||
label="Signature">
|
label="Signature"
|
||||||
|
editable={true}
|
||||||
|
overrideForm={true}>
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={3}
|
rows={3}
|
||||||
editable={true}
|
|
||||||
placeholder={getLangText('Copy paste the signature on the bottom of your Certificate of Authenticity')}
|
placeholder={getLangText('Copy paste the signature on the bottom of your Certificate of Authenticity')}
|
||||||
required/>
|
required/>
|
||||||
</Property>
|
</Property>
|
||||||
|
36
js/components/contract_notification.js
Normal file
36
js/components/contract_notification.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import NotificationStore from '../stores/notification_store';
|
||||||
|
|
||||||
|
import { mergeOptions } from '../utils/general_utils';
|
||||||
|
|
||||||
|
let ContractNotification = React.createClass({
|
||||||
|
getInitialState() {
|
||||||
|
return mergeOptions(
|
||||||
|
NotificationStore.getState()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
NotificationStore.listen(this.onChange);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
NotificationStore.unlisten(this.onChange);
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange(state) {
|
||||||
|
this.setState(state);
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ContractNotification;
|
43
js/components/global_action.js
Normal file
43
js/components/global_action.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
let GlobalAction = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
requestActions: React.PropTypes.object
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let pieceActions = null;
|
||||||
|
if (this.props.requestActions && this.props.requestActions.pieces){
|
||||||
|
pieceActions = this.props.requestActions.pieces.map((item) => {
|
||||||
|
return (
|
||||||
|
<div className="ascribe-global-action">
|
||||||
|
{item}
|
||||||
|
</div>);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let editionActions = null;
|
||||||
|
if (this.props.requestActions && this.props.requestActions.editions){
|
||||||
|
editionActions = Object.keys(this.props.requestActions.editions).map((pieceId) => {
|
||||||
|
return this.props.requestActions.editions[pieceId].map((item) => {
|
||||||
|
return (
|
||||||
|
<div className="ascribe-global-action">
|
||||||
|
{item}
|
||||||
|
</div>);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pieceActions || editionActions) {
|
||||||
|
return (
|
||||||
|
<div className="ascribe-global-action-wrapper">
|
||||||
|
{pieceActions}
|
||||||
|
{editionActions}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default GlobalAction;
|
@ -2,13 +2,6 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Router from 'react-router';
|
import Router from 'react-router';
|
||||||
import Favico from 'favico.js';
|
|
||||||
import UserActions from '../actions/user_actions';
|
|
||||||
import UserStore from '../stores/user_store';
|
|
||||||
|
|
||||||
import WhitelabelActions from '../actions/whitelabel_actions';
|
|
||||||
import WhitelabelStore from '../stores/whitelabel_store';
|
|
||||||
import EventActions from '../actions/event_actions';
|
|
||||||
|
|
||||||
import Nav from 'react-bootstrap/lib/Nav';
|
import Nav from 'react-bootstrap/lib/Nav';
|
||||||
import Navbar from 'react-bootstrap/lib/Navbar';
|
import Navbar from 'react-bootstrap/lib/Navbar';
|
||||||
@ -18,6 +11,17 @@ import MenuItem from 'react-bootstrap/lib/MenuItem';
|
|||||||
import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink';
|
import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink';
|
||||||
import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
|
import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
|
||||||
|
|
||||||
|
import AclProxy from './acl_proxy';
|
||||||
|
|
||||||
|
import UserActions from '../actions/user_actions';
|
||||||
|
import UserStore from '../stores/user_store';
|
||||||
|
|
||||||
|
import WhitelabelActions from '../actions/whitelabel_actions';
|
||||||
|
import WhitelabelStore from '../stores/whitelabel_store';
|
||||||
|
import EventActions from '../actions/event_actions';
|
||||||
|
|
||||||
|
import HeaderNotifications from './header_notification';
|
||||||
|
|
||||||
import HeaderNotificationDebug from './header_notification_debug';
|
import HeaderNotificationDebug from './header_notification_debug';
|
||||||
|
|
||||||
import NavRoutesLinks from './nav_routes_links';
|
import NavRoutesLinks from './nav_routes_links';
|
||||||
@ -25,6 +29,7 @@ import NavRoutesLinks from './nav_routes_links';
|
|||||||
import { mergeOptions } from '../utils/general_utils';
|
import { mergeOptions } from '../utils/general_utils';
|
||||||
import { getLangText } from '../utils/lang_utils';
|
import { getLangText } from '../utils/lang_utils';
|
||||||
|
|
||||||
|
let setFavicon = require('favicon-setter');
|
||||||
|
|
||||||
let Header = React.createClass({
|
let Header = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
@ -41,7 +46,10 @@ let Header = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return mergeOptions(WhitelabelStore.getState(), UserStore.getState());
|
return mergeOptions(
|
||||||
|
WhitelabelStore.getState(),
|
||||||
|
UserStore.getState()
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -56,29 +64,31 @@ let Header = React.createClass({
|
|||||||
WhitelabelStore.unlisten(this.onChange);
|
WhitelabelStore.unlisten(this.onChange);
|
||||||
},
|
},
|
||||||
getLogo(){
|
getLogo(){
|
||||||
let logo = (
|
if (this.state.whitelabel && this.state.whitelabel.logo){
|
||||||
|
let logoPath = this.state.whitelabel.logo;
|
||||||
|
let logo = <img className="img-brand" src={logoPath} />;
|
||||||
|
console.log('should change browser icon');
|
||||||
|
console.log(logoPath);
|
||||||
|
try {
|
||||||
|
setFavicon(logoPath);
|
||||||
|
}
|
||||||
|
catch (e){
|
||||||
|
console.log(e.message());
|
||||||
|
}
|
||||||
|
return logo;
|
||||||
|
}
|
||||||
|
return (
|
||||||
<span>
|
<span>
|
||||||
<span>ascribe </span>
|
<span>ascribe </span>
|
||||||
<span className="glyph-ascribe-spool-chunked ascribe-color"></span>
|
<span className="glyph-ascribe-spool-chunked ascribe-color"></span>
|
||||||
</span>);
|
</span>);
|
||||||
if (this.state.whitelabel && this.state.whitelabel.logo){
|
|
||||||
let logoPath = this.state.whitelabel.logo;
|
|
||||||
logo = <img className="img-brand" src={logoPath} />;
|
|
||||||
let favicon = new Favico();
|
|
||||||
let image = new Image();
|
|
||||||
image.src = logoPath;
|
|
||||||
console.log('should change browser icon');
|
|
||||||
console.log(logoPath);
|
|
||||||
favicon.image(image);
|
|
||||||
console.log(image);
|
|
||||||
console.log(favicon);
|
|
||||||
}
|
|
||||||
return logo;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getPoweredBy(){
|
getPoweredBy(){
|
||||||
if (this.state.whitelabel && this.state.whitelabel.logo) {
|
|
||||||
return (
|
return (
|
||||||
|
<AclProxy
|
||||||
|
aclObject={this.state.whitelabel}
|
||||||
|
aclName="acl_view_powered_by">
|
||||||
<li>
|
<li>
|
||||||
<a className="pull-right" href="https://www.ascribe.io/" target="_blank">
|
<a className="pull-right" href="https://www.ascribe.io/" target="_blank">
|
||||||
<span id="powered">{getLangText('powered by')} </span>
|
<span id="powered">{getLangText('powered by')} </span>
|
||||||
@ -86,29 +96,72 @@ let Header = React.createClass({
|
|||||||
<span className="glyph-ascribe-spool-chunked ascribe-color"></span>
|
<span className="glyph-ascribe-spool-chunked ascribe-color"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
</AclProxy>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onChange(state) {
|
onChange(state) {
|
||||||
this.setState(state);
|
this.setState(state);
|
||||||
if(this.state.currentUser && this.state.currentUser.email) {
|
if(this.state.currentUser && this.state.currentUser.email) {
|
||||||
EventActions.profileDidLoad.defer(this.state.currentUser);
|
EventActions.profileDidLoad.defer(this.state.currentUser);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onMenuItemClick() {
|
||||||
|
/*
|
||||||
|
This is a hack to make the dropdown close after clicking on an item
|
||||||
|
The function just need to be defined
|
||||||
|
|
||||||
|
from https://github.com/react-bootstrap/react-bootstrap/issues/368:
|
||||||
|
|
||||||
|
@jvillasante - Have you tried to use onSelect with the DropdownButton?
|
||||||
|
I don't have a working example that is exactly like yours,
|
||||||
|
but I just noticed that the Dropdown closes when I've attached an event handler to OnSelect:
|
||||||
|
|
||||||
|
<DropdownButton eventKey={3} title="Admin" onSelect={ this.OnSelected } >
|
||||||
|
|
||||||
|
onSelected: function(e) {
|
||||||
|
// doesn't need to have functionality (necessarily) ... just wired up
|
||||||
|
}
|
||||||
|
Internally, a call to DropdownButton.setDropDownState(false) is made which will hide the dropdown menu.
|
||||||
|
So, you should be able to call that directly on the DropdownButton instance as well if needed.
|
||||||
|
|
||||||
|
NOW, THAT DIDN'T WORK - the onSelect routine isnt triggered in all cases
|
||||||
|
Hence, we do this manually
|
||||||
|
*/
|
||||||
|
this.refs.dropdownbutton.setDropdownState(false);
|
||||||
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let account;
|
let account;
|
||||||
let signup;
|
let signup;
|
||||||
let navRoutesLinks;
|
let navRoutesLinks;
|
||||||
if (this.state.currentUser.username){
|
if (this.state.currentUser.username){
|
||||||
account = (
|
account = (
|
||||||
<DropdownButton eventKey="1" title={this.state.currentUser.username}>
|
<DropdownButton
|
||||||
<MenuItemLink eventKey="2" to="settings">{getLangText('Account Settings')}</MenuItemLink>
|
ref='dropdownbutton'
|
||||||
|
eventKey="1"
|
||||||
|
title={this.state.currentUser.username}>
|
||||||
|
<MenuItemLink
|
||||||
|
eventKey="2"
|
||||||
|
to="settings"
|
||||||
|
onClick={this.onMenuItemClick}>
|
||||||
|
{getLangText('Account Settings')}
|
||||||
|
</MenuItemLink>
|
||||||
|
<AclProxy
|
||||||
|
aclObject={this.state.currentUser.acl}
|
||||||
|
aclName="acl_view_settings_contract">
|
||||||
|
<MenuItemLink
|
||||||
|
to="contract_settings"
|
||||||
|
onClick={this.onMenuItemClick}>
|
||||||
|
{getLangText('Contract Settings')}
|
||||||
|
</MenuItemLink>
|
||||||
|
</AclProxy>
|
||||||
<MenuItem divider />
|
<MenuItem divider />
|
||||||
<MenuItemLink eventKey="3" to="logout">{getLangText('Log out')}</MenuItemLink>
|
<MenuItemLink eventKey="3" to="logout">{getLangText('Log out')}</MenuItemLink>
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
);
|
);
|
||||||
navRoutesLinks = <NavRoutesLinks routes={this.props.routes} navbar right/>;
|
navRoutesLinks = <NavRoutesLinks routes={this.props.routes} userAcl={this.state.currentUser.acl} navbar right/>;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
account = <NavItemLink to="login">{getLangText('LOGIN')}</NavItemLink>;
|
account = <NavItemLink to="login">{getLangText('LOGIN')}</NavItemLink>;
|
||||||
@ -132,6 +185,7 @@ let Header = React.createClass({
|
|||||||
{account}
|
{account}
|
||||||
{signup}
|
{signup}
|
||||||
</Nav>
|
</Nav>
|
||||||
|
<HeaderNotifications />
|
||||||
{navRoutesLinks}
|
{navRoutesLinks}
|
||||||
</CollapsibleNav>
|
</CollapsibleNav>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
218
js/components/header_notification.js
Normal file
218
js/components/header_notification.js
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Router from 'react-router';
|
||||||
|
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||||
|
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
|
||||||
|
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
||||||
|
|
||||||
|
import Nav from 'react-bootstrap/lib/Nav';
|
||||||
|
|
||||||
|
import NotificationActions from '../actions/notification_actions';
|
||||||
|
import NotificationStore from '../stores/notification_store';
|
||||||
|
|
||||||
|
import { mergeOptions } from '../utils/general_utils';
|
||||||
|
import { getLangText } from '../utils/lang_utils';
|
||||||
|
|
||||||
|
let Link = Router.Link;
|
||||||
|
|
||||||
|
|
||||||
|
let HeaderNotifications = React.createClass({
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return mergeOptions(
|
||||||
|
NotificationStore.getState()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
NotificationStore.listen(this.onChange);
|
||||||
|
NotificationActions.fetchPieceListNotifications();
|
||||||
|
NotificationActions.fetchEditionListNotifications();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
NotificationStore.unlisten(this.onChange);
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange(state) {
|
||||||
|
this.setState(state);
|
||||||
|
},
|
||||||
|
|
||||||
|
onMenuItemClick(event) {
|
||||||
|
/*
|
||||||
|
This is a hack to make the dropdown close after clicking on an item
|
||||||
|
The function just need to be defined
|
||||||
|
|
||||||
|
from https://github.com/react-bootstrap/react-bootstrap/issues/368:
|
||||||
|
|
||||||
|
@jvillasante - Have you tried to use onSelect with the DropdownButton?
|
||||||
|
I don't have a working example that is exactly like yours,
|
||||||
|
but I just noticed that the Dropdown closes when I've attached an event handler to OnSelect:
|
||||||
|
|
||||||
|
<DropdownButton eventKey={3} title="Admin" onSelect={ this.OnSelected } >
|
||||||
|
|
||||||
|
onSelected: function(e) {
|
||||||
|
// doesn't need to have functionality (necessarily) ... just wired up
|
||||||
|
}
|
||||||
|
Internally, a call to DropdownButton.setDropDownState(false) is made which will hide the dropdown menu.
|
||||||
|
So, you should be able to call that directly on the DropdownButton instance as well if needed.
|
||||||
|
|
||||||
|
NOW, THAT DIDN'T WORK - the onSelect routine isnt triggered in all cases
|
||||||
|
Hence, we do this manually
|
||||||
|
*/
|
||||||
|
this.refs.dropdownbutton.setDropdownState(false);
|
||||||
|
},
|
||||||
|
|
||||||
|
getPieceNotifications(){
|
||||||
|
if (this.state.pieceListNotifications && this.state.pieceListNotifications.length > 0) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="notification-header">
|
||||||
|
Artworks ({this.state.pieceListNotifications.length})
|
||||||
|
</div>
|
||||||
|
{this.state.pieceListNotifications.map((pieceNotification, i) => {
|
||||||
|
return (
|
||||||
|
<MenuItem eventKey={i + 2}>
|
||||||
|
<NotificationListItem
|
||||||
|
ref={i}
|
||||||
|
notification={pieceNotification.notification}
|
||||||
|
pieceOrEdition={pieceNotification.piece}
|
||||||
|
onClick={this.onMenuItemClick}/>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
getEditionNotifications(){
|
||||||
|
if (this.state.editionListNotifications && this.state.editionListNotifications.length > 0) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="notification-header">
|
||||||
|
Editions ({this.state.editionListNotifications.length})
|
||||||
|
</div>
|
||||||
|
{this.state.editionListNotifications.map((editionNotification, i) => {
|
||||||
|
return (
|
||||||
|
<MenuItem eventKey={i + 2}>
|
||||||
|
<NotificationListItem
|
||||||
|
ref={'edition' + i}
|
||||||
|
notification={editionNotification.notification}
|
||||||
|
pieceOrEdition={editionNotification.edition}
|
||||||
|
onClick={this.onMenuItemClick}/>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if ((this.state.pieceListNotifications && this.state.pieceListNotifications.length > 0) ||
|
||||||
|
(this.state.editionListNotifications && this.state.editionListNotifications.length > 0)){
|
||||||
|
let numNotifications = 0;
|
||||||
|
if (this.state.pieceListNotifications && this.state.pieceListNotifications.length > 0) {
|
||||||
|
numNotifications += this.state.pieceListNotifications.length;
|
||||||
|
}
|
||||||
|
if (this.state.editionListNotifications && this.state.editionListNotifications.length > 0) {
|
||||||
|
numNotifications += this.state.editionListNotifications.length;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Nav navbar right>
|
||||||
|
<DropdownButton
|
||||||
|
ref='dropdownbutton'
|
||||||
|
eventKey="1"
|
||||||
|
title={
|
||||||
|
<span>
|
||||||
|
<Glyphicon glyph='envelope' color="green"/>
|
||||||
|
<span className="notification-amount">({numNotifications})</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
className="notification-menu">
|
||||||
|
{this.getPieceNotifications()}
|
||||||
|
{this.getEditionNotifications()}
|
||||||
|
</DropdownButton>
|
||||||
|
</Nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let NotificationListItem = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
notification: React.PropTypes.array,
|
||||||
|
pieceOrEdition: React.PropTypes.object,
|
||||||
|
onClick: React.PropTypes.func
|
||||||
|
},
|
||||||
|
|
||||||
|
isPiece() {
|
||||||
|
return !(this.props.pieceOrEdition && this.props.pieceOrEdition.parent);
|
||||||
|
},
|
||||||
|
|
||||||
|
getLinkData() {
|
||||||
|
|
||||||
|
if (this.isPiece()) {
|
||||||
|
return {
|
||||||
|
to: 'piece',
|
||||||
|
params: {
|
||||||
|
pieceId: this.props.pieceOrEdition.id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
to: 'edition',
|
||||||
|
params: {
|
||||||
|
editionId: this.props.pieceOrEdition.bitcoin_id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
onClick(event){
|
||||||
|
this.props.onClick(event);
|
||||||
|
},
|
||||||
|
|
||||||
|
getNotificationText(){
|
||||||
|
let numNotifications = null;
|
||||||
|
if (this.props.notification.length > 1){
|
||||||
|
numNotifications = <div>+ {this.props.notification.length - 1} more...</div>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="notification-action">
|
||||||
|
{this.props.notification[0].action_str}
|
||||||
|
{numNotifications}
|
||||||
|
</div>);
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.props.pieceOrEdition) {
|
||||||
|
return (
|
||||||
|
<Link {...this.getLinkData()} onClick={this.onClick}>
|
||||||
|
<div className="row notification-wrapper">
|
||||||
|
<div className="col-xs-4 clear-paddings">
|
||||||
|
<div className="thumbnail-wrapper">
|
||||||
|
<img src={this.props.pieceOrEdition.thumbnail.url_safe}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-xs-8 notification-list-item-header">
|
||||||
|
<h1>{this.props.pieceOrEdition.title}</h1>
|
||||||
|
<div className="sub-header">by {this.props.pieceOrEdition.artist_name}</div>
|
||||||
|
{this.getNotificationText()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default HeaderNotifications;
|
@ -19,7 +19,7 @@ let LogoutContainer = React.createClass({
|
|||||||
Alt.flush();
|
Alt.flush();
|
||||||
// kill intercom (with fire)
|
// kill intercom (with fire)
|
||||||
window.Intercom('shutdown');
|
window.Intercom('shutdown');
|
||||||
this.transitionTo(baseUrl);
|
this.replaceWith(baseUrl);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.logGlobal(err);
|
console.logGlobal(err);
|
||||||
|
@ -3,53 +3,80 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import Nav from 'react-bootstrap/lib/Nav';
|
import Nav from 'react-bootstrap/lib/Nav';
|
||||||
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
|
||||||
import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink';
|
import NavRoutesLinksLink from './nav_routes_links_link';
|
||||||
import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
|
|
||||||
|
import AclProxy from './acl_proxy';
|
||||||
|
|
||||||
import { sanitizeList } from '../utils/general_utils';
|
import { sanitizeList } from '../utils/general_utils';
|
||||||
|
|
||||||
|
|
||||||
let NavRoutesLinks = React.createClass({
|
let NavRoutesLinks = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
routes: React.PropTypes.element
|
routes: React.PropTypes.element,
|
||||||
|
userAcl: React.PropTypes.object
|
||||||
},
|
},
|
||||||
|
|
||||||
extractLinksFromRoutes(node, i) {
|
/**
|
||||||
|
* This method generales a bunch of react-bootstrap specific links
|
||||||
|
* from the routes we defined in one of the specific routes.js file
|
||||||
|
*
|
||||||
|
* We can define a headerTitle as well as a aclName and according to that the
|
||||||
|
* link will be created for a specific user
|
||||||
|
* @param {ReactElement} node Starts at the very top of a routes files root
|
||||||
|
* @param {object} userAcl ACL object we use throughout the whole app
|
||||||
|
* @param {number} i Depth of the route in comparison to the root
|
||||||
|
* @return {Array} Array of ReactElements that can be displayed to the user
|
||||||
|
*/
|
||||||
|
extractLinksFromRoutes(node, userAcl, i) {
|
||||||
if(!node) {
|
if(!node) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
node = node.props;
|
let links = node.props.children.map((child, j) => {
|
||||||
|
let childrenFn = null;
|
||||||
|
let { aclName, headerTitle, name, children } = child.props;
|
||||||
|
|
||||||
let links = node.children.map((child, j) => {
|
// If the node has children that could be rendered, then we want
|
||||||
|
// to execute this function again with the child as the root
|
||||||
// check if this a candidate for a link generation
|
//
|
||||||
if(child.props.headerTitle && typeof child.props.headerTitle === 'string') {
|
// Otherwise we'll just pass childrenFn as false
|
||||||
|
|
||||||
// also check if it is a candidate for generating a dropdown menu
|
|
||||||
if(child.props.children && child.props.children.length > 0) {
|
if(child.props.children && child.props.children.length > 0) {
|
||||||
|
childrenFn = this.extractLinksFromRoutes(child, userAcl, i++);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We validate if the user has set the title correctly,
|
||||||
|
// otherwise we're not going to render his route
|
||||||
|
if(headerTitle && typeof headerTitle === 'string') {
|
||||||
|
// if there is an aclName present on the route definition,
|
||||||
|
// we evaluate it against the user's acl
|
||||||
|
if(aclName && typeof aclName !== 'undefined') {
|
||||||
return (
|
return (
|
||||||
<DropdownButton title={child.props.headerTitle} key={j}>
|
<AclProxy
|
||||||
{this.extractLinksFromRoutes(child, i++)}
|
key={j}
|
||||||
</DropdownButton>
|
aclName={aclName}
|
||||||
);
|
aclObject={this.props.userAcl}>
|
||||||
} else if(i === 1) {
|
<NavRoutesLinksLink
|
||||||
// if the node's child is actually a node of level one (a child of a node), we're
|
headerTitle={headerTitle}
|
||||||
// returning a DropdownButton matching MenuItemLink
|
routeName={name}
|
||||||
return (
|
depth={i}
|
||||||
<MenuItemLink to={child.props.name} key={j}>{child.props.headerTitle}</MenuItemLink>
|
children={childrenFn}/>
|
||||||
);
|
</AclProxy>
|
||||||
} else if(i === 0) {
|
|
||||||
return (
|
|
||||||
<NavItemLink to={child.props.name} key={j}>{child.props.headerTitle}</NavItemLink>
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return (
|
||||||
|
<NavRoutesLinksLink
|
||||||
|
key={j}
|
||||||
|
headerTitle={headerTitle}
|
||||||
|
routeName={name}
|
||||||
|
depth={i}
|
||||||
|
children={childrenFn}/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// remove all nulls from the list of generated links
|
// remove all nulls from the list of generated links
|
||||||
@ -57,9 +84,11 @@ let NavRoutesLinks = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
let {routes, userAcl} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Nav {...this.props}>
|
<Nav {...this.props}>
|
||||||
{this.extractLinksFromRoutes(this.props.routes, 0)}
|
{this.extractLinksFromRoutes(routes, userAcl, 0)}
|
||||||
</Nav>
|
</Nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
51
js/components/nav_routes_links_link.js
Normal file
51
js/components/nav_routes_links_link.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||||
|
import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink';
|
||||||
|
import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
|
||||||
|
|
||||||
|
let NavRoutesLinksLink = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
headerTitle: React.PropTypes.string,
|
||||||
|
routeName: React.PropTypes.string,
|
||||||
|
|
||||||
|
children: React.PropTypes.oneOfType([
|
||||||
|
React.PropTypes.arrayOf(React.PropTypes.element),
|
||||||
|
React.PropTypes.element
|
||||||
|
]),
|
||||||
|
|
||||||
|
depth: React.PropTypes.number
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { children, headerTitle, depth, routeName } = this.props;
|
||||||
|
|
||||||
|
// if the route has children, we're returning a DropdownButton that will get filled
|
||||||
|
// with MenuItemLinks
|
||||||
|
if(children) {
|
||||||
|
return (
|
||||||
|
<DropdownButton title={headerTitle}>
|
||||||
|
{children}
|
||||||
|
</DropdownButton>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if(depth === 1) {
|
||||||
|
// if the node's child is actually a node of level one (a child of a node), we're
|
||||||
|
// returning a DropdownButton matching MenuItemLink
|
||||||
|
return (
|
||||||
|
<MenuItemLink to={routeName}>{headerTitle}</MenuItemLink>
|
||||||
|
);
|
||||||
|
} else if(depth === 0) {
|
||||||
|
return (
|
||||||
|
<NavItemLink to={routeName}>{headerTitle}</NavItemLink>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default NavRoutesLinksLink;
|
@ -15,12 +15,16 @@ import AccordionListItemTableEditions from './ascribe_accordion_list/accordion_l
|
|||||||
|
|
||||||
import Pagination from './ascribe_pagination/pagination';
|
import Pagination from './ascribe_pagination/pagination';
|
||||||
|
|
||||||
|
import PieceListFilterDisplay from './piece_list_filter_display';
|
||||||
|
|
||||||
import PieceListBulkModal from './ascribe_piece_list_bulk_modal/piece_list_bulk_modal';
|
import PieceListBulkModal from './ascribe_piece_list_bulk_modal/piece_list_bulk_modal';
|
||||||
import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar';
|
import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar';
|
||||||
|
|
||||||
import AppConstants from '../constants/application_constants';
|
import AppConstants from '../constants/application_constants';
|
||||||
|
|
||||||
import { mergeOptions } from '../utils/general_utils';
|
import { mergeOptions } from '../utils/general_utils';
|
||||||
|
import { getLangText } from '../utils/lang_utils';
|
||||||
|
|
||||||
|
|
||||||
let PieceList = React.createClass({
|
let PieceList = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
@ -30,7 +34,6 @@ let PieceList = React.createClass({
|
|||||||
filterParams: React.PropTypes.array,
|
filterParams: React.PropTypes.array,
|
||||||
orderParams: React.PropTypes.array,
|
orderParams: React.PropTypes.array,
|
||||||
orderBy: React.PropTypes.string
|
orderBy: React.PropTypes.string
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [Router.Navigation, Router.State],
|
mixins: [Router.Navigation, Router.State],
|
||||||
@ -39,12 +42,13 @@ let PieceList = React.createClass({
|
|||||||
return {
|
return {
|
||||||
accordionListItemType: AccordionListItemWallet,
|
accordionListItemType: AccordionListItemWallet,
|
||||||
orderParams: ['artist_name', 'title'],
|
orderParams: ['artist_name', 'title'],
|
||||||
filterParams: [
|
filterParams: [{
|
||||||
|
label: getLangText('Show works I can'),
|
||||||
|
items: [
|
||||||
'acl_transfer',
|
'acl_transfer',
|
||||||
'acl_consign',
|
'acl_consign',
|
||||||
{
|
'acl_create_editions'
|
||||||
key: 'acl_create_editions',
|
]
|
||||||
label: 'create editions'
|
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -60,18 +64,18 @@ let PieceList = React.createClass({
|
|||||||
|
|
||||||
PieceListStore.listen(this.onChange);
|
PieceListStore.listen(this.onChange);
|
||||||
EditionListStore.listen(this.onChange);
|
EditionListStore.listen(this.onChange);
|
||||||
|
|
||||||
let orderBy = this.props.orderBy ? this.props.orderBy : this.state.orderBy;
|
let orderBy = this.props.orderBy ? this.props.orderBy : this.state.orderBy;
|
||||||
if (this.state.pieceList.length === 0 || this.state.page !== page){
|
if (this.state.pieceList.length === 0 || this.state.page !== page){
|
||||||
PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search,
|
PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search,
|
||||||
orderBy, this.state.orderAsc, this.state.filterBy)
|
orderBy, this.state.orderAsc, this.state.filterBy);
|
||||||
.then(() => PieceListActions.fetchPieceRequestActions());
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if (this.props.redirectTo && this.state.unfilteredPieceListCount === 0) {
|
if (this.props.redirectTo && this.state.unfilteredPieceListCount === 0) {
|
||||||
// FIXME: hack to redirect out of the dispatch cycle
|
// FIXME: hack to redirect out of the dispatch cycle
|
||||||
window.setTimeout(() => this.transitionTo(this.props.redirectTo), 0);
|
window.setTimeout(() => this.transitionTo(this.props.redirectTo, this.getQuery()));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -147,6 +151,7 @@ let PieceList = React.createClass({
|
|||||||
render() {
|
render() {
|
||||||
let loadingElement = (<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />);
|
let loadingElement = (<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />);
|
||||||
let AccordionListItemType = this.props.accordionListItemType;
|
let AccordionListItemType = this.props.accordionListItemType;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PieceListToolbar
|
<PieceListToolbar
|
||||||
@ -161,6 +166,9 @@ let PieceList = React.createClass({
|
|||||||
{this.props.customSubmitButton}
|
{this.props.customSubmitButton}
|
||||||
</PieceListToolbar>
|
</PieceListToolbar>
|
||||||
<PieceListBulkModal className="ascribe-piece-list-bulk-modal" />
|
<PieceListBulkModal className="ascribe-piece-list-bulk-modal" />
|
||||||
|
<PieceListFilterDisplay
|
||||||
|
filterBy={this.state.filterBy}
|
||||||
|
filterParams={this.props.filterParams}/>
|
||||||
<AccordionList
|
<AccordionList
|
||||||
className="ascribe-accordion-list"
|
className="ascribe-accordion-list"
|
||||||
changeOrder={this.accordionChangeOrder}
|
changeOrder={this.accordionChangeOrder}
|
||||||
|
118
js/components/piece_list_filter_display.js
Normal file
118
js/components/piece_list_filter_display.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
|
||||||
|
let PieceListFilterDisplay = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
filterBy: React.PropTypes.object,
|
||||||
|
filterParams: React.PropTypes.arrayOf(
|
||||||
|
React.PropTypes.shape({
|
||||||
|
label: React.PropTypes.string,
|
||||||
|
items: React.PropTypes.arrayOf(
|
||||||
|
React.PropTypes.oneOfType([
|
||||||
|
React.PropTypes.string,
|
||||||
|
React.PropTypes.shape({
|
||||||
|
key: React.PropTypes.string,
|
||||||
|
label: React.PropTypes.string
|
||||||
|
})
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the above described filterParams prop,
|
||||||
|
* assigns it it's true filterBy value that is derived from the filterBy prop
|
||||||
|
* and also - if there wasn't already one defined - generates a label
|
||||||
|
* @return {object}
|
||||||
|
*/
|
||||||
|
transformFilterParamsItemsToBools() {
|
||||||
|
let { filterParams, filterBy } = this.props;
|
||||||
|
|
||||||
|
return filterParams.map((filterParam) => {
|
||||||
|
return {
|
||||||
|
label: filterParam.label,
|
||||||
|
items: filterParam.items.map((item) => {
|
||||||
|
if(typeof item !== 'string' && typeof item.key === 'string' && typeof item.label === 'string') {
|
||||||
|
return {
|
||||||
|
key: item.key,
|
||||||
|
label: item.label,
|
||||||
|
value: filterBy[item.key] || false
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
key: item,
|
||||||
|
label: item.split('acl_')[1].replace(/_/g, ' '),
|
||||||
|
value: filterBy[item] || false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the list of filters generated in transformFilterParamsItemsToBools and
|
||||||
|
* transforms them into human readable text.
|
||||||
|
* @param {Object} filtersWithLabel An object of the shape {key: <String>, label: <String>, value: <Bool>}
|
||||||
|
* @return {string} A human readable string
|
||||||
|
*/
|
||||||
|
getFilterText(filtersWithLabel) {
|
||||||
|
let filterTextList = filtersWithLabel
|
||||||
|
// Iterate over all provided filterLabels and generate a list
|
||||||
|
// of human readable strings
|
||||||
|
.map((filterWithLabel) => {
|
||||||
|
let activeFilterWithLabel = filterWithLabel
|
||||||
|
.items
|
||||||
|
// If the filter is active (which it is when its value is true),
|
||||||
|
// we're going to include it's label into a list,
|
||||||
|
// otherwise we'll just return nothing
|
||||||
|
.map((filter) => {
|
||||||
|
if(filter.value) {
|
||||||
|
return filter.label;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// if nothing is returned, that index is 'undefined'.
|
||||||
|
// As we only want active filter, we filter out all falsy values e.g. undefined
|
||||||
|
.filter((filterName) => !!filterName)
|
||||||
|
// and join the result to a string
|
||||||
|
.join(', ');
|
||||||
|
|
||||||
|
// If this actually didn't generate an empty string,
|
||||||
|
// we take the label and concat it to the result.
|
||||||
|
if(activeFilterWithLabel) {
|
||||||
|
return filterWithLabel.label + ': ' + activeFilterWithLabel;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// filter out strings that are undefined, as their filter's were not activated
|
||||||
|
.filter((filterText) => !!filterText)
|
||||||
|
// if there are multiple sentences, capitalize the first one and lowercase the others
|
||||||
|
.map((filterText, i) => i === 0 ? filterText.charAt(0).toUpperCase() + filterText.substr(1) : filterText.charAt(0).toLowerCase() + filterText.substr(1))
|
||||||
|
.join(' and ');
|
||||||
|
|
||||||
|
return filterTextList;
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { filterBy } = this.props;
|
||||||
|
let filtersWithLabel = this.transformFilterParamsItemsToBools();
|
||||||
|
|
||||||
|
// do not show the FilterDisplay if there are no filters applied
|
||||||
|
if(filterBy && Object.keys(filterBy).length === 0) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div className="row">
|
||||||
|
<div className="ascribe-piece-list-filter-display col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2">
|
||||||
|
{this.getFilterText(filtersWithLabel)}
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default PieceListFilterDisplay;
|
@ -1,408 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import Router from 'react-router';
|
|
||||||
|
|
||||||
import UserActions from '../actions/user_actions';
|
|
||||||
import UserStore from '../stores/user_store';
|
|
||||||
|
|
||||||
import WalletSettingsActions from '../actions/wallet_settings_actions';
|
|
||||||
import WalletSettingsStore from '../stores/wallet_settings_store';
|
|
||||||
|
|
||||||
import ApplicationActions from '../actions/application_actions';
|
|
||||||
import ApplicationStore from '../stores/application_store';
|
|
||||||
|
|
||||||
import GlobalNotificationModel from '../models/global_notification_model';
|
|
||||||
import GlobalNotificationActions from '../actions/global_notification_actions';
|
|
||||||
|
|
||||||
import ReactS3FineUploader from './ascribe_uploader/react_s3_fine_uploader';
|
|
||||||
|
|
||||||
import CollapsibleParagraph from './ascribe_collapsible/collapsible_paragraph';
|
|
||||||
import Form from './ascribe_forms/form';
|
|
||||||
import Property from './ascribe_forms/property';
|
|
||||||
import InputCheckbox from './ascribe_forms/input_checkbox';
|
|
||||||
|
|
||||||
import ActionPanel from './ascribe_panel/action_panel';
|
|
||||||
|
|
||||||
import ApiUrls from '../constants/api_urls';
|
|
||||||
import AppConstants from '../constants/application_constants';
|
|
||||||
|
|
||||||
import { getLangText } from '../utils/lang_utils';
|
|
||||||
import { getCookie } from '../utils/fetch_api_utils';
|
|
||||||
|
|
||||||
let SettingsContainer = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
children: React.PropTypes.oneOfType([
|
|
||||||
React.PropTypes.arrayOf(React.PropTypes.element),
|
|
||||||
React.PropTypes.element])
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [Router.Navigation],
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="settings-container">
|
|
||||||
<AccountSettings />
|
|
||||||
{this.props.children}
|
|
||||||
<APISettings />
|
|
||||||
<BitcoinWalletSettings />
|
|
||||||
<LoanContractSettings />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
let AccountSettings = React.createClass({
|
|
||||||
getInitialState() {
|
|
||||||
return UserStore.getState();
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
UserStore.listen(this.onChange);
|
|
||||||
UserActions.fetchCurrentUser();
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
UserStore.unlisten(this.onChange);
|
|
||||||
},
|
|
||||||
|
|
||||||
onChange(state) {
|
|
||||||
this.setState(state);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSuccess(){
|
|
||||||
UserActions.fetchCurrentUser();
|
|
||||||
let notification = new GlobalNotificationModel(getLangText('Settings succesfully updated'), 'success', 5000);
|
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
|
||||||
},
|
|
||||||
|
|
||||||
getFormDataProfile(){
|
|
||||||
return {'email': this.state.currentUser.email};
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
|
||||||
let profile = null;
|
|
||||||
|
|
||||||
if (this.state.currentUser.username) {
|
|
||||||
content = (
|
|
||||||
<Form
|
|
||||||
url={ApiUrls.users_username}
|
|
||||||
handleSuccess={this.handleSuccess}>
|
|
||||||
<Property
|
|
||||||
name='username'
|
|
||||||
label={getLangText('Username')}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
defaultValue={this.state.currentUser.username}
|
|
||||||
placeholder={getLangText('Enter your username')}
|
|
||||||
required/>
|
|
||||||
</Property>
|
|
||||||
<Property
|
|
||||||
name='email'
|
|
||||||
label={getLangText('Email')}
|
|
||||||
editable={false}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
defaultValue={this.state.currentUser.email}
|
|
||||||
placeholder={getLangText('Enter your username')}
|
|
||||||
required/>
|
|
||||||
</Property>
|
|
||||||
<hr />
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
profile = (
|
|
||||||
<Form
|
|
||||||
url={ApiUrls.users_profile}
|
|
||||||
handleSuccess={this.handleSuccess}
|
|
||||||
getFormData={this.getFormDataProfile}>
|
|
||||||
<Property
|
|
||||||
name="hash_locally"
|
|
||||||
className="ascribe-settings-property-collapsible-toggle"
|
|
||||||
style={{paddingBottom: 0}}>
|
|
||||||
<InputCheckbox
|
|
||||||
defaultChecked={this.state.currentUser.profile.hash_locally}>
|
|
||||||
<span>
|
|
||||||
{' ' + getLangText('Enable hash option, e.g. slow connections or to keep piece private')}
|
|
||||||
</span>
|
|
||||||
</InputCheckbox>
|
|
||||||
</Property>
|
|
||||||
<hr />
|
|
||||||
{/*<Property
|
|
||||||
name='language'
|
|
||||||
label={getLangText('Choose your Language')}
|
|
||||||
editable={true}>
|
|
||||||
<select id="select-lang" name="language">
|
|
||||||
<option value="fr">
|
|
||||||
Français
|
|
||||||
</option>
|
|
||||||
<option value="en"
|
|
||||||
selected="selected">
|
|
||||||
English
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</Property>*/}
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<CollapsibleParagraph
|
|
||||||
title={getLangText('Account')}
|
|
||||||
show={true}
|
|
||||||
defaultExpanded={true}>
|
|
||||||
{content}
|
|
||||||
{profile}
|
|
||||||
{/*<Form
|
|
||||||
url={AppConstants.serverUrl + 'api/users/set_language/'}>
|
|
||||||
<Property
|
|
||||||
name='language'
|
|
||||||
label={getLangText('Choose your Language')}
|
|
||||||
editable={true}>
|
|
||||||
<select id="select-lang" name="language">
|
|
||||||
<option value="fr">
|
|
||||||
Français
|
|
||||||
</option>
|
|
||||||
<option value="en"
|
|
||||||
selected="selected">
|
|
||||||
English
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</Property>
|
|
||||||
<hr />
|
|
||||||
</Form>*/}
|
|
||||||
</CollapsibleParagraph>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let BitcoinWalletSettings = React.createClass({
|
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
defaultExpanded: React.PropTypes.bool
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState() {
|
|
||||||
return WalletSettingsStore.getState();
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
WalletSettingsStore.listen(this.onChange);
|
|
||||||
WalletSettingsActions.fetchWalletSettings();
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
WalletSettingsStore.unlisten(this.onChange);
|
|
||||||
},
|
|
||||||
|
|
||||||
onChange(state) {
|
|
||||||
this.setState(state);
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
|
||||||
if (this.state.walletSettings.btc_public_key) {
|
|
||||||
content = (
|
|
||||||
<Form >
|
|
||||||
<Property
|
|
||||||
name='btc_public_key'
|
|
||||||
label={getLangText('Bitcoin public key')}
|
|
||||||
editable={false}>
|
|
||||||
<pre className="ascribe-pre">{this.state.walletSettings.btc_public_key}</pre>
|
|
||||||
</Property>
|
|
||||||
<Property
|
|
||||||
name='btc_root_address'
|
|
||||||
label={getLangText('Root Address')}
|
|
||||||
editable={false}>
|
|
||||||
<pre className="ascribe-pre">{this.state.walletSettings.btc_root_address}</pre>
|
|
||||||
</Property>
|
|
||||||
<hr />
|
|
||||||
</Form>);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<CollapsibleParagraph
|
|
||||||
title={getLangText('Crypto Wallet')}
|
|
||||||
show={true}
|
|
||||||
defaultExpanded={this.props.defaultExpanded}>
|
|
||||||
{content}
|
|
||||||
</CollapsibleParagraph>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let LoanContractSettings = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
defaultExpanded: React.PropTypes.bool
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<CollapsibleParagraph
|
|
||||||
title="Loan Contract Settings"
|
|
||||||
show={true}
|
|
||||||
defaultExpanded={this.props.defaultExpanded}>
|
|
||||||
<FileUploader />
|
|
||||||
</CollapsibleParagraph>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let FileUploader = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Form>
|
|
||||||
<Property
|
|
||||||
label="Contract file">
|
|
||||||
<ReactS3FineUploader
|
|
||||||
keyRoutine={{
|
|
||||||
url: AppConstants.serverUrl + 's3/key/',
|
|
||||||
fileClass: 'contract'
|
|
||||||
}}
|
|
||||||
createBlobRoutine={{
|
|
||||||
url: ApiUrls.ownership_loans_contract
|
|
||||||
}}
|
|
||||||
validation={{
|
|
||||||
itemLimit: 100000,
|
|
||||||
sizeLimit: '50000000'
|
|
||||||
}}
|
|
||||||
session={{
|
|
||||||
endpoint: ApiUrls.ownership_loans_contract,
|
|
||||||
customHeaders: {
|
|
||||||
'X-CSRFToken': getCookie(AppConstants.csrftoken)
|
|
||||||
},
|
|
||||||
cors: {
|
|
||||||
expected: true,
|
|
||||||
sendCredentials: true
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
areAssetsDownloadable={true}
|
|
||||||
areAssetsEditable={true}/>
|
|
||||||
</Property>
|
|
||||||
<hr />
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let APISettings = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
defaultExpanded: React.PropTypes.bool
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState() {
|
|
||||||
return ApplicationStore.getState();
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
ApplicationStore.listen(this.onChange);
|
|
||||||
ApplicationActions.fetchApplication();
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
ApplicationStore.unlisten(this.onChange);
|
|
||||||
},
|
|
||||||
|
|
||||||
onChange(state) {
|
|
||||||
this.setState(state);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleCreateSuccess() {
|
|
||||||
ApplicationActions.fetchApplication();
|
|
||||||
let notification = new GlobalNotificationModel(getLangText('Application successfully created'), 'success', 5000);
|
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTokenRefresh(event) {
|
|
||||||
let applicationName = event.target.getAttribute('data-id');
|
|
||||||
ApplicationActions.refreshApplicationToken(applicationName);
|
|
||||||
|
|
||||||
let notification = new GlobalNotificationModel(getLangText('Token refreshed'), 'success', 2000);
|
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
|
||||||
},
|
|
||||||
|
|
||||||
getApplications(){
|
|
||||||
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
|
||||||
if (this.state.applications.length > -1) {
|
|
||||||
content = this.state.applications.map(function(app, i) {
|
|
||||||
return (
|
|
||||||
<ActionPanel
|
|
||||||
name={app.name}
|
|
||||||
key={i}
|
|
||||||
content={
|
|
||||||
<div>
|
|
||||||
<div className='ascribe-panel-title'>
|
|
||||||
{app.name}
|
|
||||||
</div>
|
|
||||||
<div className="ascribe-panel-subtitle">
|
|
||||||
{'Bearer ' + app.bearer_token.token}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
buttons={
|
|
||||||
<div className="pull-right">
|
|
||||||
<div className="pull-right">
|
|
||||||
<button
|
|
||||||
className="pull-right btn btn-default btn-sm"
|
|
||||||
onClick={this.handleTokenRefresh}
|
|
||||||
data-id={app.name}>
|
|
||||||
{getLangText('REFRESH')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}/>
|
|
||||||
);
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
return content;
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<CollapsibleParagraph
|
|
||||||
title={getLangText('API Integration')}
|
|
||||||
show={true}
|
|
||||||
defaultExpanded={this.props.defaultExpanded}>
|
|
||||||
<Form
|
|
||||||
url={ApiUrls.applications}
|
|
||||||
handleSuccess={this.handleCreateSuccess}>
|
|
||||||
<Property
|
|
||||||
name='name'
|
|
||||||
label={getLangText('Application Name')}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder={getLangText('Enter the name of your app')}
|
|
||||||
required/>
|
|
||||||
</Property>
|
|
||||||
<hr />
|
|
||||||
</Form>
|
|
||||||
<pre>
|
|
||||||
Usage: curl <url> -H 'Authorization: Bearer <token>'
|
|
||||||
</pre>
|
|
||||||
{this.getApplications()}
|
|
||||||
</CollapsibleParagraph>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default SettingsContainer;
|
|
@ -1,8 +1,13 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Router from 'react-router';
|
||||||
|
|
||||||
import SignupForm from './ascribe_forms/form_signup';
|
import SignupForm from './ascribe_forms/form_signup';
|
||||||
|
|
||||||
|
import { getLangText } from '../utils/lang_utils';
|
||||||
|
|
||||||
|
let Link = Router.Link;
|
||||||
|
|
||||||
let SignupContainer = React.createClass({
|
let SignupContainer = React.createClass({
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
@ -33,7 +38,11 @@ let SignupContainer = React.createClass({
|
|||||||
return (
|
return (
|
||||||
<div className="ascribe-login-wrapper">
|
<div className="ascribe-login-wrapper">
|
||||||
<SignupForm handleSuccess={this.handleSuccess} />
|
<SignupForm handleSuccess={this.handleSuccess} />
|
||||||
|
<div className="ascribe-login-text">
|
||||||
|
{getLangText('Already an ascribe user')}? <Link to="login">{getLangText('Log in')}...</Link><br/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -124,7 +124,7 @@ let AccordionListItemPrize = React.createClass({
|
|||||||
<div>
|
<div>
|
||||||
<AclProxy
|
<AclProxy
|
||||||
aclObject={this.props.content.acl}
|
aclObject={this.props.content.acl}
|
||||||
aclName="acl_submit">
|
aclName="acl_wallet_submit">
|
||||||
<SubmitToPrizeButton
|
<SubmitToPrizeButton
|
||||||
className="pull-right"
|
className="pull-right"
|
||||||
piece={this.props.content}
|
piece={this.props.content}
|
||||||
|
@ -86,7 +86,19 @@ let PieceContainer = React.createClass({
|
|||||||
|
|
||||||
loadPiece() {
|
loadPiece() {
|
||||||
PieceActions.fetchOne(this.props.params.pieceId);
|
PieceActions.fetchOne(this.props.params.pieceId);
|
||||||
this.setState(this.state);
|
},
|
||||||
|
|
||||||
|
getActions() {
|
||||||
|
if (this.state.piece &&
|
||||||
|
this.state.piece.notifications &&
|
||||||
|
this.state.piece.notifications.length > 0) {
|
||||||
|
return (
|
||||||
|
<ListRequestActions
|
||||||
|
pieceOrEditions={this.state.piece}
|
||||||
|
currentUser={this.state.currentUser}
|
||||||
|
handleSuccess={this.loadPiece}
|
||||||
|
notifications={this.state.piece.notifications}/>);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -121,11 +133,7 @@ let PieceContainer = React.createClass({
|
|||||||
<DetailProperty label={getLangText('BY')} value={artistName} />
|
<DetailProperty label={getLangText('BY')} value={artistName} />
|
||||||
<DetailProperty label={getLangText('DATE')} value={ this.state.piece.date_created.slice(0, 4) } />
|
<DetailProperty label={getLangText('DATE')} value={ this.state.piece.date_created.slice(0, 4) } />
|
||||||
{artistEmail}
|
{artistEmail}
|
||||||
<ListRequestActions
|
{this.getActions()}
|
||||||
pieceOrEditions={this.state.piece}
|
|
||||||
currentUser={this.state.currentUser}
|
|
||||||
handleSuccess={this.loadPiece}
|
|
||||||
requestActions={this.state.piece.request_action}/>
|
|
||||||
<hr/>
|
<hr/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -301,7 +309,6 @@ let PrizePieceRatings = React.createClass({
|
|||||||
<div>
|
<div>
|
||||||
<CollapsibleParagraph
|
<CollapsibleParagraph
|
||||||
title={getLangText('Shortlisting')}
|
title={getLangText('Shortlisting')}
|
||||||
show={true}
|
|
||||||
defaultExpanded={true}>
|
defaultExpanded={true}>
|
||||||
<div className="row no-margin">
|
<div className="row no-margin">
|
||||||
<span className="ascribe-checkbox-wrapper" style={{marginLeft: '1.5em'}}>
|
<span className="ascribe-checkbox-wrapper" style={{marginLeft: '1.5em'}}>
|
||||||
@ -321,7 +328,6 @@ let PrizePieceRatings = React.createClass({
|
|||||||
</CollapsibleParagraph>
|
</CollapsibleParagraph>
|
||||||
<CollapsibleParagraph
|
<CollapsibleParagraph
|
||||||
title={getLangText('Average Rating')}
|
title={getLangText('Average Rating')}
|
||||||
show={true}
|
|
||||||
defaultExpanded={true}>
|
defaultExpanded={true}>
|
||||||
<div id="list-rating" style={{marginLeft: '1.5em', marginBottom: '1em'}}>
|
<div id="list-rating" style={{marginLeft: '1.5em', marginBottom: '1em'}}>
|
||||||
<StarRating
|
<StarRating
|
||||||
@ -367,7 +373,6 @@ let PrizePieceRatings = React.createClass({
|
|||||||
return (
|
return (
|
||||||
<CollapsibleParagraph
|
<CollapsibleParagraph
|
||||||
title={getLangText('Rating')}
|
title={getLangText('Rating')}
|
||||||
show={true}
|
|
||||||
defaultExpanded={true}>
|
defaultExpanded={true}>
|
||||||
<div style={{marginLeft: '1.5em', marginBottom: '1em'}}>
|
<div style={{marginLeft: '1.5em', marginBottom: '1em'}}>
|
||||||
<StarRating
|
<StarRating
|
||||||
@ -409,7 +414,6 @@ let PrizePieceDetails = React.createClass({
|
|||||||
return (
|
return (
|
||||||
<CollapsibleParagraph
|
<CollapsibleParagraph
|
||||||
title={getLangText('Prize Details')}
|
title={getLangText('Prize Details')}
|
||||||
show={true}
|
|
||||||
defaultExpanded={true}>
|
defaultExpanded={true}>
|
||||||
<Form ref='form'>
|
<Form ref='form'>
|
||||||
{Object.keys(this.props.piece.extra_data).map((data) => {
|
{Object.keys(this.props.piece.extra_data).map((data) => {
|
||||||
@ -418,10 +422,10 @@ let PrizePieceDetails = React.createClass({
|
|||||||
<Property
|
<Property
|
||||||
name={data}
|
name={data}
|
||||||
label={label}
|
label={label}
|
||||||
editable={false}>
|
editable={false}
|
||||||
|
overrideForm={true}>
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={false}
|
|
||||||
defaultValue={this.props.piece.extra_data[data]}/>
|
defaultValue={this.props.piece.extra_data[data]}/>
|
||||||
</Property>);
|
</Property>);
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ let PrizePieceList = React.createClass({
|
|||||||
accordionListItemType={AccordionListItemPrize}
|
accordionListItemType={AccordionListItemPrize}
|
||||||
orderParams={orderParams}
|
orderParams={orderParams}
|
||||||
orderBy={this.state.currentUser.is_jury ? 'rating' : null}
|
orderBy={this.state.currentUser.is_jury ? 'rating' : null}
|
||||||
filterParams={null}
|
filterParams={[]}
|
||||||
customSubmitButton={this.getButtonSubmit()}/>
|
customSubmitButton={this.getButtonSubmit()}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -41,20 +41,20 @@ let PrizeRegisterPiece = React.createClass({
|
|||||||
<Property
|
<Property
|
||||||
name='artist_statement'
|
name='artist_statement'
|
||||||
label={getLangText('Artist statement')}
|
label={getLangText('Artist statement')}
|
||||||
editable={true}>
|
editable={true}
|
||||||
|
overrideForm={true}>
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={true}
|
|
||||||
placeholder={getLangText('Enter your statement')}
|
placeholder={getLangText('Enter your statement')}
|
||||||
required="required"/>
|
required="required"/>
|
||||||
</Property>
|
</Property>
|
||||||
<Property
|
<Property
|
||||||
name='work_description'
|
name='work_description'
|
||||||
label={getLangText('Work description')}
|
label={getLangText('Work description')}
|
||||||
editable={true}>
|
editable={true}
|
||||||
|
overrideForm={true}>
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={true}
|
|
||||||
placeholder={getLangText('Enter the description for your work')}
|
placeholder={getLangText('Enter the description for your work')}
|
||||||
required="required"/>
|
required="required"/>
|
||||||
</Property>
|
</Property>
|
||||||
|
@ -9,7 +9,7 @@ import PrizeStore from '../stores/prize_store';
|
|||||||
import PrizeJuryActions from '../actions/prize_jury_actions';
|
import PrizeJuryActions from '../actions/prize_jury_actions';
|
||||||
import PrizeJuryStore from '../stores/prize_jury_store';
|
import PrizeJuryStore from '../stores/prize_jury_store';
|
||||||
|
|
||||||
import SettingsContainer from '../../../settings_container';
|
import SettingsContainer from '../../../ascribe_settings/settings_container';
|
||||||
import CollapsibleParagraph from '../../../ascribe_collapsible/collapsible_paragraph';
|
import CollapsibleParagraph from '../../../ascribe_collapsible/collapsible_paragraph';
|
||||||
|
|
||||||
import Form from '../../../ascribe_forms/form';
|
import Form from '../../../ascribe_forms/form';
|
||||||
@ -79,7 +79,6 @@ let PrizeSettings = React.createClass({
|
|||||||
return (
|
return (
|
||||||
<CollapsibleParagraph
|
<CollapsibleParagraph
|
||||||
title={'Prize Settings for ' + this.state.prize.name}
|
title={'Prize Settings for ' + this.state.prize.name}
|
||||||
show={true}
|
|
||||||
defaultExpanded={true}>
|
defaultExpanded={true}>
|
||||||
<Form >
|
<Form >
|
||||||
<Property
|
<Property
|
||||||
@ -190,7 +189,7 @@ let PrizeJurySettings = React.createClass({
|
|||||||
{getLangText('RESEND')}
|
{getLangText('RESEND')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn btn-default btn-sm ascribe-btn-gray margin-left-2px"
|
className="btn btn-warning btn-sm margin-left-2px"
|
||||||
onClick={this.handleRevoke}
|
onClick={this.handleRevoke}
|
||||||
data-id={member.email}>
|
data-id={member.email}>
|
||||||
{getLangText('REVOKE')}
|
{getLangText('REVOKE')}
|
||||||
@ -218,7 +217,7 @@ let PrizeJurySettings = React.createClass({
|
|||||||
}
|
}
|
||||||
buttons={
|
buttons={
|
||||||
<button
|
<button
|
||||||
className="btn btn-default btn-sm ascribe-btn-gray"
|
className="btn btn-warning btn-sm"
|
||||||
onClick={this.handleRevoke}
|
onClick={this.handleRevoke}
|
||||||
data-id={member.email}>
|
data-id={member.email}>
|
||||||
{getLangText('REVOKE')}
|
{getLangText('REVOKE')}
|
||||||
@ -265,22 +264,19 @@ let PrizeJurySettings = React.createClass({
|
|||||||
|
|
||||||
if (this.state.members.length > -1) {
|
if (this.state.members.length > -1) {
|
||||||
content = (
|
content = (
|
||||||
<div style={{padding: '1em'}}>
|
<div>
|
||||||
<CollapsibleParagraph
|
<CollapsibleParagraph
|
||||||
title={getLangText('Active Jury Members')}
|
title={getLangText('Active Jury Members')}
|
||||||
show={true}
|
|
||||||
defaultExpanded={true}>
|
defaultExpanded={true}>
|
||||||
{this.getMembersActive()}
|
{this.getMembersActive()}
|
||||||
</CollapsibleParagraph>
|
</CollapsibleParagraph>
|
||||||
<CollapsibleParagraph
|
<CollapsibleParagraph
|
||||||
title={getLangText('Pending Jury Invitations')}
|
title={getLangText('Pending Jury Invitations')}
|
||||||
show={true}
|
|
||||||
defaultExpanded={true}>
|
defaultExpanded={true}>
|
||||||
{this.getMembersPending()}
|
{this.getMembersPending()}
|
||||||
</CollapsibleParagraph>
|
</CollapsibleParagraph>
|
||||||
<CollapsibleParagraph
|
<CollapsibleParagraph
|
||||||
title={getLangText('Deactivated Jury Members')}
|
title={getLangText('Deactivated Jury Members')}
|
||||||
show={true}
|
|
||||||
defaultExpanded={false}>
|
defaultExpanded={false}>
|
||||||
{this.getMembersInactive()}
|
{this.getMembersInactive()}
|
||||||
</CollapsibleParagraph>
|
</CollapsibleParagraph>
|
||||||
|
@ -27,7 +27,7 @@ let PrizeApp = React.createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container ascribe-prize-app">
|
<div className={'container ascribe-prize-app client--' + subdomain}>
|
||||||
{header}
|
{header}
|
||||||
<RouteHandler />
|
<RouteHandler />
|
||||||
<GlobalNotification />
|
<GlobalNotification />
|
||||||
|
@ -13,6 +13,7 @@ import PrizePieceList from './components/prize_piece_list';
|
|||||||
import PrizePieceContainer from './components/ascribe_detail/prize_piece_container';
|
import PrizePieceContainer from './components/ascribe_detail/prize_piece_container';
|
||||||
import EditionContainer from '../../ascribe_detail/edition_container';
|
import EditionContainer from '../../ascribe_detail/edition_container';
|
||||||
import SettingsContainer from './components/prize_settings_container';
|
import SettingsContainer from './components/prize_settings_container';
|
||||||
|
import CoaVerifyContainer from '../../../components/coa_verify_container';
|
||||||
|
|
||||||
import App from './prize_app';
|
import App from './prize_app';
|
||||||
import AppConstants from '../../../constants/application_constants';
|
import AppConstants from '../../../constants/application_constants';
|
||||||
@ -34,6 +35,7 @@ function getRoutes() {
|
|||||||
<Route name="piece" path="pieces/:pieceId" handler={PrizePieceContainer} />
|
<Route name="piece" path="pieces/:pieceId" handler={PrizePieceContainer} />
|
||||||
<Route name="edition" path="editions/:editionId" handler={EditionContainer} />
|
<Route name="edition" path="editions/:editionId" handler={EditionContainer} />
|
||||||
<Route name="settings" path="settings" handler={SettingsContainer} />
|
<Route name="settings" path="settings" handler={SettingsContainer} />
|
||||||
|
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} />
|
||||||
</Route>
|
</Route>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
|
||||||
|
import ListRequestActions from '../../../../ascribe_forms/list_form_request_actions';
|
||||||
|
import AclButtonList from '../../../../ascribe_buttons/acl_button_list';
|
||||||
|
import DeleteButton from '../../../../ascribe_buttons/delete_button';
|
||||||
|
|
||||||
|
import AclProxy from '../../../../acl_proxy';
|
||||||
|
|
||||||
|
|
||||||
|
import { mergeOptions } from '../../../../../utils/general_utils';
|
||||||
|
|
||||||
|
|
||||||
|
let WalletActionPanel = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
piece: React.PropTypes.object.isRequired,
|
||||||
|
currentUser: React.PropTypes.object.isRequired,
|
||||||
|
loadPiece: React.PropTypes.func.isRequired,
|
||||||
|
submitButtonType: React.PropTypes.func.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
render(){
|
||||||
|
if (this.props.piece &&
|
||||||
|
this.props.piece.notifications &&
|
||||||
|
this.props.piece.notifications.length > 0) {
|
||||||
|
return (
|
||||||
|
<ListRequestActions
|
||||||
|
pieceOrEditions={this.props.piece}
|
||||||
|
currentUser={this.props.currentUser}
|
||||||
|
handleSuccess={this.props.loadPiece}
|
||||||
|
notifications={this.props.piece.notifications}/>);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
//We need to disable the normal acl_loan because we're inserting a custom acl_loan button
|
||||||
|
let availableAcls;
|
||||||
|
|
||||||
|
if (this.props.piece && this.props.piece.acl && typeof this.props.piece.acl.acl_loan !== 'undefined') {
|
||||||
|
// make a copy to not have side effects
|
||||||
|
availableAcls = mergeOptions({}, this.props.piece.acl);
|
||||||
|
availableAcls.acl_loan = false;
|
||||||
|
}
|
||||||
|
let SubmitButtonType = this.props.submitButtonType;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AclButtonList
|
||||||
|
className="text-center ascribe-button-list"
|
||||||
|
availableAcls={availableAcls}
|
||||||
|
editions={this.props.piece}
|
||||||
|
handleSuccess={this.loadPiece}>
|
||||||
|
<AclProxy
|
||||||
|
aclObject={this.props.currentUser.acl}
|
||||||
|
aclName="acl_wallet_submit">
|
||||||
|
<AclProxy
|
||||||
|
aclObject={availableAcls}
|
||||||
|
aclName="acl_wallet_submit">
|
||||||
|
<SubmitButtonType
|
||||||
|
className="btn-sm"
|
||||||
|
handleSuccess={this.handleSubmitSuccess}
|
||||||
|
piece={this.props.piece}/>
|
||||||
|
</AclProxy>
|
||||||
|
</AclProxy>
|
||||||
|
<DeleteButton
|
||||||
|
handleSuccess={this.handleDeleteSuccess}
|
||||||
|
piece={this.props.piece}/>
|
||||||
|
</AclButtonList>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default WalletActionPanel;
|
@ -0,0 +1,92 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Piece from '../../../../../components/ascribe_detail/piece';
|
||||||
|
|
||||||
|
import WalletActionPanel from './wallet_action_panel';
|
||||||
|
import CollapsibleParagraph from '../../../../../components/ascribe_collapsible/collapsible_paragraph';
|
||||||
|
|
||||||
|
import HistoryIterator from '../../../../ascribe_detail/history_iterator';
|
||||||
|
import Note from '../../../../ascribe_detail/note';
|
||||||
|
|
||||||
|
import DetailProperty from '../../../../ascribe_detail/detail_property';
|
||||||
|
|
||||||
|
import ApiUrls from '../../../../../constants/api_urls';
|
||||||
|
import AppConstants from '../../../../../constants/application_constants';
|
||||||
|
|
||||||
|
import { getLangText } from '../../../../../utils/lang_utils';
|
||||||
|
|
||||||
|
|
||||||
|
let WalletPieceContainer = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
piece: React.PropTypes.object.isRequired,
|
||||||
|
currentUser: React.PropTypes.object.isRequired,
|
||||||
|
loadPiece: React.PropTypes.func.isRequired,
|
||||||
|
submitButtonType: React.PropTypes.func.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if(this.props.piece && this.props.piece.title) {
|
||||||
|
return (
|
||||||
|
<Piece
|
||||||
|
piece={this.props.piece}
|
||||||
|
loadPiece={this.props.loadPiece}
|
||||||
|
header={
|
||||||
|
<div className="ascribe-detail-header">
|
||||||
|
<hr style={{marginTop: 0}}/>
|
||||||
|
<h1 className="ascribe-detail-title">{this.props.piece.title}</h1>
|
||||||
|
<DetailProperty label="BY" value={this.props.piece.artist_name} />
|
||||||
|
<DetailProperty label="DATE" value={ this.props.piece.date_created.slice(0, 4) } />
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
subheader={
|
||||||
|
<div className="ascribe-detail-header">
|
||||||
|
<DetailProperty label={getLangText('REGISTREE')} value={ this.props.piece.user_registered } />
|
||||||
|
<DetailProperty label={getLangText('ID')} value={ this.props.piece.bitcoin_id } ellipsis={true} />
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
|
}>
|
||||||
|
<WalletActionPanel
|
||||||
|
piece={this.props.piece}
|
||||||
|
currentUser={this.props.currentUser}
|
||||||
|
loadPiece={this.props.loadPiece}
|
||||||
|
submitButtonType={this.props.submitButtonType}/>
|
||||||
|
<CollapsibleParagraph
|
||||||
|
title={getLangText('Loan History')}
|
||||||
|
show={this.props.piece.loan_history && this.props.piece.loan_history.length > 0}>
|
||||||
|
<HistoryIterator
|
||||||
|
history={this.props.piece.loan_history}/>
|
||||||
|
</CollapsibleParagraph>
|
||||||
|
<CollapsibleParagraph
|
||||||
|
title={getLangText('Notes')}
|
||||||
|
show={!!(this.props.currentUser.username || this.props.piece.public_note)}>
|
||||||
|
<Note
|
||||||
|
id={() => {return {'id': this.props.piece.id}; }}
|
||||||
|
label={getLangText('Personal note (private)')}
|
||||||
|
defaultValue={this.props.piece.private_note || null}
|
||||||
|
placeholder={getLangText('Enter your comments ...')}
|
||||||
|
editable={true}
|
||||||
|
successMessage={getLangText('Private note saved')}
|
||||||
|
url={ApiUrls.note_private_piece}
|
||||||
|
currentUser={this.props.currentUser}/>
|
||||||
|
</CollapsibleParagraph>
|
||||||
|
|
||||||
|
{this.props.children}
|
||||||
|
</Piece>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (
|
||||||
|
<div className="fullpage-spinner">
|
||||||
|
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default WalletPieceContainer;
|
@ -46,12 +46,19 @@ let CCRegisterPiece = React.createClass({
|
|||||||
label={getLangText('Copyright license%s', '...')}
|
label={getLangText('Copyright license%s', '...')}
|
||||||
onChange={this.onLicenseChange}
|
onChange={this.onLicenseChange}
|
||||||
footer={
|
footer={
|
||||||
|
<span className="pull-right">
|
||||||
<a
|
<a
|
||||||
className="pull-right"
|
|
||||||
href={this.state.licenses[this.state.selectedLicense].url}
|
href={this.state.licenses[this.state.selectedLicense].url}
|
||||||
target="_blank">
|
target="_blank">
|
||||||
{getLangText('Learn more')}
|
{getLangText('Learn more about ') + this.state.licenses[this.state.selectedLicense].code}
|
||||||
</a>
|
</a>
|
||||||
|
(
|
||||||
|
<a
|
||||||
|
href='https://www.ascribe.io/faq/#legals'
|
||||||
|
target="_blank">
|
||||||
|
{getLangText('ascribe faq')}
|
||||||
|
</a>)
|
||||||
|
</span>
|
||||||
}>
|
}>
|
||||||
<select name="license">
|
<select name="license">
|
||||||
{this.state.licenses.map((license, i) => {
|
{this.state.licenses.map((license, i) => {
|
||||||
@ -74,7 +81,7 @@ let CCRegisterPiece = React.createClass({
|
|||||||
return (
|
return (
|
||||||
<RegisterPiece
|
<RegisterPiece
|
||||||
enableLocalHashing={false}
|
enableLocalHashing={false}
|
||||||
headerMessage={getLangText('Submit to Creative Commons')}
|
headerMessage={getLangText('Register under a Creative Commons license')}
|
||||||
submitMessage={getLangText('Submit')}>
|
submitMessage={getLangText('Submit')}>
|
||||||
{this.getLicenses()}
|
{this.getLicenses()}
|
||||||
</RegisterPiece>
|
</RegisterPiece>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Router from 'react-router';
|
|
||||||
|
|
||||||
import AccordionListItemPiece from '../../../../../ascribe_accordion_list/accordion_list_item_piece';
|
import AccordionListItemPiece from '../../../../../ascribe_accordion_list/accordion_list_item_piece';
|
||||||
|
|
||||||
@ -64,7 +63,7 @@ let CylandAccordionListItem = React.createClass({
|
|||||||
<div>
|
<div>
|
||||||
<AclProxy
|
<AclProxy
|
||||||
aclObject={this.props.content.acl}
|
aclObject={this.props.content.acl}
|
||||||
aclName="acl_submit">
|
aclName="acl_wallet_submit">
|
||||||
<CylandSubmitButton
|
<CylandSubmitButton
|
||||||
className="pull-right"
|
className="pull-right"
|
||||||
piece={this.props.content}
|
piece={this.props.content}
|
||||||
@ -72,7 +71,7 @@ let CylandAccordionListItem = React.createClass({
|
|||||||
</AclProxy>
|
</AclProxy>
|
||||||
<AclProxy
|
<AclProxy
|
||||||
aclObject={this.props.content.acl}
|
aclObject={this.props.content.acl}
|
||||||
aclName="acl_submitted">
|
aclName="acl_wallet_submitted">
|
||||||
<button
|
<button
|
||||||
disabled
|
disabled
|
||||||
className="btn btn-default btn-xs pull-right">
|
className="btn btn-default btn-xs pull-right">
|
||||||
@ -80,6 +79,16 @@ let CylandAccordionListItem = React.createClass({
|
|||||||
aria-hidden="true"></span>
|
aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
</AclProxy>
|
</AclProxy>
|
||||||
|
<AclProxy
|
||||||
|
aclObject={this.props.content.acl}
|
||||||
|
aclName="acl_wallet_accepted">
|
||||||
|
<button
|
||||||
|
disabled
|
||||||
|
className="btn btn-default btn-xs pull-right">
|
||||||
|
{getLangText('Loaned to Cyland')} <span className="glyphicon glyphicon-ok"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</AclProxy>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -7,27 +7,19 @@ import PieceStore from '../../../../../../stores/piece_store';
|
|||||||
|
|
||||||
import UserStore from '../../../../../../stores/user_store';
|
import UserStore from '../../../../../../stores/user_store';
|
||||||
|
|
||||||
import Piece from '../../../../../../components/ascribe_detail/piece';
|
import CylandSubmitButton from '../ascribe_buttons/cyland_submit_button';
|
||||||
|
|
||||||
|
import CollapsibleParagraph from '../../../../../../components/ascribe_collapsible/collapsible_paragraph';
|
||||||
|
|
||||||
|
import CylandAdditionalDataForm from '../ascribe_forms/cyland_additional_data_form';
|
||||||
|
|
||||||
|
import WalletPieceContainer from '../../ascribe_detail/wallet_piece_container';
|
||||||
|
|
||||||
import AppConstants from '../../../../../../constants/application_constants';
|
import AppConstants from '../../../../../../constants/application_constants';
|
||||||
|
|
||||||
import Form from '../../../../../../components/ascribe_forms/form';
|
|
||||||
import Property from '../../../../../../components/ascribe_forms/property';
|
|
||||||
import InputTextAreaToggable from '../../../../../../components/ascribe_forms/input_textarea_toggable';
|
|
||||||
import CollapsibleParagraph from '../../../../../../components/ascribe_collapsible/collapsible_paragraph';
|
|
||||||
|
|
||||||
import HistoryIterator from '../../../../../ascribe_detail/history_iterator';
|
|
||||||
import Note from '../../../../../ascribe_detail/note';
|
|
||||||
|
|
||||||
import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
|
|
||||||
import DetailProperty from '../../../../../ascribe_detail/detail_property';
|
|
||||||
|
|
||||||
import ApiUrls from '../../../../../../constants/api_urls';
|
|
||||||
|
|
||||||
import { getLangText } from '../../../../../../utils/lang_utils';
|
import { getLangText } from '../../../../../../utils/lang_utils';
|
||||||
import { mergeOptions } from '../../../../../../utils/general_utils';
|
import { mergeOptions } from '../../../../../../utils/general_utils';
|
||||||
|
|
||||||
|
|
||||||
let CylandPieceContainer = React.createClass({
|
let CylandPieceContainer = React.createClass({
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return mergeOptions(
|
return mergeOptions(
|
||||||
@ -38,23 +30,18 @@ let CylandPieceContainer = React.createClass({
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
PieceStore.listen(this.onChange);
|
PieceStore.listen(this.onChange);
|
||||||
PieceActions.fetchOne(this.props.params.pieceId);
|
|
||||||
UserStore.listen(this.onChange);
|
UserStore.listen(this.onChange);
|
||||||
},
|
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
if(this.props.params.pieceId !== nextProps.params.pieceId) {
|
|
||||||
PieceActions.updatePiece({});
|
|
||||||
PieceActions.fetchOne(nextProps.params.pieceId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
// Every time we're leaving the piece detail page,
|
// Every time we're leaving the piece detail page,
|
||||||
// just reset the piece that is saved in the piece store
|
// just reset the piece that is saved in the piece store
|
||||||
// as it will otherwise display wrong/old data once the user loads
|
// as it will otherwise display wrong/old data once the user loads
|
||||||
// the piece detail a second time
|
// the piece detail a second time
|
||||||
PieceActions.updatePiece({});
|
PieceActions.updatePiece({});
|
||||||
|
|
||||||
|
this.loadPiece();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
PieceStore.unlisten(this.onChange);
|
PieceStore.unlisten(this.onChange);
|
||||||
UserStore.unlisten(this.onChange);
|
UserStore.unlisten(this.onChange);
|
||||||
},
|
},
|
||||||
@ -70,50 +57,23 @@ let CylandPieceContainer = React.createClass({
|
|||||||
render() {
|
render() {
|
||||||
if(this.state.piece && this.state.piece.title) {
|
if(this.state.piece && this.state.piece.title) {
|
||||||
return (
|
return (
|
||||||
<Piece
|
<WalletPieceContainer
|
||||||
piece={this.state.piece}
|
piece={this.state.piece}
|
||||||
|
currentUser={this.state.currentUser}
|
||||||
loadPiece={this.loadPiece}
|
loadPiece={this.loadPiece}
|
||||||
header={
|
submitButtonType={CylandSubmitButton}>
|
||||||
<div className="ascribe-detail-header">
|
|
||||||
<hr style={{marginTop: 0}}/>
|
|
||||||
<h1 className="ascribe-detail-title">{this.state.piece.title}</h1>
|
|
||||||
<DetailProperty label="BY" value={this.state.piece.artist_name} />
|
|
||||||
<DetailProperty label="DATE" value={ this.state.piece.date_created.slice(0, 4) } />
|
|
||||||
<hr/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
subheader={
|
|
||||||
<div className="ascribe-detail-header">
|
|
||||||
<DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } />
|
|
||||||
<DetailProperty label={getLangText('ID')} value={ this.state.piece.bitcoin_id } ellipsis={true} />
|
|
||||||
<hr/>
|
|
||||||
</div>
|
|
||||||
}>
|
|
||||||
|
|
||||||
<CollapsibleParagraph
|
<CollapsibleParagraph
|
||||||
title={getLangText('Loan History')}
|
title={getLangText('Further Details')}
|
||||||
show={this.state.piece.loan_history && this.state.piece.loan_history.length > 0}>
|
defaultExpanded={true}>
|
||||||
<HistoryIterator
|
<CylandAdditionalDataForm
|
||||||
history={this.state.piece.loan_history} />
|
piece={this.state.piece}
|
||||||
|
disabled={!this.state.piece.acl.acl_edit}
|
||||||
|
isInline={true} />
|
||||||
</CollapsibleParagraph>
|
</CollapsibleParagraph>
|
||||||
<CollapsibleParagraph
|
</WalletPieceContainer>
|
||||||
title={getLangText('Notes')}
|
|
||||||
show={(this.state.currentUser.username && true || false) ||
|
|
||||||
(this.state.piece.public_note)}>
|
|
||||||
<Note
|
|
||||||
id={() => {return {'id': this.state.piece.id}; }}
|
|
||||||
label={getLangText('Personal note (private)')}
|
|
||||||
defaultValue={this.state.piece.private_note ? this.state.piece.private_note : null}
|
|
||||||
placeholder={getLangText('Enter your comments ...')}
|
|
||||||
editable={true}
|
|
||||||
successMessage={getLangText('Private note saved')}
|
|
||||||
url={ApiUrls.note_private_piece}
|
|
||||||
currentUser={this.state.currentUser}/>
|
|
||||||
</CollapsibleParagraph>
|
|
||||||
<CylandPieceDetails piece={this.state.piece}/>
|
|
||||||
</Piece>
|
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
return (
|
return (
|
||||||
<div className="fullpage-spinner">
|
<div className="fullpage-spinner">
|
||||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
||||||
@ -123,47 +83,4 @@ let CylandPieceContainer = React.createClass({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
let CylandPieceDetails = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
piece: React.PropTypes.object
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.props.piece && Object.keys(this.props.piece.extra_data).length !== 0){
|
|
||||||
return (
|
|
||||||
<CollapsibleParagraph
|
|
||||||
title={getLangText('Further Details')}
|
|
||||||
show={true}
|
|
||||||
defaultExpanded={true}>
|
|
||||||
<Form ref='form'>
|
|
||||||
{Object.keys(this.props.piece.extra_data).map((data, i) => {
|
|
||||||
let label = data.replace('_', ' ');
|
|
||||||
return (
|
|
||||||
<Property
|
|
||||||
key={i}
|
|
||||||
name={data}
|
|
||||||
label={label}
|
|
||||||
editable={false}>
|
|
||||||
<InputTextAreaToggable
|
|
||||||
rows={1}
|
|
||||||
editable={false}
|
|
||||||
defaultValue={this.props.piece.extra_data[data]}/>
|
|
||||||
</Property>);
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
<FurtherDetailsFileuploader
|
|
||||||
editable={false}
|
|
||||||
pieceId={this.props.piece.id}
|
|
||||||
otherData={this.props.piece.other_data}
|
|
||||||
multiple={false}/>
|
|
||||||
<hr />
|
|
||||||
</Form>
|
|
||||||
</CollapsibleParagraph>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default CylandPieceContainer;
|
export default CylandPieceContainer;
|
||||||
|
@ -9,19 +9,35 @@ import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_t
|
|||||||
|
|
||||||
import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
|
import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
|
||||||
|
|
||||||
|
import GlobalNotificationModel from '../../../../../../models/global_notification_model';
|
||||||
|
import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
|
||||||
|
|
||||||
import ApiUrls from '../../../../../../constants/api_urls';
|
import ApiUrls from '../../../../../../constants/api_urls';
|
||||||
import AppConstants from '../../../../../../constants/application_constants';
|
import AppConstants from '../../../../../../constants/application_constants';
|
||||||
|
|
||||||
import requests from '../../../../../../utils/requests';
|
import requests from '../../../../../../utils/requests';
|
||||||
|
|
||||||
import { getLangText } from '../../../../../../utils/lang_utils';
|
import { getLangText } from '../../../../../../utils/lang_utils';
|
||||||
|
import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_s3_fine_uploader_utils';
|
||||||
|
|
||||||
|
|
||||||
let CylandAdditionalDataForm = React.createClass({
|
let CylandAdditionalDataForm = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
handleSuccess: React.PropTypes.func.isRequired,
|
handleSuccess: React.PropTypes.func,
|
||||||
piece: React.PropTypes.object.isRequired,
|
piece: React.PropTypes.object.isRequired,
|
||||||
|
disabled: React.PropTypes.bool,
|
||||||
|
isInline: React.PropTypes.bool
|
||||||
|
},
|
||||||
|
|
||||||
disabled: React.PropTypes.bool
|
getDefaultProps() {
|
||||||
|
return {
|
||||||
|
isInline: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSuccess() {
|
||||||
|
let notification = new GlobalNotificationModel('Further details successfully updated', 'success', 10000);
|
||||||
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
@ -60,72 +76,70 @@ let CylandAdditionalDataForm = React.createClass({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
isReadyForFormSubmission(files) {
|
|
||||||
let uploadingFiles = files.filter((file) => file.status === 'submitting');
|
|
||||||
|
|
||||||
if (uploadingFiles.length === 0) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if(this.props.piece && this.props.piece.id) {
|
let { piece, isInline, disabled, handleSuccess } = this.props;
|
||||||
return (
|
let buttons, spinner, heading;
|
||||||
<Form
|
|
||||||
disabled={this.props.disabled}
|
if(!isInline) {
|
||||||
className="ascribe-form-bordered"
|
buttons = (
|
||||||
ref='form'
|
|
||||||
url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: this.props.piece.id})}
|
|
||||||
handleSuccess={this.props.handleSuccess}
|
|
||||||
getFormData={this.getFormData}
|
|
||||||
buttons={
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn ascribe-btn ascribe-btn-login"
|
className="btn ascribe-btn ascribe-btn-login"
|
||||||
disabled={!this.state.isUploadReady || this.props.disabled}>
|
disabled={!this.state.isUploadReady || disabled}>
|
||||||
{getLangText('Proceed to loan')}
|
{getLangText('Proceed to loan')}
|
||||||
</button>
|
</button>
|
||||||
}
|
);
|
||||||
spinner={
|
|
||||||
|
spinner = (
|
||||||
<div className="modal-footer">
|
<div className="modal-footer">
|
||||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||||
</div>
|
</div>
|
||||||
}>
|
);
|
||||||
|
|
||||||
|
heading = (
|
||||||
<div className="ascribe-form-header">
|
<div className="ascribe-form-header">
|
||||||
<h3>
|
<h3>
|
||||||
{getLangText('Provide supporting materials')}
|
{getLangText('Provide supporting materials')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(piece && piece.id) {
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
disabled={disabled}
|
||||||
|
className="ascribe-form-bordered"
|
||||||
|
ref='form'
|
||||||
|
url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: piece.id})}
|
||||||
|
handleSuccess={handleSuccess || this.handleSuccess}
|
||||||
|
getFormData={this.getFormData}
|
||||||
|
buttons={buttons}
|
||||||
|
spinner={spinner}>
|
||||||
|
{heading}
|
||||||
<Property
|
<Property
|
||||||
name='artist_bio'
|
name='artist_bio'
|
||||||
label={getLangText('Artist Biography')}
|
label={getLangText('Artist Biography')}>
|
||||||
editable={!this.props.disabled}>
|
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={!this.props.disabled}
|
defaultValue={piece.extra_data.artist_bio}
|
||||||
placeholder={getLangText('Enter the artist\'s biography...')}
|
placeholder={getLangText('Enter the artist\'s biography...')}/>
|
||||||
required="required"/>
|
|
||||||
</Property>
|
</Property>
|
||||||
<Property
|
<Property
|
||||||
name='conceptual_overview'
|
name='conceptual_overview'
|
||||||
label={getLangText('Conceptual Overview')}
|
label={getLangText('Conceptual Overview')}>
|
||||||
editable={!this.props.disabled}>
|
|
||||||
<InputTextAreaToggable
|
<InputTextAreaToggable
|
||||||
rows={1}
|
rows={1}
|
||||||
editable={!this.props.disabled}
|
defaultValue={piece.extra_data.conceptual_overview}
|
||||||
placeholder={getLangText('Enter a conceptual overview...')}
|
placeholder={getLangText('Enter a conceptual overview...')}/>
|
||||||
required="required"/>
|
|
||||||
</Property>
|
</Property>
|
||||||
<FurtherDetailsFileuploader
|
<FurtherDetailsFileuploader
|
||||||
uploadStarted={this.uploadStarted}
|
uploadStarted={this.uploadStarted}
|
||||||
submitKey={this.submitKey}
|
submitFile={this.submitFile}
|
||||||
setIsUploadReady={this.setIsUploadReady}
|
setIsUploadReady={this.setIsUploadReady}
|
||||||
isReadyForFormSubmission={this.isReadyForFormSubmission}
|
isReadyForFormSubmission={formSubmissionValidation.fileOptional}
|
||||||
editable={!this.props.disabled}
|
pieceId={piece.id}
|
||||||
pieceId={this.props.piece.id}
|
otherData={piece.other_data}
|
||||||
otherData={this.props.piece.other_data}
|
|
||||||
multiple={true}/>
|
multiple={true}/>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
@ -8,6 +8,8 @@ import UserStore from '../../../../../stores/user_store';
|
|||||||
|
|
||||||
import CylandAccordionListItem from './ascribe_accordion_list/cyland_accordion_list_item';
|
import CylandAccordionListItem from './ascribe_accordion_list/cyland_accordion_list_item';
|
||||||
|
|
||||||
|
import { getLangText } from '../../../../../utils/lang_utils';
|
||||||
|
|
||||||
|
|
||||||
let CylandPieceList = React.createClass({
|
let CylandPieceList = React.createClass({
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
@ -33,6 +35,13 @@ let CylandPieceList = React.createClass({
|
|||||||
<PieceList
|
<PieceList
|
||||||
redirectTo="register_piece"
|
redirectTo="register_piece"
|
||||||
accordionListItemType={CylandAccordionListItem}
|
accordionListItemType={CylandAccordionListItem}
|
||||||
|
filterParams={[{
|
||||||
|
label: getLangText('Show works I have'),
|
||||||
|
items: [{
|
||||||
|
key: 'acl_loaned',
|
||||||
|
label: getLangText('loaned to Cyland')
|
||||||
|
}]
|
||||||
|
}]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -10,9 +10,6 @@ import Row from 'react-bootstrap/lib/Row';
|
|||||||
|
|
||||||
import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece';
|
import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece';
|
||||||
|
|
||||||
import Property from '../../../../ascribe_forms/property';
|
|
||||||
import InputCheckbox from '../../../../ascribe_forms/input_checkbox';
|
|
||||||
|
|
||||||
import WhitelabelActions from '../../../../../actions/whitelabel_actions';
|
import WhitelabelActions from '../../../../../actions/whitelabel_actions';
|
||||||
import WhitelabelStore from '../../../../../stores/whitelabel_store';
|
import WhitelabelStore from '../../../../../stores/whitelabel_store';
|
||||||
|
|
||||||
@ -136,7 +133,7 @@ let CylandRegisterPiece = React.createClass({
|
|||||||
this.transitionTo('piece', {pieceId: this.state.piece.id});
|
this.transitionTo('piece', {pieceId: this.state.piece.id});
|
||||||
},
|
},
|
||||||
|
|
||||||
// We need to increase the step to lock the forms that are already filed out
|
// We need to increase the step to lock the forms that are already filled out
|
||||||
incrementStep() {
|
incrementStep() {
|
||||||
// also increase step
|
// also increase step
|
||||||
let newStep = this.state.step + 1;
|
let newStep = this.state.step + 1;
|
||||||
@ -222,21 +219,7 @@ let CylandRegisterPiece = React.createClass({
|
|||||||
showStartDate={false}
|
showStartDate={false}
|
||||||
showEndDate={false}
|
showEndDate={false}
|
||||||
showPersonalMessage={false}
|
showPersonalMessage={false}
|
||||||
handleSuccess={this.handleLoanSuccess}>
|
handleSuccess={this.handleLoanSuccess} />
|
||||||
<Property
|
|
||||||
name="terms"
|
|
||||||
className="ascribe-settings-property-collapsible-toggle"
|
|
||||||
style={{paddingBottom: 0}}>
|
|
||||||
<InputCheckbox>
|
|
||||||
<span>
|
|
||||||
{' ' + getLangText('I agree to the Terms of Service of Cyland Archive') + ' '}
|
|
||||||
(<a href="https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/cyland/terms_and_contract.pdf" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
|
|
||||||
{getLangText('read')}
|
|
||||||
</a>)
|
|
||||||
</span>
|
|
||||||
</InputCheckbox>
|
|
||||||
</Property>
|
|
||||||
</LoanForm>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
@ -62,17 +62,31 @@ let IkonotvAccordionListItem = React.createClass({
|
|||||||
getSubmitButtons() {
|
getSubmitButtons() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<AclProxy
|
||||||
|
aclObject={this.state.currentUser.acl}
|
||||||
|
aclName="acl_wallet_submit">
|
||||||
<AclProxy
|
<AclProxy
|
||||||
aclObject={this.props.content.acl}
|
aclObject={this.props.content.acl}
|
||||||
aclName="acl_submit">
|
aclName="acl_wallet_submit">
|
||||||
<IkonotvSubmitButton
|
<IkonotvSubmitButton
|
||||||
className="btn-xs pull-right"
|
className="btn-xs pull-right"
|
||||||
handleSuccess={this.handleSubmitSuccess}
|
handleSuccess={this.handleSubmitSuccess}
|
||||||
piece={this.props.content}/>
|
piece={this.props.content}/>
|
||||||
</AclProxy>
|
</AclProxy>
|
||||||
|
</AclProxy>
|
||||||
<AclProxy
|
<AclProxy
|
||||||
aclObject={this.props.content.acl}
|
aclObject={this.props.content.acl}
|
||||||
aclName="acl_submitted">
|
aclName="acl_wallet_submitted">
|
||||||
|
<button
|
||||||
|
disabled
|
||||||
|
className="btn btn-default btn-xs pull-right">
|
||||||
|
{getLangText('Submitted to IkonoTV')} <span className="glyphicon glyphicon-ok"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</AclProxy>
|
||||||
|
<AclProxy
|
||||||
|
aclObject={this.props.content.acl}
|
||||||
|
aclName="acl_wallet_accepted">
|
||||||
<button
|
<button
|
||||||
disabled
|
disabled
|
||||||
className="btn btn-default btn-xs pull-right">
|
className="btn btn-default btn-xs pull-right">
|
||||||
|
@ -1,17 +1,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Moment from 'moment';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import ButtonLink from 'react-router-bootstrap/lib/ButtonLink';
|
||||||
import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper';
|
|
||||||
|
|
||||||
import LoanForm from '../../../../../ascribe_forms/form_loan';
|
|
||||||
|
|
||||||
import Property from '../../../../../ascribe_forms/property';
|
|
||||||
import InputCheckbox from '../../../../../ascribe_forms/input_checkbox';
|
|
||||||
|
|
||||||
import ApiUrls from '../../../../../../constants/api_urls';
|
|
||||||
|
|
||||||
import { getLangText } from '../../../../../../utils/lang_utils';
|
import { getLangText } from '../../../../../../utils/lang_utils';
|
||||||
|
|
||||||
@ -22,51 +13,36 @@ let IkonotvSubmitButton = React.createClass({
|
|||||||
piece: React.PropTypes.object.isRequired
|
piece: React.PropTypes.object.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getSubmitButton() {
|
getDefaultProps() {
|
||||||
return (
|
return {
|
||||||
<button
|
className: 'btn-xs'
|
||||||
className={classNames('btn', 'btn-default', this.props.className)}>
|
};
|
||||||
{getLangText('Loan to IkonoTV')}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
let piece = this.props.piece;
|
||||||
|
let startFrom = 1;
|
||||||
|
|
||||||
let today = new Moment();
|
// In the Ikonotv loan page a user has to complete two steps.
|
||||||
let enddate = new Moment();
|
// Since every one of those steps is atomic a user should always be able to continue
|
||||||
enddate.add(1, 'years');
|
// where he left of.
|
||||||
|
// This is why we start the process form slide 1/2 if the user has already finished
|
||||||
|
// it in another session.
|
||||||
|
if(piece && piece.extra_data && Object.keys(piece.extra_data).length > 0) {
|
||||||
|
startFrom = 1;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalWrapper
|
<ButtonLink
|
||||||
trigger={this.getSubmitButton()}
|
to="register_piece"
|
||||||
handleSuccess={this.props.handleSuccess}
|
query={{
|
||||||
title={getLangText('Loan to IkonoTV archive')}>
|
'slide_num': 0,
|
||||||
<LoanForm
|
'start_from': startFrom,
|
||||||
id={{piece_id: this.props.piece.id}}
|
'piece_id': piece.id
|
||||||
url={ApiUrls.ownership_loans_pieces}
|
}}
|
||||||
email="submissions@ikono.org"
|
className={classNames('ascribe-margin-1px', this.props.className)}>
|
||||||
startdate={today}
|
{getLangText('Loan to IkonoTV')}
|
||||||
enddate={enddate}
|
</ButtonLink>
|
||||||
gallery="IkonoTV archive"
|
|
||||||
showPersonalMessage={false}
|
|
||||||
handleSuccess={this.props.handleSuccess}>
|
|
||||||
<Property
|
|
||||||
name="terms"
|
|
||||||
className="ascribe-settings-property-collapsible-toggle"
|
|
||||||
style={{paddingBottom: 0}}>
|
|
||||||
<InputCheckbox>
|
|
||||||
<span>
|
|
||||||
{' ' + getLangText('I agree to the Terms of Service of IkonoTV Archive') + ' '}
|
|
||||||
(<a href="https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/ikonotv/ikono-tos.pdf" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
|
|
||||||
{getLangText('read')}
|
|
||||||
</a>)
|
|
||||||
</span>
|
|
||||||
</InputCheckbox>
|
|
||||||
</Property>
|
|
||||||
</LoanForm>
|
|
||||||
</ModalWrapper>
|
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -5,53 +5,45 @@ import React from 'react';
|
|||||||
import PieceActions from '../../../../../../actions/piece_actions';
|
import PieceActions from '../../../../../../actions/piece_actions';
|
||||||
import PieceStore from '../../../../../../stores/piece_store';
|
import PieceStore from '../../../../../../stores/piece_store';
|
||||||
|
|
||||||
import PieceListActions from '../../../../../../actions/piece_list_actions';
|
|
||||||
import PieceListStore from '../../../../../../stores/piece_list_store';
|
|
||||||
|
|
||||||
import UserStore from '../../../../../../stores/user_store';
|
import UserStore from '../../../../../../stores/user_store';
|
||||||
|
|
||||||
import Piece from '../../../../../../components/ascribe_detail/piece';
|
|
||||||
|
|
||||||
import ListRequestActions from '../../../../../ascribe_forms/list_form_request_actions';
|
|
||||||
import AclButtonList from '../../../../../ascribe_buttons/acl_button_list';
|
|
||||||
import DeleteButton from '../../../../../ascribe_buttons/delete_button';
|
|
||||||
|
|
||||||
import CollapsibleParagraph from '../../../../../../components/ascribe_collapsible/collapsible_paragraph';
|
|
||||||
|
|
||||||
import IkonotvSubmitButton from '../ascribe_buttons/ikonotv_submit_button';
|
import IkonotvSubmitButton from '../ascribe_buttons/ikonotv_submit_button';
|
||||||
|
|
||||||
import HistoryIterator from '../../../../../ascribe_detail/history_iterator';
|
import CollapsibleParagraph from '../../../../../../components/ascribe_collapsible/collapsible_paragraph';
|
||||||
|
|
||||||
import DetailProperty from '../../../../../ascribe_detail/detail_property';
|
import IkonotvArtistDetailsForm from '../ascribe_forms/ikonotv_artist_details_form';
|
||||||
|
import IkonotvArtworkDetailsForm from '../ascribe_forms/ikonotv_artwork_details_form';
|
||||||
|
|
||||||
|
import WalletPieceContainer from '../../ascribe_detail/wallet_piece_container';
|
||||||
import GlobalNotificationModel from '../../../../../../models/global_notification_model';
|
|
||||||
import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
|
|
||||||
|
|
||||||
import AclProxy from '../../../../../acl_proxy';
|
|
||||||
|
|
||||||
import AppConstants from '../../../../../../constants/application_constants';
|
import AppConstants from '../../../../../../constants/application_constants';
|
||||||
|
|
||||||
import { getLangText } from '../../../../../../utils/lang_utils';
|
import { getLangText } from '../../../../../../utils/lang_utils';
|
||||||
import { mergeOptions } from '../../../../../../utils/general_utils';
|
import { mergeOptions } from '../../../../../../utils/general_utils';
|
||||||
|
|
||||||
|
|
||||||
let IkonotvPieceContainer = React.createClass({
|
let IkonotvPieceContainer = React.createClass({
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return mergeOptions(
|
return mergeOptions(
|
||||||
PieceStore.getState(),
|
PieceStore.getState(),
|
||||||
UserStore.getState(),
|
UserStore.getState()
|
||||||
PieceListStore.getState()
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
PieceStore.listen(this.onChange);
|
PieceStore.listen(this.onChange);
|
||||||
PieceActions.fetchOne(this.props.params.pieceId);
|
|
||||||
UserStore.listen(this.onChange);
|
UserStore.listen(this.onChange);
|
||||||
PieceListStore.listen(this.onChange);
|
|
||||||
|
// Every time we're leaving the piece detail page,
|
||||||
|
// just reset the piece that is saved in the piece store
|
||||||
|
// as it will otherwise display wrong/old data once the user loads
|
||||||
|
// the piece detail a second time
|
||||||
|
PieceActions.updatePiece({});
|
||||||
|
|
||||||
|
this.loadPiece();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// We need this for when the user clicks on a notification while being in another piece view
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if(this.props.params.pieceId !== nextProps.params.pieceId) {
|
if(this.props.params.pieceId !== nextProps.params.pieceId) {
|
||||||
PieceActions.updatePiece({});
|
PieceActions.updatePiece({});
|
||||||
@ -60,14 +52,8 @@ let IkonotvPieceContainer = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
// Every time we're leaving the piece detail page,
|
|
||||||
// just reset the piece that is saved in the piece store
|
|
||||||
// as it will otherwise display wrong/old data once the user loads
|
|
||||||
// the piece detail a second time
|
|
||||||
PieceActions.updatePiece({});
|
|
||||||
PieceStore.unlisten(this.onChange);
|
PieceStore.unlisten(this.onChange);
|
||||||
UserStore.unlisten(this.onChange);
|
UserStore.unlisten(this.onChange);
|
||||||
PieceListStore.unlisten(this.onChange);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onChange(state) {
|
onChange(state) {
|
||||||
@ -78,92 +64,44 @@ let IkonotvPieceContainer = React.createClass({
|
|||||||
PieceActions.fetchOne(this.props.params.pieceId);
|
PieceActions.fetchOne(this.props.params.pieceId);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSubmitSuccess(response) {
|
render() {
|
||||||
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
|
let furtherDetails = (
|
||||||
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
|
<CollapsibleParagraph
|
||||||
|
title={getLangText('Further Details')}
|
||||||
|
defaultExpanded={true}>
|
||||||
|
<span>{getLangText('This piece has been loaned before we started to collect further details.')}</span>
|
||||||
|
</CollapsibleParagraph>
|
||||||
|
);
|
||||||
|
|
||||||
this.loadPiece();
|
if(this.state.piece.extra_data && Object.keys(this.state.piece.extra_data).length > 0 && this.state.piece.acl) {
|
||||||
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
|
furtherDetails = (
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
<CollapsibleParagraph
|
||||||
},
|
title={getLangText('Further Details')}
|
||||||
|
defaultExpanded={true}>
|
||||||
|
<IkonotvArtistDetailsForm
|
||||||
|
piece={this.state.piece}
|
||||||
|
isInline={true}
|
||||||
|
disabled={!this.state.piece.acl.acl_edit} />
|
||||||
|
<IkonotvArtworkDetailsForm
|
||||||
|
piece={this.state.piece}
|
||||||
|
isInline={true}
|
||||||
|
disabled={!this.state.piece.acl.acl_edit} />
|
||||||
|
</CollapsibleParagraph>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getActions(){
|
if(this.state.piece && this.state.piece.title) {
|
||||||
if (this.state.piece &&
|
|
||||||
this.state.piece.request_action &&
|
|
||||||
this.state.piece.request_action.length > 0) {
|
|
||||||
return (
|
return (
|
||||||
<ListRequestActions
|
<WalletPieceContainer
|
||||||
pieceOrEditions={this.state.piece}
|
piece={this.state.piece}
|
||||||
currentUser={this.state.currentUser}
|
currentUser={this.state.currentUser}
|
||||||
handleSuccess={this.loadPiece}
|
loadPiece={this.loadPiece}
|
||||||
requestActions={this.state.piece.request_action}/>
|
submitButtonType={IkonotvSubmitButton}>
|
||||||
|
{furtherDetails}
|
||||||
|
</WalletPieceContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
//We need to disable the normal acl_loan because we're inserting a custom acl_loan button
|
|
||||||
let availableAcls;
|
|
||||||
|
|
||||||
if(this.state.piece && this.state.piece.acl && typeof this.state.piece.acl.acl_loan !== 'undefined') {
|
|
||||||
// make a copy to not have side effects
|
|
||||||
availableAcls = mergeOptions({}, this.state.piece.acl);
|
|
||||||
availableAcls.acl_loan = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AclButtonList
|
|
||||||
className="text-center ascribe-button-list"
|
|
||||||
availableAcls={availableAcls}
|
|
||||||
editions={this.state.piece}
|
|
||||||
handleSuccess={this.loadPiece}>
|
|
||||||
<AclProxy
|
|
||||||
aclObject={availableAcls}
|
|
||||||
aclName="acl_submit">
|
|
||||||
<IkonotvSubmitButton
|
|
||||||
className="btn-sm"
|
|
||||||
handleSuccess={this.handleSubmitSuccess}
|
|
||||||
piece={this.state.piece}/>
|
|
||||||
</AclProxy>
|
|
||||||
<DeleteButton
|
|
||||||
handleSuccess={this.handleDeleteSuccess}
|
|
||||||
piece={this.state.piece}/>
|
|
||||||
</AclButtonList>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if(this.state.piece && this.state.piece.title) {
|
|
||||||
return (
|
|
||||||
<Piece
|
|
||||||
piece={this.state.piece}
|
|
||||||
loadPiece={this.loadPiece}
|
|
||||||
header={
|
|
||||||
<div className="ascribe-detail-header">
|
|
||||||
<hr style={{marginTop: 0}}/>
|
|
||||||
<h1 className="ascribe-detail-title">{this.state.piece.title}</h1>
|
|
||||||
<DetailProperty label="BY" value={this.state.piece.artist_name} />
|
|
||||||
<DetailProperty label="DATE" value={ this.state.piece.date_created.slice(0, 4) } />
|
|
||||||
<hr/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
subheader={
|
|
||||||
<div className="ascribe-detail-header">
|
|
||||||
<DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } />
|
|
||||||
<DetailProperty label={getLangText('ID')} value={ this.state.piece.bitcoin_id } ellipsis={true} />
|
|
||||||
<hr/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
buttons={this.getActions()}>
|
|
||||||
<CollapsibleParagraph
|
|
||||||
title={getLangText('Loan History')}
|
|
||||||
show={this.state.piece.loan_history && this.state.piece.loan_history.length > 0}>
|
|
||||||
<HistoryIterator
|
|
||||||
history={this.state.piece.loan_history} />
|
|
||||||
</CollapsibleParagraph>
|
|
||||||
</Piece>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
return (
|
||||||
<div className="fullpage-spinner">
|
<div className="fullpage-spinner">
|
||||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user