mirror of
https://github.com/ascribe/onion.git
synced 2024-12-22 09:23:13 +01:00
Merge remote-tracking branch 'origin/master' into piece-detail-mediaplayer
Conflicts: js/components/edition.js js/mixins/inject_in_head_mixin.js
This commit is contained in:
commit
641e8ac82d
@ -5,11 +5,11 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>ascribe</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="build/css/main.css">
|
||||
<link rel="stylesheet" href="//brick.a.ssl.fastly.net/Source+Sans+Pro:400,600,700,900">
|
||||
<link rel="stylesheet" href="build/css/main.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="main" class="container clear-margins-and-paddings"></div>
|
||||
<div id="main" class="container clear-paddings"></div>
|
||||
<div id="modal" class="container"></div>
|
||||
<script src="build/app.js"></script>
|
||||
</body>
|
||||
|
@ -15,7 +15,7 @@ class PieceActions {
|
||||
this.actions.updatePiece(res.piece);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,8 @@ import PieceListFetcher from '../fetchers/piece_list_fetcher';
|
||||
class PieceListActions {
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
'updatePieceList'
|
||||
'updatePieceList',
|
||||
'showEditionList'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,27 +1,66 @@
|
||||
import React from 'react';
|
||||
|
||||
import ConsignForm from './ascribe_forms/form_consign';
|
||||
import TransferForm from './ascribe_forms/form_transfer';
|
||||
import LoanForm from './ascribe_forms/form_loan';
|
||||
import ShareForm from './ascribe_forms/form_share_email';
|
||||
import ModalWrapper from './ascribe_modal/modal_wrapper';
|
||||
import AppConstants from '../constants/application_constants';
|
||||
|
||||
let AclButton = React.createClass({
|
||||
propTypes: {
|
||||
action: React.PropTypes.oneOf(AppConstants.aclList).isRequired,
|
||||
availableAcls: React.PropTypes.array.isRequired,
|
||||
actionFunction: React.PropTypes.func.isRequired
|
||||
editions: React.PropTypes.array.isRequired,
|
||||
currentUser: React.PropTypes.object,
|
||||
handleSuccess: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
actionFunction() {
|
||||
this.props.actionFunction(this.props.action);
|
||||
actionProperties(){
|
||||
if (this.props.action == 'consign'){
|
||||
return {
|
||||
title: "Consign artwork",
|
||||
tooltip: "Have someone else sell the artwork",
|
||||
form: <ConsignForm />
|
||||
}
|
||||
}
|
||||
else if (this.props.action == 'transfer') {
|
||||
return {
|
||||
title: "Transfer artwork",
|
||||
tooltip: "Transfer the ownership of the artwork",
|
||||
form: <TransferForm />
|
||||
}
|
||||
}
|
||||
else if (this.props.action == 'loan'){
|
||||
return {
|
||||
title: "Loan artwork",
|
||||
tooltip: "Loan your artwork for a limited period of time",
|
||||
form: <LoanForm />}
|
||||
}
|
||||
else if (this.props.action == 'share'){
|
||||
return {
|
||||
title: "Share artwork",
|
||||
tooltip: "Share the artwork",
|
||||
form: <ShareForm />}
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
let shouldDisplay = this.props.availableAcls.indexOf(this.props.action) > -1;
|
||||
let aclProps = this.actionProperties();
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={shouldDisplay ? 'btn btn-default btn-sm' : 'hidden'}
|
||||
onClick={this.actionFunction}>
|
||||
{this.props.action.toUpperCase()}
|
||||
</button>
|
||||
<ModalWrapper
|
||||
button={
|
||||
<div className={shouldDisplay ? 'btn btn-default btn-sm' : 'hidden'}>
|
||||
{this.props.action.toUpperCase()}
|
||||
</div>
|
||||
}
|
||||
currentUser={ this.props.currentUser }
|
||||
editions={ this.props.editions }
|
||||
handleSuccess={ this.props.handleSuccess }
|
||||
title={ aclProps.title }
|
||||
tooltip={ aclProps.tooltip }>
|
||||
{ aclProps.form }
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
|
||||
import AccordionListItemTable from './accordion_list_item_table';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let AccordionListItem = React.createClass({
|
||||
propTypes: {
|
||||
@ -19,7 +20,7 @@ let AccordionListItem = React.createClass({
|
||||
</div>
|
||||
<div className="info-wrapper">
|
||||
<h1>{this.props.content.title}</h1>
|
||||
<h3>by {this.props.content.artist_name}</h3>
|
||||
<h3>{getLangText('by %s', this.props.content.artist_name)}</h3>
|
||||
</div>
|
||||
<span style={{'clear': 'both'}}></span>
|
||||
</div>
|
||||
|
@ -5,31 +5,20 @@ import TableItem from '../ascribe_table/table_item';
|
||||
|
||||
import TableColumnContentModel from '../../models/table_column_content_model';
|
||||
|
||||
let AccordionListItemTable = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
'show': false
|
||||
};
|
||||
},
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let AccordionListItemTable = React.createClass({
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
parentId: React.PropTypes.number,
|
||||
fetchData: React.PropTypes.func,
|
||||
itemList: React.PropTypes.array,
|
||||
columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)),
|
||||
numOfTableItems: React.PropTypes.number
|
||||
},
|
||||
|
||||
toggleTable() {
|
||||
this.props.fetchData();
|
||||
this.setState({
|
||||
'show': !this.state.show
|
||||
});
|
||||
numOfTableItems: React.PropTypes.number,
|
||||
show: React.PropTypes.bool
|
||||
},
|
||||
|
||||
render() {
|
||||
if(this.props.itemList && this.state.show) {
|
||||
if(this.props.show && this.props.itemList) {
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<Table
|
||||
@ -44,44 +33,17 @@ let AccordionListItemTable = React.createClass({
|
||||
);
|
||||
})}
|
||||
</Table>
|
||||
<AccordionListItemTableToggle
|
||||
className="ascribe-accordion-list-table-toggle"
|
||||
onClick={this.toggleTable}
|
||||
show={this.state.show}
|
||||
numOfTableItems={this.props.numOfTableItems} />
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<AccordionListItemTableToggle
|
||||
className="ascribe-accordion-list-table-toggle"
|
||||
onClick={this.toggleTable}
|
||||
show={this.state.show}
|
||||
numOfTableItems={this.props.numOfTableItems} />
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let AccordionListItemTableToggle = React.createClass({
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
onClick: React.PropTypes.func,
|
||||
show: React.PropTypes.bool,
|
||||
numOfTableItems: React.PropTypes.number
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span
|
||||
className={this.props.className}
|
||||
onClick={this.props.onClick}>
|
||||
{this.props.show ? 'Hide all ' + this.props.numOfTableItems + ' Editions' : 'Show all ' + this.props.numOfTableItems + ' Editions'}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default AccordionListItemTable;
|
@ -2,8 +2,11 @@ import React from 'react';
|
||||
|
||||
import EditionListStore from '../../stores/edition_list_store';
|
||||
import EditionListActions from '../../actions/edition_list_actions';
|
||||
import PieceListStore from '../../stores/piece_list_store';
|
||||
import PieceListActions from '../../actions/piece_list_actions';
|
||||
|
||||
import AccordionListItemTable from './accordion_list_item_table';
|
||||
import AccordionListItemTableToggle from './accordion_list_item_table_toggle';
|
||||
|
||||
import TableColumnContentModel from '../../models/table_column_content_model';
|
||||
|
||||
@ -12,12 +15,15 @@ import TableItemText from '../ascribe_table/table_item_text';
|
||||
import TableItemCheckbox from '../ascribe_table/table_item_checkbox';
|
||||
import TableItemAclFiltered from '../ascribe_table/table_item_acl_filtered';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let AccordionListItemTableEditions = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
parentId: React.PropTypes.number,
|
||||
numOfEditions: React.PropTypes.number
|
||||
numOfEditions: React.PropTypes.number,
|
||||
show: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
@ -36,30 +42,83 @@ let AccordionListItemTableEditions = React.createClass({
|
||||
EditionListStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
getEditionList() {
|
||||
EditionListActions.fetchEditionList(this.props.parentId);
|
||||
},
|
||||
|
||||
selectItem(pieceId, editionId) {
|
||||
EditionListActions.selectEdition({pieceId, editionId});
|
||||
},
|
||||
|
||||
toggleTable() {
|
||||
PieceListActions.showEditionList(this.props.parentId);
|
||||
EditionListActions.fetchEditionList(this.props.parentId);
|
||||
},
|
||||
|
||||
render() {
|
||||
let columnList = [
|
||||
new TableColumnContentModel((item) => { return { 'editionId': item.id, 'pieceId': this.props.parentId, 'selectItem': this.selectItem, 'selected': item.selected }}, '', '', TableItemCheckbox, 1, false),
|
||||
new TableColumnContentModel((item) => { return { 'content': item.edition_number }}, 'num_editions', 'Nr', TableItemText, 1, false),
|
||||
new TableColumnContentModel((item) => { return { 'content': item.bitcoin_id }}, 'bitcoin_id', 'Bitcoin Address', TableItemText, 5, false),
|
||||
new TableColumnContentModel((item) => { return { 'content': item.acl }}, 'acl', 'Actions', TableItemAclFiltered, 4, false)
|
||||
new TableColumnContentModel(
|
||||
(item) => {
|
||||
return {
|
||||
'editionId': item.id,
|
||||
'pieceId': this.props.parentId,
|
||||
'selectItem': this.selectItem,
|
||||
'selected': item.selected
|
||||
}},
|
||||
'',
|
||||
'',
|
||||
TableItemCheckbox,
|
||||
1,
|
||||
false
|
||||
),
|
||||
new TableColumnContentModel(
|
||||
(item) => {
|
||||
return {
|
||||
'content': item.edition_number
|
||||
}},
|
||||
'num_editions',
|
||||
'#',
|
||||
TableItemText,
|
||||
1,
|
||||
false
|
||||
),
|
||||
new TableColumnContentModel(
|
||||
(item) => {
|
||||
return {
|
||||
'content': item.bitcoin_id
|
||||
}},
|
||||
'bitcoin_id',
|
||||
getLangText('Bitcoin Address'),
|
||||
TableItemText,
|
||||
5,
|
||||
false
|
||||
),
|
||||
new TableColumnContentModel(
|
||||
(item) => {
|
||||
return {
|
||||
'content': item.acl
|
||||
}},
|
||||
'acl',
|
||||
getLangText('Actions'),
|
||||
TableItemAclFiltered,
|
||||
4,
|
||||
false
|
||||
)
|
||||
];
|
||||
|
||||
return (
|
||||
<AccordionListItemTable
|
||||
className={this.props.className}
|
||||
parentId={this.props.parentId}
|
||||
fetchData={this.getEditionList}
|
||||
itemList={this.state.editionList[this.props.parentId]}
|
||||
columnList={columnList}
|
||||
numOfTableItems={this.props.numOfEditions} />
|
||||
<div>
|
||||
<AccordionListItemTable
|
||||
className={this.props.className}
|
||||
parentId={this.props.parentId}
|
||||
itemList={this.state.editionList[this.props.parentId]}
|
||||
columnList={columnList}
|
||||
numOfTableItems={this.props.numOfEditions}
|
||||
show={this.props.show}>
|
||||
<AccordionListItemTableToggle
|
||||
className="ascribe-accordion-list-table-toggle"
|
||||
onClick={this.toggleTable}
|
||||
show={this.props.show}
|
||||
numOfTableItems={this.props.numOfEditions} />
|
||||
</AccordionListItemTable>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
let AccordionListItemTableToggle = React.createClass({
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
onClick: React.PropTypes.func,
|
||||
show: React.PropTypes.bool,
|
||||
numOfTableItems: React.PropTypes.number
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span
|
||||
className={this.props.className}
|
||||
onClick={this.props.onClick}>
|
||||
{this.props.show ? 'Hide all ' + this.props.numOfTableItems + ' Editions' : 'Show all ' + this.props.numOfTableItems + ' Editions'}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default AccordionListItemTableToggle;
|
@ -3,11 +3,11 @@ import React from 'react';
|
||||
let ButtonSubmitOrClose = React.createClass({
|
||||
render() {
|
||||
if (this.props.submitted){
|
||||
return (
|
||||
<div className="modal-footer">
|
||||
Loading
|
||||
</div>
|
||||
)
|
||||
//return (
|
||||
// <div className="modal-footer">
|
||||
// Loading
|
||||
// </div>
|
||||
//)
|
||||
}
|
||||
return (
|
||||
<div className="modal-footer">
|
||||
|
68
js/components/ascribe_forms/form_consign.js
Normal file
68
js/components/ascribe_forms/form_consign.js
Normal file
@ -0,0 +1,68 @@
|
||||
import fetch from 'isomorphic-fetch';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import FormMixin from '../../mixins/form_mixin';
|
||||
import InputText from './input_text';
|
||||
import InputTextArea from './input_textarea';
|
||||
import ButtonSubmitOrClose from './button_submit_close';
|
||||
|
||||
let ConsignForm = React.createClass({
|
||||
mixins: [FormMixin],
|
||||
|
||||
url() {
|
||||
return ApiUrls.ownership_consigns
|
||||
},
|
||||
getFormData() {
|
||||
return {
|
||||
bitcoin_id: this.getBitcoinIds().join(),
|
||||
consignee: this.refs.consignee.state.value,
|
||||
consign_message: this.refs.consign_message.state.value,
|
||||
password: this.refs.password.state.value
|
||||
}
|
||||
},
|
||||
|
||||
renderForm() {
|
||||
let title = this.getTitlesString().join("");
|
||||
let username = this.props.currentUser.username;
|
||||
let message =
|
||||
`Hi,
|
||||
|
||||
I consign :
|
||||
${title}to you.
|
||||
|
||||
Truly yours,
|
||||
${username}`;
|
||||
|
||||
return (
|
||||
<form id="consign_modal_content" role="form" onSubmit={this.submit}>
|
||||
<input className="invisible" type="email" name="fake_consignee"/>
|
||||
<input className="invisible" type="password" name="fake_password"/>
|
||||
<InputText
|
||||
ref="consignee"
|
||||
placeHolder="Consignee email"
|
||||
required="required"
|
||||
type="email"
|
||||
submitted={this.state.submitted}/>
|
||||
<InputTextArea
|
||||
ref="consign_message"
|
||||
defaultValue={message}
|
||||
required=""
|
||||
/>
|
||||
<InputText
|
||||
ref="password"
|
||||
placeHolder="Password"
|
||||
required="required"
|
||||
type="password"
|
||||
submitted={this.state.submitted}/>
|
||||
<ButtonSubmitOrClose
|
||||
text="CONSIGN"
|
||||
onClose={this.props.onRequestHide}
|
||||
submitted={this.state.submitted} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default ConsignForm;
|
137
js/components/ascribe_forms/form_loan.js
Normal file
137
js/components/ascribe_forms/form_loan.js
Normal file
@ -0,0 +1,137 @@
|
||||
import fetch from 'isomorphic-fetch';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import FormMixin from '../../mixins/form_mixin';
|
||||
import InputText from './input_text';
|
||||
import InputHidden from './input_hidden';
|
||||
import InputCheckbox from './input_checkbox';
|
||||
import InputDate from './input_date';
|
||||
import InputTextArea from './input_textarea';
|
||||
import OwnershipFetcher from '../../fetchers/ownership_fetcher'
|
||||
import ButtonSubmitOrClose from './button_submit_close';
|
||||
|
||||
let LoanForm = React.createClass({
|
||||
mixins: [FormMixin],
|
||||
|
||||
url() {
|
||||
return ApiUrls.ownership_loans
|
||||
},
|
||||
componentDidMount(){
|
||||
this.setState({contract_key: null,
|
||||
contract_url: null,
|
||||
loaneeHasContract: false});
|
||||
},
|
||||
getFormData() {
|
||||
return {
|
||||
bitcoin_id: this.getBitcoinIds().join(),
|
||||
loanee: this.refs.loanee.state.value,
|
||||
gallery_name: this.refs.gallery_name.state.value,
|
||||
startdate: this.refs.startdate.state.value.format("YYYY-MM-DD"),
|
||||
enddate: this.refs.enddate.state.value.format("YYYY-MM-DD"),
|
||||
loan_message: this.refs.loan_message.state.value,
|
||||
password: this.refs.password.state.value,
|
||||
terms: this.refs.terms.state.value
|
||||
}
|
||||
},
|
||||
handleLoanEmailBlur(e){
|
||||
OwnershipFetcher.fetchLoanContract(this.refs.loanee.state.value)
|
||||
.then((res) => {
|
||||
if (res && res.length > 0) {
|
||||
this.setState({contract_key: res[0].s3Key,
|
||||
contract_url: res[0].s3Url,
|
||||
loaneeHasContract: true});
|
||||
}
|
||||
else{
|
||||
this.resetLoanContract();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
this.resetLoanContract();
|
||||
});
|
||||
},
|
||||
resetLoanContract(){
|
||||
this.setState({contract_key: null,
|
||||
contract_url: null,
|
||||
loaneeHasContract: false
|
||||
});
|
||||
},
|
||||
renderForm() {
|
||||
let title = this.getTitlesString().join("");
|
||||
let username = this.props.currentUser.username;
|
||||
let message =
|
||||
`Hi,
|
||||
|
||||
I loan :
|
||||
${title}to you.
|
||||
|
||||
Truly yours,
|
||||
${username}`;
|
||||
|
||||
let contract = <InputHidden ref="terms" value="True"/>;
|
||||
if (this.state.loaneeHasContract){
|
||||
let label = <div>
|
||||
I agree to the
|
||||
<a href={this.state.contract_url} target="_blank">
|
||||
terms of {this.refs.loanee.state.value}
|
||||
</a>
|
||||
</div>;
|
||||
contract = <InputCheckbox
|
||||
ref="terms"
|
||||
required="required"
|
||||
label={label}
|
||||
/>
|
||||
}
|
||||
return (
|
||||
<form id="loan_modal_content" role="form" onSubmit={this.submit}>
|
||||
<input className="invisible" type="email" name="fake_loanee"/>
|
||||
<input className="invisible" type="password" name="fake_password"/>
|
||||
<InputText
|
||||
ref="loanee"
|
||||
placeHolder="Loanee email"
|
||||
required="required"
|
||||
type="email"
|
||||
submitted={this.state.submitted}
|
||||
onBlur={this.handleLoanEmailBlur}/>
|
||||
<InputText
|
||||
ref="gallery_name"
|
||||
placeHolder="Gallery/exhibition (optional)"
|
||||
required=""
|
||||
type="text"
|
||||
submitted={this.state.submitted}/>
|
||||
<div className="row">
|
||||
<div className="col-md-6">
|
||||
<InputDate
|
||||
ref="startdate"
|
||||
placeholderText="Loan start date" />
|
||||
</div>
|
||||
<div className="col-md-6 form-group">
|
||||
<InputDate
|
||||
ref="enddate"
|
||||
placeholderText="Loan end date" />
|
||||
</div>
|
||||
</div>
|
||||
<InputTextArea
|
||||
ref="loan_message"
|
||||
defaultValue={message}
|
||||
required=""
|
||||
/>
|
||||
<InputText
|
||||
ref="password"
|
||||
placeHolder="Password"
|
||||
required="required"
|
||||
type="password"
|
||||
submitted={this.state.submitted}/>
|
||||
{contract}
|
||||
<ButtonSubmitOrClose
|
||||
text="LOAN"
|
||||
onClose={this.props.onRequestHide}
|
||||
submitted={this.state.submitted} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default LoanForm;
|
@ -16,18 +16,23 @@ let ShareForm = React.createClass({
|
||||
},
|
||||
getFormData() {
|
||||
return {
|
||||
bitcoin_id: this.props.edition.bitcoin_id,
|
||||
bitcoin_id: this.getBitcoinIds().join(),
|
||||
share_emails: this.refs.share_emails.state.value,
|
||||
share_message: this.refs.share_message.state.value
|
||||
}
|
||||
},
|
||||
renderForm() {
|
||||
let message = "Hi,\n" +
|
||||
"\n" +
|
||||
"I am sharing \"" + this.props.edition.title + "\" with you.\n" +
|
||||
"\n" +
|
||||
"Truly yours,\n" +
|
||||
this.props.currentUser.username;
|
||||
let title = this.getTitlesString().join("");
|
||||
let username = this.props.currentUser.username;
|
||||
let message =
|
||||
`Hi,
|
||||
|
||||
I am sharing :
|
||||
${title}with you.
|
||||
|
||||
Truly yours,
|
||||
${username}`;
|
||||
|
||||
return (
|
||||
<form id="share_modal_content" role="form" key="share_modal_content" onSubmit={this.submit}>
|
||||
<InputText
|
||||
|
@ -18,19 +18,24 @@ let TransferForm = React.createClass({
|
||||
},
|
||||
getFormData() {
|
||||
return {
|
||||
bitcoin_id: this.props.edition.bitcoin_id,
|
||||
bitcoin_id: this.getBitcoinIds().join(),
|
||||
transferee: this.refs.transferee.state.value,
|
||||
transfer_message: this.refs.transfer_message.state.value,
|
||||
password: this.refs.password.state.value
|
||||
}
|
||||
},
|
||||
renderForm() {
|
||||
let message = "Hi,\n" +
|
||||
"\n" +
|
||||
"I transfer ownership of \"" + this.props.edition.title + "\" to you.\n" +
|
||||
"\n" +
|
||||
"Truly yours,\n" +
|
||||
this.props.currentUser.username;
|
||||
let title = this.getTitlesString().join("");
|
||||
let username = this.props.currentUser.username;
|
||||
let message =
|
||||
`Hi,
|
||||
|
||||
I transfer ownership of :
|
||||
${title}to you.
|
||||
|
||||
Truly yours,
|
||||
${username}`;
|
||||
|
||||
return (
|
||||
<form id="transfer_modal_content" role="form" onSubmit={this.submit}>
|
||||
<input className="invisible" type="email" name="fake_transferee"/>
|
||||
|
58
js/components/ascribe_forms/form_unconsign.js
Normal file
58
js/components/ascribe_forms/form_unconsign.js
Normal file
@ -0,0 +1,58 @@
|
||||
import fetch from 'isomorphic-fetch';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import FormMixin from '../../mixins/form_mixin';
|
||||
import InputText from './input_text';
|
||||
import InputTextArea from './input_textarea';
|
||||
import ButtonSubmitOrClose from './button_submit_close';
|
||||
|
||||
let UnConsignForm = React.createClass({
|
||||
mixins: [FormMixin],
|
||||
|
||||
url() {
|
||||
return ApiUrls.ownership_unconsigns
|
||||
},
|
||||
getFormData() {
|
||||
return {
|
||||
bitcoin_id: this.props.edition.bitcoin_id,
|
||||
unconsign_message: this.refs.unconsign_message.state.value,
|
||||
password: this.refs.password.state.value
|
||||
}
|
||||
},
|
||||
renderForm() {
|
||||
let title = this.props.edition.title;
|
||||
let username = this.props.currentUser.username;
|
||||
let message =
|
||||
`Hi,
|
||||
|
||||
I un-consign \" ${title} \" from you.
|
||||
|
||||
Truly yours,
|
||||
${username}`;
|
||||
|
||||
return (
|
||||
<form id="unconsign_modal_content" role="form" onSubmit={this.submit}>
|
||||
<input className="invisible" type="email" name="fake_unconsignee"/>
|
||||
<input className="invisible" type="password" name="fake_password"/>
|
||||
<InputTextArea
|
||||
ref="unconsign_message"
|
||||
defaultValue={message}
|
||||
required="" />
|
||||
<InputText
|
||||
ref="password"
|
||||
placeHolder="Password"
|
||||
required="required"
|
||||
type="password"
|
||||
submitted={this.state.submitted} />
|
||||
<ButtonSubmitOrClose
|
||||
text="UNCONSIGN"
|
||||
onClose={this.props.onRequestHide}
|
||||
submitted={this.state.submitted} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default UnConsignForm;
|
49
js/components/ascribe_forms/form_unconsign_request.js
Normal file
49
js/components/ascribe_forms/form_unconsign_request.js
Normal file
@ -0,0 +1,49 @@
|
||||
import fetch from 'isomorphic-fetch';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import FormMixin from '../../mixins/form_mixin';
|
||||
import InputText from './input_text';
|
||||
import InputTextArea from './input_textarea';
|
||||
import ButtonSubmitOrClose from './button_submit_close';
|
||||
|
||||
let UnConsignRequestForm = React.createClass({
|
||||
mixins: [FormMixin],
|
||||
|
||||
url() {
|
||||
return ApiUrls.ownership_unconsigns_request
|
||||
},
|
||||
getFormData() {
|
||||
return {
|
||||
bitcoin_id: this.props.edition.bitcoin_id,
|
||||
unconsign_request_message: this.refs.unconsign_request_message.state.value
|
||||
}
|
||||
},
|
||||
renderForm() {
|
||||
let title = this.props.edition.title;
|
||||
let username = this.props.currentUser.username;
|
||||
let message =
|
||||
`Hi,
|
||||
|
||||
I request you to un-consign \" ${title} \".
|
||||
|
||||
Truly yours,
|
||||
${username}`;
|
||||
|
||||
return (
|
||||
<form id="unconsign_request_modal_content" role="form" onSubmit={this.submit}>
|
||||
<InputTextArea
|
||||
ref="unconsign_request_message"
|
||||
defaultValue={message}
|
||||
required="" />
|
||||
<ButtonSubmitOrClose
|
||||
text="UNCONSIGN REQUEST"
|
||||
onClose={this.props.onRequestHide}
|
||||
submitted={this.state.submitted} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default UnConsignRequestForm;
|
40
js/components/ascribe_forms/input_checkbox.js
Normal file
40
js/components/ascribe_forms/input_checkbox.js
Normal file
@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
|
||||
import AlertMixin from '../../mixins/alert_mixin'
|
||||
|
||||
let InputCheckbox = React.createClass({
|
||||
|
||||
mixins : [AlertMixin],
|
||||
|
||||
getInitialState() {
|
||||
return {value: null,
|
||||
alerts: null // needed in AlertMixin
|
||||
};
|
||||
},
|
||||
handleChange(event) {
|
||||
this.setState({value: event.target.value});
|
||||
},
|
||||
render() {
|
||||
let alerts = (this.props.submitted) ? null : this.state.alerts;
|
||||
return (
|
||||
<div className="form-group">
|
||||
{alerts}
|
||||
<div className="input-checkbox-ascribe">
|
||||
<div className="checkbox">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
required={this.props.required}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
{this.props.label}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
export default InputCheckbox;
|
51
js/components/ascribe_forms/input_date.js
Normal file
51
js/components/ascribe_forms/input_date.js
Normal file
@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
|
||||
import AlertMixin from '../../mixins/alert_mixin'
|
||||
import DatePicker from 'react-datepicker/dist/react-datepicker'
|
||||
|
||||
let InputDate = React.createClass({
|
||||
|
||||
mixins : [AlertMixin],
|
||||
|
||||
getInitialState() {
|
||||
return {value: null,
|
||||
alerts: null // needed in AlertMixin
|
||||
};
|
||||
},
|
||||
|
||||
handleChange(date) {
|
||||
this.setState({value: date});
|
||||
},
|
||||
|
||||
render: function () {
|
||||
let className = "form-control input-text-ascribe";
|
||||
let alerts = (this.props.submitted) ? null : this.state.alerts;
|
||||
return (
|
||||
<DatePicker
|
||||
key="example2"
|
||||
dateFormat="YYYY-MM-DD"
|
||||
selected={this.state.value}
|
||||
onChange={this.handleChange}
|
||||
placeholderText={this.props.placeholderText}
|
||||
/>
|
||||
);
|
||||
//return (
|
||||
// <div className="input-group date"
|
||||
// ref={this.props.name + "_picker"}
|
||||
// onChange={this.handleChange}>
|
||||
// <input className={className}
|
||||
// ref={this.props.name}
|
||||
// placeholder={this.props.placeholder}
|
||||
// required={this.props.required}
|
||||
// type="text"/>
|
||||
// <span className="input-group-addon input-text-ascribe">
|
||||
// <span className="glyphicon glyphicon-calendar" style={{"color": "black"}}></span>
|
||||
// </span>
|
||||
// </div>
|
||||
//)
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
export default InputDate;
|
33
js/components/ascribe_forms/input_hidden.js
Normal file
33
js/components/ascribe_forms/input_hidden.js
Normal file
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
|
||||
import AlertMixin from '../../mixins/alert_mixin'
|
||||
|
||||
let InputHidden = React.createClass({
|
||||
|
||||
mixins : [AlertMixin],
|
||||
|
||||
getInitialState() {
|
||||
return {value: this.props.value,
|
||||
alerts: null // needed in AlertMixin
|
||||
};
|
||||
},
|
||||
handleChange(event) {
|
||||
this.setState({value: event.target.value});
|
||||
},
|
||||
render() {
|
||||
let alerts = (this.props.submitted) ? null : this.state.alerts;
|
||||
return (
|
||||
<div className="form-group">
|
||||
{alerts}
|
||||
<input
|
||||
value={this.props.value}
|
||||
type="hidden"
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
export default InputHidden;
|
@ -8,8 +8,7 @@ let InputText = React.createClass({
|
||||
|
||||
getInitialState() {
|
||||
return {value: null,
|
||||
alerts: null, // needed in AlertMixin
|
||||
retry: 0 // needed in AlertMixin for generating unique alerts
|
||||
alerts: null // needed in AlertMixin
|
||||
};
|
||||
},
|
||||
handleChange(event) {
|
||||
@ -25,7 +24,8 @@ let InputText = React.createClass({
|
||||
placeholder={this.props.placeHolder}
|
||||
required={this.props.required}
|
||||
type={this.props.type}
|
||||
onChange={this.handleChange}/>
|
||||
onChange={this.handleChange}
|
||||
onBlur={this.props.onBlur}/>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -8,8 +8,7 @@ let InputTextArea = React.createClass({
|
||||
|
||||
getInitialState() {
|
||||
return {value: this.props.defaultValue,
|
||||
alerts: null, // needed in AlertMixin
|
||||
retry: 0 // needed in AlertMixin for generating unique alerts
|
||||
alerts: null // needed in AlertMixin
|
||||
};
|
||||
},
|
||||
handleChange(event) {
|
||||
@ -28,7 +27,6 @@ let InputTextArea = React.createClass({
|
||||
onChange={this.handleChange}></textarea>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
43
js/components/ascribe_modal/modal_loan.js
Normal file
43
js/components/ascribe_modal/modal_loan.js
Normal file
@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import Modal from 'react-bootstrap/lib/Modal';
|
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
|
||||
import ModalTrigger from 'react-bootstrap/lib/ModalTrigger';
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip';
|
||||
|
||||
import LoanForm from '../ascribe_forms/form_loan'
|
||||
import ModalMixin from '../../mixins/modal_mixin'
|
||||
|
||||
let LoanModalButton = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<OverlayTrigger delay={500} placement="left"
|
||||
overlay={<Tooltip>Loan your artwork for a limited period of time</Tooltip>}>
|
||||
<ModalTrigger modal={<LoanModal edition={this.props.edition}
|
||||
currentUser={this.props.currentUser}/>}>
|
||||
<div className="btn btn-ascribe-inv">
|
||||
LOAN
|
||||
</div>
|
||||
</ModalTrigger>
|
||||
</OverlayTrigger>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let LoanModal = React.createClass({
|
||||
mixins : [ModalMixin],
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal {...this.props} title="Loan artwork">
|
||||
<div className="modal-body">
|
||||
<LoanForm edition={this.props.edition}
|
||||
currentUser={this.props.currentUser}
|
||||
onRequestHide={this.onRequestHide}/>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default LoanModalButton;
|
@ -4,6 +4,7 @@ import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
|
||||
import ModalTrigger from 'react-bootstrap/lib/ModalTrigger';
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip';
|
||||
|
||||
import ModalMixin from '../../mixins/modal_mixin'
|
||||
|
||||
|
||||
import ShareForm from '../ascribe_forms/form_share_email'
|
||||
@ -24,11 +25,8 @@ let ShareModalButton = React.createClass({
|
||||
});
|
||||
|
||||
let ShareModal = React.createClass({
|
||||
onRequestHide(e){
|
||||
if (e)
|
||||
e.preventDefault();
|
||||
this.props.onRequestHide();
|
||||
},
|
||||
mixins : [ModalMixin],
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal {...this.props} title="Share artwork">
|
||||
|
@ -1,45 +0,0 @@
|
||||
import React from 'react';
|
||||
import Modal from 'react-bootstrap/lib/Modal';
|
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
|
||||
import ModalTrigger from 'react-bootstrap/lib/ModalTrigger';
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip';
|
||||
|
||||
import TransferForm from '../ascribe_forms/form_transfer'
|
||||
|
||||
|
||||
let TransferModalButton = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<OverlayTrigger delay={500} placement="left"
|
||||
overlay={<Tooltip>Transfer the ownership of the artwork</Tooltip>}>
|
||||
<ModalTrigger modal={<TransferModal edition={this.props.edition}
|
||||
currentUser={this.props.currentUser}/>}>
|
||||
<div className="btn btn-ascribe-inv">
|
||||
TRANSFER
|
||||
</div>
|
||||
</ModalTrigger>
|
||||
</OverlayTrigger>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let TransferModal = React.createClass({
|
||||
onRequestHide(e){
|
||||
e.preventDefault();
|
||||
this.props.onRequestHide();
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Modal {...this.props} title="Transfer artwork">
|
||||
<div className="modal-body">
|
||||
<TransferForm edition={this.props.edition}
|
||||
currentUser={this.props.currentUser}
|
||||
onRequestHide={this.onRequestHide}/>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default TransferModalButton;
|
43
js/components/ascribe_modal/modal_unconsign.js
Normal file
43
js/components/ascribe_modal/modal_unconsign.js
Normal file
@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import Modal from 'react-bootstrap/lib/Modal';
|
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
|
||||
import ModalTrigger from 'react-bootstrap/lib/ModalTrigger';
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip';
|
||||
|
||||
import UnConsignForm from '../ascribe_forms/form_unconsign'
|
||||
import ModalMixin from '../../mixins/modal_mixin'
|
||||
|
||||
let UnConsignModalButton = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<OverlayTrigger delay={500} placement="left"
|
||||
overlay={<Tooltip>Unconsign this artwork</Tooltip>}>
|
||||
<ModalTrigger modal={<UnConsignModal edition={this.props.edition}
|
||||
currentUser={this.props.currentUser}/>}>
|
||||
<div className="btn btn-ascribe-inv">
|
||||
UNCONSIGN
|
||||
</div>
|
||||
</ModalTrigger>
|
||||
</OverlayTrigger>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let UnConsignModal = React.createClass({
|
||||
mixins : [ModalMixin],
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal {...this.props} title="Consign artwork">
|
||||
<div className="modal-body">
|
||||
<UnConsignForm edition={this.props.edition}
|
||||
currentUser={this.props.currentUser}
|
||||
onRequestHide={this.onRequestHide}/>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default UnConsignModalButton;
|
43
js/components/ascribe_modal/modal_unconsign_request.js
Normal file
43
js/components/ascribe_modal/modal_unconsign_request.js
Normal file
@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import Modal from 'react-bootstrap/lib/Modal';
|
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
|
||||
import ModalTrigger from 'react-bootstrap/lib/ModalTrigger';
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip';
|
||||
|
||||
import UnConsignRequestForm from '../ascribe_forms/form_unconsign_request'
|
||||
import ModalMixin from '../../mixins/modal_mixin'
|
||||
|
||||
let UnConsignRequestModalButton = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<OverlayTrigger delay={500} placement="left"
|
||||
overlay={<Tooltip>Request to unconsign the artwork</Tooltip>}>
|
||||
<ModalTrigger modal={<UnConsignRequestModal edition={this.props.edition}
|
||||
currentUser={this.props.currentUser}/>}>
|
||||
<div className="btn btn-ascribe-inv">
|
||||
UNCONSIGN REQUEST
|
||||
</div>
|
||||
</ModalTrigger>
|
||||
</OverlayTrigger>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let UnConsignRequestModal = React.createClass({
|
||||
mixins : [ModalMixin],
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal {...this.props} title="Request to unconsign artwork">
|
||||
<div className="modal-body">
|
||||
<UnConsignRequestForm edition={this.props.edition}
|
||||
currentUser={this.props.currentUser}
|
||||
onRequestHide={this.onRequestHide}/>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default UnConsignRequestModalButton;
|
64
js/components/ascribe_modal/modal_wrapper.js
Normal file
64
js/components/ascribe_modal/modal_wrapper.js
Normal file
@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import ReactAddons from 'react/addons';
|
||||
|
||||
import Modal from 'react-bootstrap/lib/Modal';
|
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
|
||||
import ModalTrigger from 'react-bootstrap/lib/ModalTrigger';
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip';
|
||||
|
||||
import ModalMixin from '../../mixins/modal_mixin'
|
||||
|
||||
let ModalWrapper = React.createClass({
|
||||
|
||||
render() {
|
||||
return (
|
||||
<OverlayTrigger delay={500} placement="left"
|
||||
overlay={<Tooltip>{this.props.tooltip}</Tooltip>}>
|
||||
<ModalTrigger modal={
|
||||
<ModalBody
|
||||
title={this.props.title}
|
||||
editions={this.props.editions}
|
||||
currentUser={this.props.currentUser}
|
||||
handleSuccess={this.props.handleSuccess}
|
||||
>
|
||||
{this.props.children}
|
||||
</ModalBody>
|
||||
}>
|
||||
{this.props.button}
|
||||
</ModalTrigger>
|
||||
</OverlayTrigger>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
let ModalBody = React.createClass({
|
||||
mixins : [ModalMixin],
|
||||
|
||||
handleSuccess(){
|
||||
this.props.handleSuccess();
|
||||
this.props.onRequestHide();
|
||||
},
|
||||
renderChildren() {
|
||||
return ReactAddons.Children.map(this.props.children, (child, i) => {
|
||||
return ReactAddons.addons.cloneWithProps(child, {
|
||||
editions: this.props.editions,
|
||||
currentUser: this.props.currentUser,
|
||||
onRequestHide: this.onRequestHide,
|
||||
handleSuccess: this.handleSuccess
|
||||
});
|
||||
});
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Modal {...this.props} title={this.props.title}>
|
||||
<div className="modal-body">
|
||||
{this.renderChildren()}
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default ModalWrapper;
|
@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
let PaginationButton = React.createClass({
|
||||
@ -25,14 +27,14 @@ let PaginationButton = React.createClass({
|
||||
page -= 1;
|
||||
directionDisplay = (
|
||||
<span>
|
||||
<span aria-hidden="true">←</span> Previous
|
||||
<span aria-hidden="true">←</span> {getLangText('Previous')}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
page += 1;
|
||||
directionDisplay = (
|
||||
<span>
|
||||
Next <span aria-hidden="true">→</span>
|
||||
{getLangText('Next')} <span aria-hidden="true">→</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,140 @@
|
||||
import React from 'react';
|
||||
|
||||
import EditionListStore from '../../stores/edition_list_store';
|
||||
import EditionListActions from '../../actions/edition_list_actions';
|
||||
|
||||
import UserActions from '../../actions/user_actions';
|
||||
import UserStore from '../../stores/user_store';
|
||||
|
||||
import AclButton from '../acl_button';
|
||||
import PieceListBulkModalSelectedEditionsWidget from './piece_list_bulk_modal_selected_editions_widget';
|
||||
|
||||
let PieceListBulkModal = React.createClass({
|
||||
propTypes: {
|
||||
className: React.PropTypes.string
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return EditionListStore.getState();
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
UserActions.fetchCurrentUser();
|
||||
EditionListStore.listen(this.onChange);
|
||||
UserStore.listen(this.onChange);
|
||||
},
|
||||
|
||||
componentDidUnmount() {
|
||||
EditionListStore.unlisten(this.onChange);
|
||||
UserStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
fetchSelectedEditionList() {
|
||||
let selectedEditionList = [];
|
||||
|
||||
Object
|
||||
.keys(this.state.editionList)
|
||||
.forEach((key) => {
|
||||
let filteredEditionsForPiece = this.state.editionList[key].filter((edition) => edition.selected);
|
||||
selectedEditionList = selectedEditionList.concat(filteredEditionsForPiece);
|
||||
});
|
||||
|
||||
return selectedEditionList;
|
||||
},
|
||||
|
||||
intersectAcls(a, b) {
|
||||
return a.filter((val) => b.indexOf(val) > -1);
|
||||
},
|
||||
|
||||
getAvailableAcls() {
|
||||
let availableAcls = [];
|
||||
let selectedEditionList = this.fetchSelectedEditionList();
|
||||
|
||||
// If no edition has been selected, availableActions is empty
|
||||
// If only one edition has been selected, their actions are available
|
||||
// If more than one editions have been selected, their acl properties are intersected
|
||||
if(selectedEditionList.length >= 1) {
|
||||
availableAcls = selectedEditionList[0].acl;
|
||||
}
|
||||
if(selectedEditionList.length >= 2) {
|
||||
for(let i = 1; i < selectedEditionList.length; i++) {
|
||||
availableAcls = this.intersectAcls(availableAcls, selectedEditionList[i].acl);
|
||||
}
|
||||
}
|
||||
|
||||
return availableAcls;
|
||||
},
|
||||
|
||||
clearAllSelections() {
|
||||
EditionListActions.clearAllEditionSelections();
|
||||
},
|
||||
|
||||
handleSuccess(){
|
||||
|
||||
},
|
||||
|
||||
render() {
|
||||
let availableAcls = this.getAvailableAcls();
|
||||
let selectedEditions = this.fetchSelectedEditionList();
|
||||
|
||||
if(availableAcls.length > 0) {
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<div className="row no-margin">
|
||||
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12 piece-list-bulk-modal">
|
||||
<p></p>
|
||||
<div className="row">
|
||||
<div className="text-center">
|
||||
<PieceListBulkModalSelectedEditionsWidget
|
||||
numberOfSelectedEditions={this.fetchSelectedEditionList().length} />
|
||||
|
||||
<span
|
||||
className="piece-list-bulk-modal-clear-all"
|
||||
onClick={this.clearAllSelections}>clear all</span>
|
||||
</div>
|
||||
</div>
|
||||
<p></p>
|
||||
<div className="row">
|
||||
<div className="text-center">
|
||||
<AclButton
|
||||
availableAcls={availableAcls}
|
||||
action="transfer"
|
||||
editions={selectedEditions}
|
||||
currentUser={this.state.currentUser}
|
||||
handleSuccess={this.handleSuccess} />
|
||||
<AclButton
|
||||
availableAcls={availableAcls}
|
||||
action="consign"
|
||||
editions={selectedEditions}
|
||||
currentUser={this.state.currentUser}
|
||||
handleSuccess={this.handleSuccess} />
|
||||
<AclButton
|
||||
availableAcls={availableAcls}
|
||||
action="loan"
|
||||
editions={selectedEditions}
|
||||
currentUser={this.state.currentUser}
|
||||
handleSuccess={this.handleSuccess} />
|
||||
<AclButton
|
||||
availableAcls={availableAcls}
|
||||
action="share"
|
||||
editions={selectedEditions}
|
||||
currentUser={this.state.currentUser}
|
||||
handleSuccess={this.handleSuccess} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
export default PieceListBulkModal;
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
let PieceListToolbarSelectedEditionsWidget = React.createClass({
|
||||
let PieceListBulkModalSelectedEditionsWidget = React.createClass({
|
||||
propTypes: {
|
||||
numberOfSelectedEditions: React.PropTypes.number.isRequired
|
||||
},
|
||||
@ -14,4 +14,4 @@ let PieceListToolbarSelectedEditionsWidget = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
export default PieceListToolbarSelectedEditionsWidget;
|
||||
export default PieceListBulkModalSelectedEditionsWidget;
|
@ -1,18 +1,17 @@
|
||||
import React from 'react';
|
||||
|
||||
import EditionListStore from '../../stores/edition_list_store';
|
||||
import EditionListActions from '../../actions/edition_list_actions';
|
||||
import PieceListStore from '../../stores/piece_list_store';
|
||||
import PieceListActions from '../../actions/piece_list_actions';
|
||||
|
||||
import AclButton from '../acl_button';
|
||||
import PieceListToolbarSelectedEditionsWidget from './piece_list_toolbar_selected_editions_widget';
|
||||
import Input from 'react-bootstrap/lib/Input';
|
||||
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
|
||||
|
||||
import PieceListToolbarFilterWidgetFilter from './piece_list_toolbar_filter_widget';
|
||||
|
||||
let PieceListToolbar = React.createClass({
|
||||
propTypes: {
|
||||
className: React.PropTypes.string
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return EditionListStore.getState();
|
||||
return PieceListStore.getState();
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
@ -20,97 +19,38 @@ let PieceListToolbar = React.createClass({
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
EditionListStore.listen(this.onChange)
|
||||
PieceListStore.listen(this.onChange);
|
||||
},
|
||||
|
||||
componentDidUnmount() {
|
||||
EditionListStore.unlisten(this.onChange)
|
||||
PieceListStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
filterForSelected(edition) {
|
||||
return edition.selected;
|
||||
},
|
||||
|
||||
fetchSelectedEditionList() {
|
||||
let selectedEditionList = [];
|
||||
|
||||
Object
|
||||
.keys(this.state.editionList)
|
||||
.forEach((key) => {
|
||||
let filteredEditionsForPiece = this.state.editionList[key].filter(this.filterForSelected);
|
||||
selectedEditionList = selectedEditionList.concat(filteredEditionsForPiece);
|
||||
});
|
||||
|
||||
return selectedEditionList;
|
||||
},
|
||||
|
||||
intersectAcls(a, b) {
|
||||
return a.filter((val) => b.indexOf(val) > -1);
|
||||
},
|
||||
|
||||
bulk(action) {
|
||||
console.log(action);
|
||||
},
|
||||
|
||||
getAvailableAcls() {
|
||||
let availableAcls = [];
|
||||
let selectedEditionList = this.fetchSelectedEditionList();
|
||||
|
||||
// If no edition has been selected, availableActions is empty
|
||||
// If only one edition has been selected, their actions are available
|
||||
// If more than one editions have been selected, their acl properties are intersected
|
||||
if(selectedEditionList.length >= 1) {
|
||||
availableAcls = selectedEditionList[0].acl;
|
||||
}
|
||||
if(selectedEditionList.length >= 2) {
|
||||
for(let i = 1; i < selectedEditionList.length; i++) {
|
||||
availableAcls = this.intersectAcls(availableAcls, selectedEditionList[i].acl);
|
||||
}
|
||||
}
|
||||
|
||||
return availableAcls;
|
||||
},
|
||||
|
||||
clearAllSelections() {
|
||||
EditionListActions.clearAllEditionSelections();
|
||||
searchFor() {
|
||||
let searchTerm = this.refs.search.getInputDOMNode().value;
|
||||
PieceListActions.fetchPieceList(this.state.page, this.pageSize, searchTerm, this.state.orderBy, this.state.orderAsc);
|
||||
},
|
||||
|
||||
render() {
|
||||
let availableAcls = this.getAvailableAcls();
|
||||
let searchIcon = <Glyphicon glyph='search' />;
|
||||
|
||||
if(availableAcls.length > 0) {
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<div className="row no-margin">
|
||||
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12 piece-list-toolbar">
|
||||
<p></p>
|
||||
<div className="row">
|
||||
<div className="text-center">
|
||||
<PieceListToolbarSelectedEditionsWidget
|
||||
numberOfSelectedEditions={this.fetchSelectedEditionList().length} />
|
||||
|
||||
<span
|
||||
className="piece-list-toolbar-clear-all"
|
||||
onClick={this.clearAllSelections}>clear all</span>
|
||||
</div>
|
||||
</div>
|
||||
<p></p>
|
||||
<div className="row">
|
||||
<div className="text-center">
|
||||
<AclButton availableAcls={availableAcls} action="transfer" actionFunction={this.bulk} />
|
||||
<AclButton availableAcls={availableAcls} action="consign" actionFunction={this.bulk} />
|
||||
<AclButton availableAcls={availableAcls} action="share" actionFunction={this.bulk} />
|
||||
<AclButton availableAcls={availableAcls} action="loan" actionFunction={this.bulk} />
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<div className="row">
|
||||
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
|
||||
<div className="row">
|
||||
<div className="col-xs-12 col-md-12 col-md-5 col-lg-4 col-sm-offset-1 col-md-offset-2 col-lg-offset-2 clear-paddings">
|
||||
<div className="form-inline">
|
||||
<Input type='text' ref="search" placeholder="Search..." onChange={this.searchFor} addonAfter={searchIcon} />
|
||||
|
||||
{/*<PieceListToolbarFilterWidgetFilter />*/}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
|
||||
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
||||
|
||||
let PieceListToolbarFilterWidgetFilter = React.createClass({
|
||||
render() {
|
||||
let filterIcon = <Glyphicon glyph='filter' />;
|
||||
|
||||
return (
|
||||
<DropdownButton title={filterIcon}>
|
||||
<li style={{'textAlign': 'center'}}>
|
||||
<em>Show Pieces that:</em>
|
||||
</li>
|
||||
<MenuItem eventKey='1'>
|
||||
<div className="checkbox">
|
||||
I can transfer <input type="checkbox" />
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem eventKey='2'>
|
||||
<div className="checkbox">
|
||||
I can consign <input type="checkbox" />
|
||||
</div>
|
||||
</MenuItem>
|
||||
</DropdownButton>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default PieceListToolbarFilterWidgetFilter;
|
@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import TableColumnMixin from '../../mixins/table_column_mixin';
|
||||
import GeneralUtils from '../../utils/general_utils';
|
||||
import TableHeaderItem from './table_header_item';
|
||||
|
||||
import TableColumnContentModel from '../../models/table_column_content_model';
|
||||
|
@ -7,8 +7,10 @@ let TableItemAclFiltered = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
var availableAcls = ['consign', 'loan', 'transfer', 'view'];
|
||||
|
||||
let filteredAcls = this.props.content.filter((v) => {
|
||||
return v === 'consign' || v === 'loan' || v === 'transfer' || v === 'view';
|
||||
return availableAcls.indexOf(v) > -1;
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import ResourceViewer from './ascribe_media/resource_viewer';
|
||||
import TransferModalButton from './ascribe_modal/modal_transfer';
|
||||
import ShareModalButton from './ascribe_modal/modal_share';
|
||||
|
||||
import EditionActions from '../actions/edition_actions'
|
||||
import AclButton from './acl_button'
|
||||
|
||||
/**
|
||||
* This is the component that implements display-specific functionality
|
||||
@ -49,6 +50,9 @@ let EditionHeader = React.createClass({
|
||||
});
|
||||
|
||||
let EditionDetails = React.createClass({
|
||||
handleSuccess(){
|
||||
EditionActions.fetchOne(this.props.edition.id);
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<div className="ascribe-detail-header">
|
||||
@ -57,8 +61,20 @@ let EditionDetails = React.createClass({
|
||||
<EditionDetailProperty label="id" value={ this.props.edition.bitcoin_id } />
|
||||
<EditionDetailProperty label="owner" value={ this.props.edition.owner } />
|
||||
<br/>
|
||||
<TransferModalButton edition={ this.props.edition } currentUser={ this.props.currentUser }/>
|
||||
<ShareModalButton edition={ this.props.edition } currentUser={ this.props.currentUser }/>
|
||||
<AclButton
|
||||
availableAcls={["transfer"]}
|
||||
action="transfer"
|
||||
editions={[this.props.edition]}
|
||||
currentUser={this.props.currentUser}
|
||||
handleSuccess={this.handleSuccess}
|
||||
/>
|
||||
<AclButton
|
||||
availableAcls={["consign"]}
|
||||
action="consign"
|
||||
editions={[this.props.edition]}
|
||||
currentUser={this.props.currentUser}
|
||||
handleSuccess={this.handleSuccess}
|
||||
/>
|
||||
<hr/>
|
||||
</div>
|
||||
);
|
||||
|
@ -27,7 +27,6 @@ let EditionContainer = React.createClass({
|
||||
UserActions.fetchCurrentUser();
|
||||
UserStore.listen(this.onChange);
|
||||
},
|
||||
|
||||
componentDidUnmount() {
|
||||
EditionStore.unlisten(this.onChange);
|
||||
UserStore.unlisten(this.onChange);
|
||||
|
@ -5,6 +5,14 @@ import AltContainer from 'alt/AltContainer';
|
||||
import UserActions from '../actions/user_actions';
|
||||
import UserStore from '../stores/user_store';
|
||||
|
||||
import Nav from 'react-bootstrap/lib/Nav';
|
||||
import Navbar from 'react-bootstrap/lib/Navbar';
|
||||
import NavItem from 'react-bootstrap/lib/NavItem';
|
||||
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
||||
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
let Header = React.createClass({
|
||||
@ -24,40 +32,24 @@ let Header = React.createClass({
|
||||
|
||||
render() {
|
||||
return (
|
||||
<nav className="navbar navbar-default">
|
||||
<div className="container">
|
||||
<div className="navbar-header">
|
||||
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
||||
<span className="sr-only">Toggle navigation</span>
|
||||
<span className="icon-bar"></span>
|
||||
<span className="icon-bar"></span>
|
||||
<span className="icon-bar"></span>
|
||||
</button>
|
||||
<a className="navbar-brand" href="#">
|
||||
<span>ascribe </span>
|
||||
<span className="glyph-ascribe-spool-chunked ascribe-color"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="navbar" className="navbar-collapse collapse">
|
||||
<ul className="nav navbar-nav navbar-left">
|
||||
</ul>
|
||||
<ul className="nav navbar-nav navbar-right">
|
||||
<li className="dropdown">
|
||||
<a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{this.state.currentUser.username} <span className="caret"></span></a>
|
||||
<ul className="dropdown-menu" role="menu">
|
||||
<li><a href="/art/faq/">Account Settings</a></li>
|
||||
<li className="divider"></li>
|
||||
<li><a href="/art/faq/">FAQ</a></li>
|
||||
<li><a href="/art/terms/">Terms of Service</a></li>
|
||||
<li className="divider"></li>
|
||||
<li><a href="/api/users/logout/">Log out</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<Navbar>
|
||||
<Nav>
|
||||
<a className="navbar-brand" href="#">
|
||||
<span>ascribe </span>
|
||||
<span className="glyph-ascribe-spool-chunked ascribe-color"></span>
|
||||
</a>
|
||||
</Nav>
|
||||
<Nav right>
|
||||
<DropdownButton eventKey={3} title={this.state.currentUser.username}>
|
||||
<MenuItem eventKey="1" href="/art/account_settings/">{getLangText('Account Settings')}</MenuItem>
|
||||
<li className="divider"></li>
|
||||
<MenuItem eventKey="2" href="/art/faq/">{getLangText('FAQ')}</MenuItem>
|
||||
<MenuItem eventKey="3" href="/art/terms/">{getLangText('Terms of Service')}</MenuItem>
|
||||
<MenuItem divider />
|
||||
<MenuItem eventKey="4" href="/api/users/logout/">{getLangText('Log out')}</MenuItem>
|
||||
</DropdownButton>
|
||||
</Nav>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -10,6 +10,7 @@ import AccordionListItemTableEditions from './ascribe_accordion_list/accordion_l
|
||||
|
||||
import Pagination from './ascribe_pagination/pagination';
|
||||
|
||||
import PieceListBulkModal from './ascribe_piece_list_bulk_modal/piece_list_bulk_modal';
|
||||
import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar';
|
||||
|
||||
|
||||
@ -51,6 +52,7 @@ let PieceList = React.createClass({
|
||||
return (
|
||||
<div>
|
||||
<PieceListToolbar className="ascribe-piece-list-toolbar" />
|
||||
<PieceListBulkModal className="ascribe-piece-list-bulk-modal" />
|
||||
<AccordionList
|
||||
className="ascribe-accordion-list"
|
||||
changeOrder={this.accordionChangeOrder}
|
||||
@ -68,7 +70,8 @@ let PieceList = React.createClass({
|
||||
key={i}>
|
||||
<AccordionListItemTableEditions
|
||||
className="ascribe-accordion-list-item-table col-xs-6 col-sm-6 col-md-6 col-lg-6 col-xs-offset-3 col-sm-offset-3 col-md-offset-3 col-lg-offset-3"
|
||||
parentId={item.id}
|
||||
parentId={item.id}
|
||||
show={item.show}
|
||||
numOfEditions={item.num_editions}/>
|
||||
</AccordionListItem>
|
||||
);
|
||||
|
@ -3,14 +3,15 @@ import AppConstants from './application_constants';
|
||||
let apiUrls = {
|
||||
'ownership_shares_mail' : AppConstants.baseUrl + 'ownership/shares/mail/',
|
||||
'ownership_transfers' : AppConstants.baseUrl + 'ownership/transfers/',
|
||||
|
||||
'user': AppConstants.baseUrl + 'users/',
|
||||
|
||||
'pieces_list': AppConstants.baseUrl + 'pieces/',
|
||||
'piece': AppConstants.baseUrl + 'pieces/${piece_id}',
|
||||
|
||||
'edition': AppConstants.baseUrl + 'editions/${bitcoin_id}/',
|
||||
'editions_list': AppConstants.baseUrl + 'pieces/${piece_id}/editions/'
|
||||
'editions_list': AppConstants.baseUrl + 'pieces/${piece_id}/editions/',
|
||||
'ownership_loans' : AppConstants.baseUrl + 'ownership/loans/',
|
||||
'ownership_consigns' : AppConstants.baseUrl + 'ownership/consigns/',
|
||||
'ownership_unconsigns' : AppConstants.baseUrl + 'ownership/unconsigns/',
|
||||
'ownership_unconsigns_request' : AppConstants.baseUrl + 'ownership/unconsigns/request/'
|
||||
};
|
||||
|
||||
export default apiUrls;
|
||||
|
32
js/constants/languages.js
Normal file
32
js/constants/languages.js
Normal file
@ -0,0 +1,32 @@
|
||||
const languages = {
|
||||
'en-US': {
|
||||
'Bitcoin Address': 'Bitcoin Address',
|
||||
'Actions': 'Actions',
|
||||
'Hide': 'Hide',
|
||||
'Show the edition': 'Show the edition',
|
||||
'Show all %d Editions': 'Show all %d Editions',
|
||||
'by %s': 'by %s',
|
||||
'Account Settings': 'Account Settings',
|
||||
'FAQ': 'FAQ',
|
||||
'Terms of Service': 'Terms of Service',
|
||||
'Log out': 'Log out',
|
||||
'Previous': 'Previous',
|
||||
'Next': 'Next'
|
||||
},
|
||||
'de': {
|
||||
'Bitcoin Address': 'Bitcoin Adresse',
|
||||
'Actions': 'Aktionen',
|
||||
'Hide': 'Verstecke',
|
||||
'Show the edition': 'Zeige die Edition',
|
||||
'Show all %d Editions': 'Zeige alle %d Editionen an',
|
||||
'by %s': 'von %s',
|
||||
'Account Settings': 'Kontoeinstellungen',
|
||||
'FAQ': 'Fragen & Antworten',
|
||||
'Terms of Service': 'AGB',
|
||||
'Log out': 'Log out',
|
||||
'Previous': 'Zurück',
|
||||
'Next': 'Weiter'
|
||||
}
|
||||
};
|
||||
|
||||
export default languages;
|
@ -1,7 +1,6 @@
|
||||
import fetch from '../utils/fetch';
|
||||
|
||||
import AppConstants from '../constants/application_constants';
|
||||
import FetchApiUtils from '../utils/fetch_api_utils';
|
||||
|
||||
|
||||
let EditionFetcher = {
|
||||
|
24
js/fetchers/ownership_fetcher.js
Normal file
24
js/fetchers/ownership_fetcher.js
Normal file
@ -0,0 +1,24 @@
|
||||
import fetch from 'isomorphic-fetch';
|
||||
|
||||
import AppConstants from '../constants/application_constants';
|
||||
import FetchApiUtils from '../utils/fetch_api_utils';
|
||||
|
||||
|
||||
let OwnershipFetcher = {
|
||||
/**
|
||||
* Fetch one user from the API.
|
||||
* If no arg is supplied, load the current user
|
||||
*
|
||||
*/
|
||||
fetchLoanContract(email) {
|
||||
return fetch(AppConstants.baseUrl + 'ownership/loans/contract/?loanee=' + email, {
|
||||
headers: {
|
||||
'Authorization': 'Basic ' + AppConstants.debugCredentialBase64
|
||||
}
|
||||
}).then(
|
||||
(res) => res.json()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default OwnershipFetcher;
|
@ -1,5 +1,5 @@
|
||||
import AppConstants from '../constants/application_constants';
|
||||
import FetchApiUtils from '../utils/fetch_api_utils';
|
||||
import { generateOrderingQueryParams } from '../utils/fetch_api_utils';
|
||||
import fetch from '../utils/fetch';
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ let PieceListFetcher = {
|
||||
* Can be called with all supplied queryparams the API.
|
||||
*/
|
||||
fetch(page, pageSize, search, orderBy, orderAsc) {
|
||||
let ordering = FetchApiUtils.generateOrderingQueryParams(orderBy, orderAsc);
|
||||
let ordering = generateOrderingQueryParams(orderBy, orderAsc);
|
||||
return fetch.get('pieces_list', { page, pageSize, search, ordering });
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,6 @@
|
||||
import fetch from '../utils/fetch';
|
||||
|
||||
import AppConstants from '../constants/application_constants';
|
||||
import FetchApiUtils from '../utils/fetch_api_utils';
|
||||
|
||||
|
||||
let UserFetcher = {
|
||||
|
@ -5,12 +5,15 @@ let AlertMixin = {
|
||||
setAlerts(errors){
|
||||
let alerts = errors.map(
|
||||
function(error) {
|
||||
let key = error + this.state.retry;
|
||||
return <AlertDismissable error={error} key={key}/>;
|
||||
return <AlertDismissable error={error} key={error}/>;
|
||||
}.bind(this)
|
||||
);
|
||||
this.setState({alerts: alerts, retry: this.state.retry + 1});
|
||||
this.setState({alerts: alerts});
|
||||
},
|
||||
clearAlerts(){
|
||||
this.setState({alerts: null});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export default AlertMixin;
|
@ -9,6 +9,7 @@ export const FormMixin = {
|
||||
return {
|
||||
submitted: false
|
||||
, status: null
|
||||
, errors: []
|
||||
}
|
||||
},
|
||||
|
||||
@ -17,28 +18,46 @@ export const FormMixin = {
|
||||
this.setState({submitted: true});
|
||||
fetch
|
||||
.post(this.url(), { body: this.getFormData() })
|
||||
.then(response => { this.props.onRequestHide(); })
|
||||
.then(response => { this.props.handleSuccess(); })
|
||||
.catch(this.handleError);
|
||||
|
||||
},
|
||||
|
||||
handleError(err){
|
||||
if (err.json) {
|
||||
for (var input in errors){
|
||||
for (var input in err.json.errors){
|
||||
if (this.refs && this.refs[input] && this.refs[input].state) {
|
||||
this.refs[input].setAlerts(errors[input]);
|
||||
this.refs[input].setAlerts( err.json.errors[input]);
|
||||
} else {
|
||||
this.setState({errors: this.state.errors.concat(err.json.errors[input])});
|
||||
}
|
||||
}
|
||||
this.setState({submitted: false});
|
||||
} else {
|
||||
this.setState({submitted: false, status: response.status});
|
||||
}
|
||||
else{
|
||||
this.setState({errors: ['Something went wrong, please try again later"']});
|
||||
}
|
||||
this.setState({submitted: false});
|
||||
},
|
||||
|
||||
getBitcoinIds(){
|
||||
return this.props.editions.map(function(edition){
|
||||
return edition.bitcoin_id
|
||||
})
|
||||
},
|
||||
|
||||
getTitlesString(){
|
||||
return this.props.editions.map(function(edition){
|
||||
return '- \"' + edition.title + ', edition ' + edition.edition_number + '\"\n'
|
||||
})
|
||||
},
|
||||
|
||||
render(){
|
||||
let alert = null;
|
||||
if (this.state.status >= 500){
|
||||
alert = <AlertDismissable error="Something went wrong, please try again later"/>;
|
||||
if (this.state.errors.length > 0){
|
||||
alert = this.state.errors.map(
|
||||
function(error) {
|
||||
return <AlertDismissable error={error} key={error}/>;
|
||||
}.bind(this)
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
|
11
js/mixins/modal_mixin.js
Normal file
11
js/mixins/modal_mixin.js
Normal file
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
let ModalMixin = {
|
||||
onRequestHide(e){
|
||||
if (e)
|
||||
e.preventDefault();
|
||||
this.props.onRequestHide();
|
||||
}
|
||||
};
|
||||
|
||||
export default ModalMixin;
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import GeneralUtils from '../utils/general_utils';
|
||||
import { sumNumList } from '../utils/general_utils';
|
||||
|
||||
let TableColumnMixin = {
|
||||
/**
|
||||
@ -11,7 +11,7 @@ let TableColumnMixin = {
|
||||
let bootstrapClasses = ['col-xs-', 'col-sm-', 'col-md-', 'col-lg-'];
|
||||
|
||||
let listOfRowValues = list.map((column) => column.rowWidth );
|
||||
let numOfUsedColumns = GeneralUtils.sumNumList(listOfRowValues);
|
||||
let numOfUsedColumns = sumNumList(listOfRowValues);
|
||||
|
||||
if(numOfUsedColumns > numOfColumns) {
|
||||
throw new Error('This table has only ' + numOfColumns + ' columns to assign. You defined ' + numOfUsedColumns + '. Change this in the columnMap you\'re passing to the table.')
|
||||
|
@ -1,11 +1,11 @@
|
||||
import alt from '../alt';
|
||||
import EditionAction from '../actions/edition_actions';
|
||||
import EditionActions from '../actions/edition_actions';
|
||||
|
||||
|
||||
class EditionStore {
|
||||
constructor() {
|
||||
this.edition = {};
|
||||
this.bindActions(EditionAction);
|
||||
this.bindActions(EditionActions);
|
||||
}
|
||||
|
||||
onUpdateEdition(edition) {
|
||||
|
@ -24,6 +24,19 @@ class PieceListStore {
|
||||
this.bindActions(PieceListActions);
|
||||
}
|
||||
|
||||
onShowEditionList(pieceId) {
|
||||
this.pieceList
|
||||
.forEach((piece) => {
|
||||
if(piece.id === pieceId) {
|
||||
if(piece.show) {
|
||||
piece.show = false;
|
||||
} else {
|
||||
piece.show = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onUpdatePieceList({ page, pageSize, search, pieceList, orderBy, orderAsc, pieceListCount }) {
|
||||
this.page = page;
|
||||
this.pageSize = pageSize;
|
||||
|
@ -4,13 +4,13 @@ import UserAction from '../actions/user_actions';
|
||||
|
||||
class UserStore{
|
||||
constructor() {
|
||||
this.currentUser = {}
|
||||
this.currentUser = {};
|
||||
this.bindActions(UserAction);
|
||||
}
|
||||
|
||||
onUpdateCurrentUser(user) {
|
||||
this.currentUser = user;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default alt.createStore(UserStore);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { default as _fetch } from 'isomorphic-fetch';
|
||||
import FetchApiUtils from '../utils/fetch_api_utils';
|
||||
|
||||
import { argsToQueryParams } from '../utils/fetch_api_utils';
|
||||
|
||||
|
||||
class UrlMapError extends Error {};
|
||||
@ -66,7 +67,7 @@ class Fetch {
|
||||
});
|
||||
|
||||
if (attachParamsToQuery && params && Object.keys(params).length > 0) {
|
||||
newUrl += FetchApiUtils.argsToQueryParams(params);
|
||||
newUrl += argsToQueryParams(params);
|
||||
}
|
||||
|
||||
return newUrl;
|
||||
|
@ -1,8 +1,6 @@
|
||||
import GeneralUtils from './general_utils';
|
||||
|
||||
import { sanitize } from './general_utils';
|
||||
|
||||
// TODO: Create Unittests that test all functions
|
||||
let FetchApiUtils = {
|
||||
|
||||
/**
|
||||
* Takes a key-value object of this form:
|
||||
@ -20,48 +18,45 @@ let FetchApiUtils = {
|
||||
* CamelCase gets converted to snake_case!
|
||||
*
|
||||
*/
|
||||
argsToQueryParams(obj) {
|
||||
export function argsToQueryParams(obj) {
|
||||
|
||||
obj = GeneralUtils.sanitize(obj);
|
||||
obj = sanitize(obj);
|
||||
|
||||
return Object
|
||||
.keys(obj)
|
||||
.map((key, i) => {
|
||||
let s = '';
|
||||
return Object
|
||||
.keys(obj)
|
||||
.map((key, i) => {
|
||||
let s = '';
|
||||
|
||||
if(i === 0) {
|
||||
s += '?';
|
||||
} else {
|
||||
s += '&';
|
||||
}
|
||||
if(i === 0) {
|
||||
s += '?';
|
||||
} else {
|
||||
s += '&';
|
||||
}
|
||||
|
||||
let snakeCaseKey = key.replace(/[A-Z]/, (match) => '_' + match.toLowerCase());
|
||||
let snakeCaseKey = key.replace(/[A-Z]/, (match) => '_' + match.toLowerCase());
|
||||
|
||||
return s + snakeCaseKey + '=' + encodeURIComponent(obj[key]);
|
||||
})
|
||||
.join('');
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes a string and a boolean and generates a string query parameter for
|
||||
* an API call.
|
||||
*/
|
||||
generateOrderingQueryParams(orderBy, orderAsc) {
|
||||
let interpolation = '';
|
||||
|
||||
if(!orderAsc) {
|
||||
interpolation += '-';
|
||||
}
|
||||
|
||||
return interpolation + orderBy;
|
||||
},
|
||||
|
||||
status(response) {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response
|
||||
}
|
||||
throw new Error(response.json())
|
||||
}
|
||||
return s + snakeCaseKey + '=' + encodeURIComponent(obj[key]);
|
||||
})
|
||||
.join('');
|
||||
};
|
||||
|
||||
export default FetchApiUtils;
|
||||
/**
|
||||
* Takes a string and a boolean and generates a string query parameter for
|
||||
* an API call.
|
||||
*/
|
||||
export function generateOrderingQueryParams(orderBy, orderAsc) {
|
||||
let interpolation = '';
|
||||
|
||||
if(!orderAsc) {
|
||||
interpolation += '-';
|
||||
}
|
||||
|
||||
return interpolation + orderBy;
|
||||
};
|
||||
|
||||
export function status(response) {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response
|
||||
}
|
||||
throw new Error(response.json())
|
||||
};
|
||||
|
@ -1,41 +1,63 @@
|
||||
// TODO: Create Unittests that test all functions
|
||||
|
||||
let GeneralUtils = {
|
||||
/**
|
||||
* Removes undefined and null values from an key-value object.
|
||||
*/
|
||||
sanitize(obj) {
|
||||
Object
|
||||
.keys(obj)
|
||||
.map((key) => {
|
||||
// By matching null with a double equal, we can match undefined and null
|
||||
// http://stackoverflow.com/a/15992131
|
||||
if(obj[key] == null || obj[key] === '') {
|
||||
delete obj[key];
|
||||
}
|
||||
});
|
||||
|
||||
return obj;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the values of an object.
|
||||
*/
|
||||
valuesOfObject(obj) {
|
||||
return Object
|
||||
.keys(obj)
|
||||
.map(key => obj[key]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sums up a list of numbers. Like a Epsilon-math-kinda-sum...
|
||||
*/
|
||||
sumNumList(l) {
|
||||
let sum = 0;
|
||||
l.forEach((num) => sum += parseFloat(num) || 0);
|
||||
return sum;
|
||||
}
|
||||
export function sanitize(obj) {
|
||||
Object
|
||||
.keys(obj)
|
||||
.map((key) => {
|
||||
// By matching null with a double equal, we can match undefined and null
|
||||
// http://stackoverflow.com/a/15992131
|
||||
if(obj[key] == null || obj[key] === '') {
|
||||
delete obj[key];
|
||||
}
|
||||
});
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
export default GeneralUtils;
|
||||
/**
|
||||
* Returns the values of an object.
|
||||
*/
|
||||
export function valuesOfObject(obj) {
|
||||
return Object
|
||||
.keys(obj)
|
||||
.map(key => obj[key]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sums up a list of numbers. Like a Epsilon-math-kinda-sum...
|
||||
*/
|
||||
export function sumNumList(l) {
|
||||
let sum = 0;
|
||||
l.forEach((num) => sum += parseFloat(num) || 0);
|
||||
return sum;
|
||||
};
|
||||
|
||||
/*
|
||||
Taken from http://stackoverflow.com/a/4795914/1263876
|
||||
Behaves like C's format string function
|
||||
*/
|
||||
export function formatText() {
|
||||
var args = arguments,
|
||||
string = args[0],
|
||||
i = 1;
|
||||
return string.replace(/%((%)|s|d)/g, function (m) {
|
||||
// m is the matched format, e.g. %s, %d
|
||||
var val = null;
|
||||
if (m[2]) {
|
||||
val = m[2];
|
||||
} else {
|
||||
val = args[i];
|
||||
// A switch statement so that the formatter can be extended. Default is %s
|
||||
switch (m) {
|
||||
case '%d':
|
||||
val = parseFloat(val);
|
||||
if (isNaN(val)) {
|
||||
val = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return val;
|
||||
});
|
||||
};
|
||||
|
30
js/utils/lang_utils.js
Normal file
30
js/utils/lang_utils.js
Normal file
@ -0,0 +1,30 @@
|
||||
import languages from '../constants/languages';
|
||||
|
||||
import { formatText } from './general_utils';
|
||||
|
||||
/**
|
||||
* Is used to translate strings to another language. Basically can be used with C's string format method.
|
||||
* @param {string} s The string you want to translate
|
||||
* @param {array} args An array of arguments (essentially JavaScript's this.arguments) that can be used to substitute digits and other strings
|
||||
* @return {string} The formated string
|
||||
*/
|
||||
export function getLangText(s, ...args) {
|
||||
let lang = navigator.language || navigator.userLanguage;
|
||||
// this is just for testing, as changing the navigator.language wasn't possible
|
||||
lang = 'de';
|
||||
try {
|
||||
if(lang in languages) {
|
||||
return formatText(languages[lang][s], args);
|
||||
} else {
|
||||
// just use the english language
|
||||
return formatText(languages['en-US'][s], args);
|
||||
}
|
||||
} catch(err) {
|
||||
if(!(s in languages[lang])) {
|
||||
console.error(new Error('Language-string is not in constants file. Add: "' + s + '" to the "' + lang + '" language file.'));
|
||||
} else {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
@ -36,7 +36,9 @@
|
||||
"react": "^0.13.2",
|
||||
"react-bootstrap": "~0.22.6",
|
||||
"react-router": "^0.13.3",
|
||||
"uglifyjs": "^2.4.10"
|
||||
"uglifyjs": "^2.4.10",
|
||||
"react-bootstrap": "~0.22.6",
|
||||
"react-datepicker": "~0.8.0"
|
||||
},
|
||||
"jest": {
|
||||
"scriptPreprocessor": "node_modules/babel-jest",
|
||||
|
@ -1,4 +1,4 @@
|
||||
.ascribe-piece-list-toolbar {
|
||||
.ascribe-piece-list-bulk-modal {
|
||||
position: fixed;
|
||||
top:0;
|
||||
width:1170px;
|
||||
@ -14,7 +14,7 @@
|
||||
z-index:9999;
|
||||
}
|
||||
|
||||
.piece-list-toolbar-clear-all {
|
||||
.piece-list-bulk-modal-clear-all {
|
||||
text-decoration: underline;
|
||||
cursor:pointer;
|
||||
}
|
3
sass/ascribe_piece_list_toolbar.scss
Normal file
3
sass/ascribe_piece_list_toolbar.scss
Normal file
@ -0,0 +1,3 @@
|
||||
.ascribe-piece-list-toolbar {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
// If you import a new .scss file, make sure to restart gulp
|
||||
// otherwise it will not be included
|
||||
@import 'variables';
|
||||
@import 'ascribe-variables';
|
||||
@import 'ascribe_variables';
|
||||
@import '../node_modules/bootstrap-sass/assets/stylesheets/bootstrap';
|
||||
@import '../node_modules/react-datepicker/dist/react-datepicker';
|
||||
@import './ascribe-fonts/style';
|
||||
@import './ascribe-fonts/ascribe-fonts';
|
||||
@import 'ascribe-accordion_list';
|
||||
@import 'ascribe-piece-list-toolbar';
|
||||
@import 'ascribe_accordion_list';
|
||||
@import 'ascribe_piece_list_bulk_modal';
|
||||
@import 'ascribe_piece_list_toolbar';
|
||||
@import 'offset_right';
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
@ -15,10 +18,10 @@
|
||||
.navbar-default {
|
||||
border-left:0;
|
||||
border-right:0;
|
||||
margin-bottom: 3em;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.clear-margins-and-paddings {
|
||||
.clear-paddings {
|
||||
padding-left:0;
|
||||
padding-right:0;
|
||||
}
|
||||
@ -162,7 +165,8 @@
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.input-text-ascribe {
|
||||
.input-text-ascribe,
|
||||
.datepicker__input {
|
||||
border-bottom: 1px solid black;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
@ -176,6 +180,14 @@
|
||||
height: 13em !important;
|
||||
}
|
||||
|
||||
.input-checkbox-ascribe {
|
||||
text-align: left;
|
||||
line-height: 1.6;
|
||||
width: 90%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* columns of same height styles */
|
||||
/* http://www.minimit.com/articles/solutions-tutorials/bootstrap-3-responsive-columns-of-same-height */
|
||||
.row-full-height {
|
||||
@ -232,4 +244,4 @@
|
||||
|
||||
.col-bottom {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
|
164
sass/offset_right.scss
Normal file
164
sass/offset_right.scss
Normal file
@ -0,0 +1,164 @@
|
||||
/* Taken from: http://stackoverflow.com/a/27501063/1263876 */
|
||||
|
||||
.col-xs-offset-right-12 {
|
||||
margin-right: 100%;
|
||||
}
|
||||
.col-xs-offset-right-11 {
|
||||
margin-right: 91.66666667%;
|
||||
}
|
||||
.col-xs-offset-right-10 {
|
||||
margin-right: 83.33333333%;
|
||||
}
|
||||
.col-xs-offset-right-9 {
|
||||
margin-right: 75%;
|
||||
}
|
||||
.col-xs-offset-right-8 {
|
||||
margin-right: 66.66666667%;
|
||||
}
|
||||
.col-xs-offset-right-7 {
|
||||
margin-right: 58.33333333%;
|
||||
}
|
||||
.col-xs-offset-right-6 {
|
||||
margin-right: 50%;
|
||||
}
|
||||
.col-xs-offset-right-5 {
|
||||
margin-right: 41.66666667%;
|
||||
}
|
||||
.col-xs-offset-right-4 {
|
||||
margin-right: 33.33333333%;
|
||||
}
|
||||
.col-xs-offset-right-3 {
|
||||
margin-right: 25%;
|
||||
}
|
||||
.col-xs-offset-right-2 {
|
||||
margin-right: 16.66666667%;
|
||||
}
|
||||
.col-xs-offset-right-1 {
|
||||
margin-right: 8.33333333%;
|
||||
}
|
||||
.col-xs-offset-right-0 {
|
||||
margin-right: 0;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.col-sm-offset-right-12 {
|
||||
margin-right: 100%;
|
||||
}
|
||||
.col-sm-offset-right-11 {
|
||||
margin-right: 91.66666667%;
|
||||
}
|
||||
.col-sm-offset-right-10 {
|
||||
margin-right: 83.33333333%;
|
||||
}
|
||||
.col-sm-offset-right-9 {
|
||||
margin-right: 75%;
|
||||
}
|
||||
.col-sm-offset-right-8 {
|
||||
margin-right: 66.66666667%;
|
||||
}
|
||||
.col-sm-offset-right-7 {
|
||||
margin-right: 58.33333333%;
|
||||
}
|
||||
.col-sm-offset-right-6 {
|
||||
margin-right: 50%;
|
||||
}
|
||||
.col-sm-offset-right-5 {
|
||||
margin-right: 41.66666667%;
|
||||
}
|
||||
.col-sm-offset-right-4 {
|
||||
margin-right: 33.33333333%;
|
||||
}
|
||||
.col-sm-offset-right-3 {
|
||||
margin-right: 25%;
|
||||
}
|
||||
.col-sm-offset-right-2 {
|
||||
margin-right: 16.66666667%;
|
||||
}
|
||||
.col-sm-offset-right-1 {
|
||||
margin-right: 8.33333333%;
|
||||
}
|
||||
.col-sm-offset-right-0 {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.col-md-offset-right-12 {
|
||||
margin-right: 100%;
|
||||
}
|
||||
.col-md-offset-right-11 {
|
||||
margin-right: 91.66666667%;
|
||||
}
|
||||
.col-md-offset-right-10 {
|
||||
margin-right: 83.33333333%;
|
||||
}
|
||||
.col-md-offset-right-9 {
|
||||
margin-right: 75%;
|
||||
}
|
||||
.col-md-offset-right-8 {
|
||||
margin-right: 66.66666667%;
|
||||
}
|
||||
.col-md-offset-right-7 {
|
||||
margin-right: 58.33333333%;
|
||||
}
|
||||
.col-md-offset-right-6 {
|
||||
margin-right: 50%;
|
||||
}
|
||||
.col-md-offset-right-5 {
|
||||
margin-right: 41.66666667%;
|
||||
}
|
||||
.col-md-offset-right-4 {
|
||||
margin-right: 33.33333333%;
|
||||
}
|
||||
.col-md-offset-right-3 {
|
||||
margin-right: 25%;
|
||||
}
|
||||
.col-md-offset-right-2 {
|
||||
margin-right: 16.66666667%;
|
||||
}
|
||||
.col-md-offset-right-1 {
|
||||
margin-right: 8.33333333%;
|
||||
}
|
||||
.col-md-offset-right-0 {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.col-lg-offset-right-12 {
|
||||
margin-right: 100%;
|
||||
}
|
||||
.col-lg-offset-right-11 {
|
||||
margin-right: 91.66666667%;
|
||||
}
|
||||
.col-lg-offset-right-10 {
|
||||
margin-right: 83.33333333%;
|
||||
}
|
||||
.col-lg-offset-right-9 {
|
||||
margin-right: 75%;
|
||||
}
|
||||
.col-lg-offset-right-8 {
|
||||
margin-right: 66.66666667%;
|
||||
}
|
||||
.col-lg-offset-right-7 {
|
||||
margin-right: 58.33333333%;
|
||||
}
|
||||
.col-lg-offset-right-6 {
|
||||
margin-right: 50%;
|
||||
}
|
||||
.col-lg-offset-right-5 {
|
||||
margin-right: 41.66666667%;
|
||||
}
|
||||
.col-lg-offset-right-4 {
|
||||
margin-right: 33.33333333%;
|
||||
}
|
||||
.col-lg-offset-right-3 {
|
||||
margin-right: 25%;
|
||||
}
|
||||
.col-lg-offset-right-2 {
|
||||
margin-right: 16.66666667%;
|
||||
}
|
||||
.col-lg-offset-right-1 {
|
||||
margin-right: 8.33333333%;
|
||||
}
|
||||
.col-lg-offset-right-0 {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user