diff --git a/index.html b/index.html index 8b61e4b0..8d16b099 100644 --- a/index.html +++ b/index.html @@ -5,11 +5,11 @@ ascribe - + -
+
diff --git a/js/actions/piece_actions.js b/js/actions/piece_actions.js index d657539b..274ef458 100644 --- a/js/actions/piece_actions.js +++ b/js/actions/piece_actions.js @@ -15,7 +15,7 @@ class PieceActions { this.actions.updatePiece(res.piece); }) .catch((err) => { - console.log(err); + console.log(err); }); } } diff --git a/js/actions/piece_list_actions.js b/js/actions/piece_list_actions.js index 29c8413f..0105ff06 100644 --- a/js/actions/piece_list_actions.js +++ b/js/actions/piece_list_actions.js @@ -6,7 +6,8 @@ import PieceListFetcher from '../fetchers/piece_list_fetcher'; class PieceListActions { constructor() { this.generateActions( - 'updatePieceList' + 'updatePieceList', + 'showEditionList' ); } diff --git a/js/components/acl_button.js b/js/components/acl_button.js index 6d4f1756..5f2a0708 100644 --- a/js/components/acl_button.js +++ b/js/components/acl_button.js @@ -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: + } + } + else if (this.props.action == 'transfer') { + return { + title: "Transfer artwork", + tooltip: "Transfer the ownership of the artwork", + form: + } + } + else if (this.props.action == 'loan'){ + return { + title: "Loan artwork", + tooltip: "Loan your artwork for a limited period of time", + form: } + } + else if (this.props.action == 'share'){ + return { + title: "Share artwork", + tooltip: "Share the artwork", + form: } + } }, - render() { let shouldDisplay = this.props.availableAcls.indexOf(this.props.action) > -1; + let aclProps = this.actionProperties(); return ( - + + {this.props.action.toUpperCase()} + + } + currentUser={ this.props.currentUser } + editions={ this.props.editions } + handleSuccess={ this.props.handleSuccess } + title={ aclProps.title } + tooltip={ aclProps.tooltip }> + { aclProps.form } + ); } }); diff --git a/js/components/ascribe_accordion_list/accordion_list_item.js b/js/components/ascribe_accordion_list/accordion_list_item.js index f932b12b..e9bb8742 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item.js +++ b/js/components/ascribe_accordion_list/accordion_list_item.js @@ -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({

{this.props.content.title}

-

by {this.props.content.artist_name}

+

{getLangText('by %s', this.props.content.artist_name)}

diff --git a/js/components/ascribe_accordion_list/accordion_list_item_table.js b/js/components/ascribe_accordion_list/accordion_list_item_table.js index 9ddb3308..1cf410df 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_table.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_table.js @@ -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 (
- + {this.props.children} ); } else { return (
- + {this.props.children}
); } } }); -let AccordionListItemTableToggle = React.createClass({ - propTypes: { - className: React.PropTypes.string, - onClick: React.PropTypes.func, - show: React.PropTypes.bool, - numOfTableItems: React.PropTypes.number - }, - - render() { - return ( - - {this.props.show ? 'Hide all ' + this.props.numOfTableItems + ' Editions' : 'Show all ' + this.props.numOfTableItems + ' Editions'} - - ); - } -}); - export default AccordionListItemTable; \ No newline at end of file diff --git a/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js b/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js index 2b3bd5c0..13225c59 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js @@ -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 ( - +
+ + + + +
); } }); diff --git a/js/components/ascribe_accordion_list/accordion_list_item_table_toggle.js b/js/components/ascribe_accordion_list/accordion_list_item_table_toggle.js new file mode 100644 index 00000000..a3431b04 --- /dev/null +++ b/js/components/ascribe_accordion_list/accordion_list_item_table_toggle.js @@ -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 ( + + {this.props.show ? 'Hide all ' + this.props.numOfTableItems + ' Editions' : 'Show all ' + this.props.numOfTableItems + ' Editions'} + + ); + } +}); + +export default AccordionListItemTableToggle; \ No newline at end of file diff --git a/js/components/ascribe_forms/button_submit_close.js b/js/components/ascribe_forms/button_submit_close.js index 60091f2f..e811cab5 100644 --- a/js/components/ascribe_forms/button_submit_close.js +++ b/js/components/ascribe_forms/button_submit_close.js @@ -3,11 +3,11 @@ import React from 'react'; let ButtonSubmitOrClose = React.createClass({ render() { if (this.props.submitted){ - return ( -
- Loading -
- ) + //return ( + //
+ // Loading + //
+ //) } return (
diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js new file mode 100644 index 00000000..dbb1eee0 --- /dev/null +++ b/js/components/ascribe_forms/form_consign.js @@ -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 ( +
+ + + + + + + + ); + } +}); + +export default ConsignForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js new file mode 100644 index 00000000..483d0f0a --- /dev/null +++ b/js/components/ascribe_forms/form_loan.js @@ -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 = ; + if (this.state.loaneeHasContract){ + let label = ; + contract = + } + return ( +
+ + + + +
+
+ +
+
+ +
+
+ + + {contract} + + + ); + } +}); + +export default LoanForm; diff --git a/js/components/ascribe_forms/form_share_email.js b/js/components/ascribe_forms/form_share_email.js index dd61bc7b..cbcfc7a1 100644 --- a/js/components/ascribe_forms/form_share_email.js +++ b/js/components/ascribe_forms/form_share_email.js @@ -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 (
diff --git a/js/components/ascribe_forms/form_unconsign.js b/js/components/ascribe_forms/form_unconsign.js new file mode 100644 index 00000000..60112334 --- /dev/null +++ b/js/components/ascribe_forms/form_unconsign.js @@ -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 ( + + + + + + + + ); + } +}); + +export default UnConsignForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/form_unconsign_request.js b/js/components/ascribe_forms/form_unconsign_request.js new file mode 100644 index 00000000..f572a287 --- /dev/null +++ b/js/components/ascribe_forms/form_unconsign_request.js @@ -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 ( +
+ + + + ); + } +}); + +export default UnConsignRequestForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/input_checkbox.js b/js/components/ascribe_forms/input_checkbox.js new file mode 100644 index 00000000..c1ecda1a --- /dev/null +++ b/js/components/ascribe_forms/input_checkbox.js @@ -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 ( +
+ {alerts} +
+
+ +
+
+
+ ); + + } +}); + +export default InputCheckbox; \ No newline at end of file diff --git a/js/components/ascribe_forms/input_date.js b/js/components/ascribe_forms/input_date.js new file mode 100644 index 00000000..98422c72 --- /dev/null +++ b/js/components/ascribe_forms/input_date.js @@ -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 ( + + ); + //return ( + //
+ // + // + // + // + //
+ //) + + + } +}); + +export default InputDate; diff --git a/js/components/ascribe_forms/input_hidden.js b/js/components/ascribe_forms/input_hidden.js new file mode 100644 index 00000000..96ff95ce --- /dev/null +++ b/js/components/ascribe_forms/input_hidden.js @@ -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 ( +
+ {alerts} + +
+ ); + + } +}); + +export default InputHidden; \ No newline at end of file diff --git a/js/components/ascribe_forms/input_text.js b/js/components/ascribe_forms/input_text.js index a65bcc8f..b9e7287a 100644 --- a/js/components/ascribe_forms/input_text.js +++ b/js/components/ascribe_forms/input_text.js @@ -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}/>
); diff --git a/js/components/ascribe_forms/input_textarea.js b/js/components/ascribe_forms/input_textarea.js index b5f64b66..cf5cfc4a 100644 --- a/js/components/ascribe_forms/input_textarea.js +++ b/js/components/ascribe_forms/input_textarea.js @@ -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}> ); - } }); diff --git a/js/components/ascribe_modal/modal_loan.js b/js/components/ascribe_modal/modal_loan.js new file mode 100644 index 00000000..aed475b1 --- /dev/null +++ b/js/components/ascribe_modal/modal_loan.js @@ -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 ( + Loan your artwork for a limited period of time}> + }> +
+ LOAN +
+
+
+ ) + } +}); + +let LoanModal = React.createClass({ + mixins : [ModalMixin], + + render() { + return ( + +
+ +
+
+ ) + } +}); + + +export default LoanModalButton; diff --git a/js/components/ascribe_modal/modal_share.js b/js/components/ascribe_modal/modal_share.js index 176541ee..4ada244c 100644 --- a/js/components/ascribe_modal/modal_share.js +++ b/js/components/ascribe_modal/modal_share.js @@ -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 ( diff --git a/js/components/ascribe_modal/modal_transfer.js b/js/components/ascribe_modal/modal_transfer.js deleted file mode 100644 index b57064ff..00000000 --- a/js/components/ascribe_modal/modal_transfer.js +++ /dev/null @@ -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 ( - Transfer the ownership of the artwork}> - }> -
- TRANSFER -
-
-
- ) - } -}); - -let TransferModal = React.createClass({ - onRequestHide(e){ - e.preventDefault(); - this.props.onRequestHide(); - }, - render() { - return ( - -
- -
-
- ) - } -}); - - -export default TransferModalButton; diff --git a/js/components/ascribe_modal/modal_unconsign.js b/js/components/ascribe_modal/modal_unconsign.js new file mode 100644 index 00000000..1245fc01 --- /dev/null +++ b/js/components/ascribe_modal/modal_unconsign.js @@ -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 ( + Unconsign this artwork}> + }> +
+ UNCONSIGN +
+
+
+ ) + } +}); + +let UnConsignModal = React.createClass({ + mixins : [ModalMixin], + + render() { + return ( + +
+ +
+
+ ) + } +}); + + +export default UnConsignModalButton; diff --git a/js/components/ascribe_modal/modal_unconsign_request.js b/js/components/ascribe_modal/modal_unconsign_request.js new file mode 100644 index 00000000..c74e637b --- /dev/null +++ b/js/components/ascribe_modal/modal_unconsign_request.js @@ -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 ( + Request to unconsign the artwork}> + }> +
+ UNCONSIGN REQUEST +
+
+
+ ) + } +}); + +let UnConsignRequestModal = React.createClass({ + mixins : [ModalMixin], + + render() { + return ( + +
+ +
+
+ ) + } +}); + + +export default UnConsignRequestModalButton; diff --git a/js/components/ascribe_modal/modal_wrapper.js b/js/components/ascribe_modal/modal_wrapper.js new file mode 100644 index 00000000..a5d20389 --- /dev/null +++ b/js/components/ascribe_modal/modal_wrapper.js @@ -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 ( + {this.props.tooltip}}> + + {this.props.children} + + }> + {this.props.button} + + + ) + } +}); + +// +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 ( + +
+ {this.renderChildren()} +
+
+ ) + } +}); + + +export default ModalWrapper; diff --git a/js/components/ascribe_pagination/pagination_button.js b/js/components/ascribe_pagination/pagination_button.js index ee9c1374..bbb2a789 100644 --- a/js/components/ascribe_pagination/pagination_button.js +++ b/js/components/ascribe_pagination/pagination_button.js @@ -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 = ( - Previous + {getLangText('Previous')} ); } else { page += 1; directionDisplay = ( - Next + {getLangText('Next')} ); } diff --git a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js new file mode 100644 index 00000000..896882df --- /dev/null +++ b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js @@ -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 ( +
+
+
+

+
+
+ +           + clear all +
+
+

+
+
+ + + + +
+
+
+
+
+ ); + } else { + return null; + } + + } +}); + +export default PieceListBulkModal; \ No newline at end of file diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_selected_editions_widget.js b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal_selected_editions_widget.js similarity index 74% rename from js/components/ascribe_piece_list_toolbar/piece_list_toolbar_selected_editions_widget.js rename to js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal_selected_editions_widget.js index 5c020d45..22b35a4a 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_selected_editions_widget.js +++ b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal_selected_editions_widget.js @@ -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; \ No newline at end of file +export default PieceListBulkModalSelectedEditionsWidget; \ No newline at end of file diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js index f7b04cc6..ce9150b1 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js @@ -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 = ; - if(availableAcls.length > 0) { - return ( -
-
-
-

-
-
- -           - clear all -
-
-

-
-
- - - - + return ( +
+
+
+
+
+
+ +    + {/**/}
- ); - } else { - return null; - } - +
+ ); } }); diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js new file mode 100644 index 00000000..7aaf9dd8 --- /dev/null +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js @@ -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 = ; + + return ( + +
  • + Show Pieces that: +
  • + +
    + I can transfer +
    +
    + +
    + I can consign +
    +
    +
    + ); + } +}); + +export default PieceListToolbarFilterWidgetFilter; \ No newline at end of file diff --git a/js/components/ascribe_table/table_header.js b/js/components/ascribe_table/table_header.js index 8176ad31..44594394 100644 --- a/js/components/ascribe_table/table_header.js +++ b/js/components/ascribe_table/table_header.js @@ -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'; diff --git a/js/components/ascribe_table/table_item_acl_filtered.js b/js/components/ascribe_table/table_item_acl_filtered.js index dada05ab..6fd8e9fe 100644 --- a/js/components/ascribe_table/table_item_acl_filtered.js +++ b/js/components/ascribe_table/table_item_acl_filtered.js @@ -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 ( diff --git a/js/components/edition.js b/js/components/edition.js index b7f96291..767eae0f 100644 --- a/js/components/edition.js +++ b/js/components/edition.js @@ -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 (
    @@ -57,8 +61,20 @@ let EditionDetails = React.createClass({
    - - + +
    ); diff --git a/js/components/edition_container.js b/js/components/edition_container.js index 3d847036..2377ea3e 100644 --- a/js/components/edition_container.js +++ b/js/components/edition_container.js @@ -27,7 +27,6 @@ let EditionContainer = React.createClass({ UserActions.fetchCurrentUser(); UserStore.listen(this.onChange); }, - componentDidUnmount() { EditionStore.unlisten(this.onChange); UserStore.unlisten(this.onChange); diff --git a/js/components/header.js b/js/components/header.js index 20855b1b..b7a412e0 100644 --- a/js/components/header.js +++ b/js/components/header.js @@ -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 ( - + + + + ); } }); diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 9580ffb1..e6735a27 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -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 (
    + ); diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js index 9c17de7c..db846a72 100644 --- a/js/constants/api_urls.js +++ b/js/constants/api_urls.js @@ -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; diff --git a/js/constants/languages.js b/js/constants/languages.js new file mode 100644 index 00000000..47e29ee0 --- /dev/null +++ b/js/constants/languages.js @@ -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; \ No newline at end of file diff --git a/js/fetchers/edition_fetcher.js b/js/fetchers/edition_fetcher.js index 7ce5bdab..46cdf1b7 100644 --- a/js/fetchers/edition_fetcher.js +++ b/js/fetchers/edition_fetcher.js @@ -1,7 +1,6 @@ import fetch from '../utils/fetch'; import AppConstants from '../constants/application_constants'; -import FetchApiUtils from '../utils/fetch_api_utils'; let EditionFetcher = { diff --git a/js/fetchers/ownership_fetcher.js b/js/fetchers/ownership_fetcher.js new file mode 100644 index 00000000..94050860 --- /dev/null +++ b/js/fetchers/ownership_fetcher.js @@ -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; diff --git a/js/fetchers/piece_list_fetcher.js b/js/fetchers/piece_list_fetcher.js index 9490c6fb..ed867253 100644 --- a/js/fetchers/piece_list_fetcher.js +++ b/js/fetchers/piece_list_fetcher.js @@ -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 }); } }; diff --git a/js/fetchers/user_fetcher.js b/js/fetchers/user_fetcher.js index b5313cd6..df3e887e 100644 --- a/js/fetchers/user_fetcher.js +++ b/js/fetchers/user_fetcher.js @@ -1,7 +1,6 @@ import fetch from '../utils/fetch'; import AppConstants from '../constants/application_constants'; -import FetchApiUtils from '../utils/fetch_api_utils'; let UserFetcher = { diff --git a/js/mixins/alert_mixin.js b/js/mixins/alert_mixin.js index efabed0e..f52890c7 100644 --- a/js/mixins/alert_mixin.js +++ b/js/mixins/alert_mixin.js @@ -5,12 +5,15 @@ let AlertMixin = { setAlerts(errors){ let alerts = errors.map( function(error) { - let key = error + this.state.retry; - return ; + return ; }.bind(this) ); - this.setState({alerts: alerts, retry: this.state.retry + 1}); + this.setState({alerts: alerts}); + }, + clearAlerts(){ + this.setState({alerts: null}); } + }; export default AlertMixin; \ No newline at end of file diff --git a/js/mixins/form_mixin.js b/js/mixins/form_mixin.js index 73f50b62..4e2c7045 100644 --- a/js/mixins/form_mixin.js +++ b/js/mixins/form_mixin.js @@ -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 = ; + if (this.state.errors.length > 0){ + alert = this.state.errors.map( + function(error) { + return ; + }.bind(this) + ); } return (
    diff --git a/js/mixins/modal_mixin.js b/js/mixins/modal_mixin.js new file mode 100644 index 00000000..7dee243b --- /dev/null +++ b/js/mixins/modal_mixin.js @@ -0,0 +1,11 @@ +import React from 'react'; + +let ModalMixin = { + onRequestHide(e){ + if (e) + e.preventDefault(); + this.props.onRequestHide(); + } +}; + +export default ModalMixin; \ No newline at end of file diff --git a/js/mixins/table_column_mixin.js b/js/mixins/table_column_mixin.js index d74214e1..eac266b6 100644 --- a/js/mixins/table_column_mixin.js +++ b/js/mixins/table_column_mixin.js @@ -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.') diff --git a/js/stores/edition_store.js b/js/stores/edition_store.js index 9859ec7c..b33e248f 100644 --- a/js/stores/edition_store.js +++ b/js/stores/edition_store.js @@ -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) { diff --git a/js/stores/piece_list_store.js b/js/stores/piece_list_store.js index 97e3f4a4..7ca96c87 100644 --- a/js/stores/piece_list_store.js +++ b/js/stores/piece_list_store.js @@ -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; diff --git a/js/stores/user_store.js b/js/stores/user_store.js index 45fbec57..6ae631c9 100644 --- a/js/stores/user_store.js +++ b/js/stores/user_store.js @@ -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); diff --git a/js/utils/fetch.js b/js/utils/fetch.js index a9fa1c02..f7e12f43 100644 --- a/js/utils/fetch.js +++ b/js/utils/fetch.js @@ -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; diff --git a/js/utils/fetch_api_utils.js b/js/utils/fetch_api_utils.js index 47b52e66..bf66a45c 100644 --- a/js/utils/fetch_api_utils.js +++ b/js/utils/fetch_api_utils.js @@ -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()) +}; diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js index 7ab180fa..96f56c1e 100644 --- a/js/utils/general_utils.js +++ b/js/utils/general_utils.js @@ -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; + }); +}; diff --git a/js/utils/lang_utils.js b/js/utils/lang_utils.js new file mode 100644 index 00000000..7eb4b6f3 --- /dev/null +++ b/js/utils/lang_utils.js @@ -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); + } + + } +}; diff --git a/package.json b/package.json index 3fc2112b..99f8f431 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/sass/ascribe-accordion_list.scss b/sass/ascribe_accordion_list.scss similarity index 100% rename from sass/ascribe-accordion_list.scss rename to sass/ascribe_accordion_list.scss diff --git a/sass/ascribe-piece-list-toolbar.scss b/sass/ascribe_piece_list_bulk_modal.scss similarity index 85% rename from sass/ascribe-piece-list-toolbar.scss rename to sass/ascribe_piece_list_bulk_modal.scss index 6093aed9..4a6b98d7 100644 --- a/sass/ascribe-piece-list-toolbar.scss +++ b/sass/ascribe_piece_list_bulk_modal.scss @@ -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; } \ No newline at end of file diff --git a/sass/ascribe_piece_list_toolbar.scss b/sass/ascribe_piece_list_toolbar.scss new file mode 100644 index 00000000..73c0bc94 --- /dev/null +++ b/sass/ascribe_piece_list_toolbar.scss @@ -0,0 +1,3 @@ +.ascribe-piece-list-toolbar { + margin-bottom: 1.5em; +} \ No newline at end of file diff --git a/sass/ascribe-variables.scss b/sass/ascribe_variables.scss similarity index 100% rename from sass/ascribe-variables.scss rename to sass/ascribe_variables.scss diff --git a/sass/main.scss b/sass/main.scss index 471a6124..2fe60cac 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -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; -} \ No newline at end of file +} diff --git a/sass/offset_right.scss b/sass/offset_right.scss new file mode 100644 index 00000000..f844ce20 --- /dev/null +++ b/sass/offset_right.scss @@ -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; + } +} \ No newline at end of file