1
0
mirror of https://github.com/ascribe/onion.git synced 2024-11-14 09:05:08 +01:00

Merge branch 'master' into AD-613-cyland-white-label-page

Conflicts:
	.gitignore
This commit is contained in:
Tim Daubenschütz 2015-08-11 11:50:09 +02:00
commit 95e0b256b6
112 changed files with 2336 additions and 1178 deletions

7
.gitignore vendored
View File

@ -7,9 +7,12 @@ lib-cov
*.pid *.pid
*.gz *.gz
*.sublime-project *.sublime-project
spool-project.sublime-project spool-project.sublime-project
*.sublime-workspace *.sublime-workspace
*.sublime-workspace
webapp-dependencies.txt
pids pids
logs logs
results results
@ -18,4 +21,4 @@ node_modules/*
build build
.DS_Store .DS_Store

View File

@ -101,6 +101,9 @@ A: Easily by starting the your gulp process with the following command:
ONION_BASE_URL='/' ONION_SERVER_URL='http://localhost.com:8000/' gulp serve ONION_BASE_URL='/' ONION_SERVER_URL='http://localhost.com:8000/' gulp serve
``` ```
Q: I want to know all dependencies that get bundled into the live build.
A: ```browserify -e js/app.js --list > webapp-dependencies.txt```
Reading list Reading list
============ ============

View File

@ -2,16 +2,18 @@
*This should be a living document. So if you have any ideas for refactoring stuff, then feel free to add them to this document* *This should be a living document. So if you have any ideas for refactoring stuff, then feel free to add them to this document*
- Get rid of all Mixins. (making good progress there :))
- Make all standalone components independent from things like global utilities (GeneralUtils is maybe used in table for example) - Make all standalone components independent from things like global utilities (GeneralUtils is maybe used in table for example)
- Check if all polyfills are appropriately initialized and available: Compare to this
- Extract all standalone components to their own folder structure and write application independent tests (+ figure out how to do that in a productive way) (fetch lib especially) - Extract all standalone components to their own folder structure and write application independent tests (+ figure out how to do that in a productive way) (fetch lib especially)
- Refactor forms to generic-declarative form component
- Check for mobile compatibility: Is site responsive anywhere? - Check for mobile compatibility: Is site responsive anywhere?
queryParams of the piece_list_store should all be reflected in the url and not a single component each should manipulate the URL bar (refactor pagination, use actions and state) queryParams of the piece_list_store should all be reflected in the url and not a single component each should manipulate the URL bar (refactor pagination, use actions and state)
- Refactor string-templating for api_urls - Refactor string-templating for api_urls
- Use classNames plugin instead of if-conditional-classes - Use classNames plugin instead of if-conditional-classes
# Refactor DONE
- Refactor forms to generic-declarative form component ✓
- Get rid of all Mixins (inject head is fine) ✓
- Check if all polyfills are appropriately initialized and available: Compare to this ✓
## React-S3-Fineuploader ## React-S3-Fineuploader
- implementation should enable to define all important methods outside - implementation should enable to define all important methods outside
- and: maybe create a utility class for all methods to avoid code duplication - and: maybe create a utility class for all methods to avoid code duplication

View File

@ -17,21 +17,21 @@ class EditionListActions {
); );
} }
fetchEditionList(pieceId, page, pageSize, orderBy, orderAsc) { fetchEditionList(pieceId, page, pageSize, orderBy, orderAsc, filterBy) {
if(!orderBy && typeof orderAsc === 'undefined') { if((!orderBy && typeof orderAsc === 'undefined') || !orderAsc) {
orderBy = 'edition_number'; orderBy = 'edition_number';
orderAsc = true; orderAsc = true;
} }
// Taken from: http://stackoverflow.com/a/519157/1263876 // Taken from: http://stackoverflow.com/a/519157/1263876
if(typeof page === 'undefined' && typeof pageSize === 'undefined') { if((typeof page === 'undefined' || !page) && (typeof pageSize === 'undefined' || !pageSize)) {
page = 1; page = 1;
pageSize = 10; pageSize = 10;
} }
return Q.Promise((resolve, reject) => { return Q.Promise((resolve, reject) => {
EditionListFetcher EditionListFetcher
.fetch(pieceId, page, pageSize, orderBy, orderAsc) .fetch(pieceId, page, pageSize, orderBy, orderAsc, filterBy)
.then((res) => { .then((res) => {
this.actions.updateEditionList({ this.actions.updateEditionList({
pieceId, pieceId,
@ -39,6 +39,7 @@ class EditionListActions {
pageSize, pageSize,
orderBy, orderBy,
orderAsc, orderAsc,
filterBy,
'editionListOfPiece': res.editions, 'editionListOfPiece': res.editions,
'count': res.count 'count': res.count
}); });

View File

@ -7,7 +7,8 @@ import OwnershipFetcher from '../fetchers/ownership_fetcher';
class LoanContractActions { class LoanContractActions {
constructor() { constructor() {
this.generateActions( this.generateActions(
'updateLoanContract' 'updateLoanContract',
'flushLoanContract'
); );
} }

View File

@ -14,7 +14,7 @@ class PieceListActions {
); );
} }
fetchPieceList(page, pageSize, search, orderBy, orderAsc) { fetchPieceList(page, pageSize, search, orderBy, orderAsc, filterBy) {
// To prevent flickering on a pagination request, // To prevent flickering on a pagination request,
// we overwrite the piecelist with an empty list before // we overwrite the piecelist with an empty list before
// pieceListCount === -1 defines the loading state // pieceListCount === -1 defines the loading state
@ -24,6 +24,7 @@ class PieceListActions {
search, search,
orderBy, orderBy,
orderAsc, orderAsc,
filterBy,
'pieceList': [], 'pieceList': [],
'pieceListCount': -1 'pieceListCount': -1
}); });
@ -32,7 +33,7 @@ class PieceListActions {
return Q.Promise((resolve, reject) => { return Q.Promise((resolve, reject) => {
PieceListFetcher PieceListFetcher
.fetch(page, pageSize, search, orderBy, orderAsc) .fetch(page, pageSize, search, orderBy, orderAsc, filterBy)
.then((res) => { .then((res) => {
this.actions.updatePieceList({ this.actions.updatePieceList({
page, page,
@ -40,6 +41,7 @@ class PieceListActions {
search, search,
orderBy, orderBy,
orderAsc, orderAsc,
filterBy,
'pieceList': res.pieces, 'pieceList': res.pieces,
'pieceListCount': res.count 'pieceListCount': res.count
}); });

View File

@ -0,0 +1,34 @@
'use strict';
import alt from '../alt';
import Q from 'q';
import PrizeListFetcher from '../fetchers/prize_list_fetcher';
class PrizeListActions {
constructor() {
this.generateActions(
'updatePrizeList'
);
}
fetchPrizeList() {
return Q.Promise((resolve, reject) => {
PrizeListFetcher
.fetch()
.then((res) => {
this.actions.updatePrizeList({
prizeList: res.prizes,
prizeListCount: res.count
});
resolve(res);
})
.catch((err) => {
console.logGlobal(err);
reject(err);
});
});
}
}
export default alt.createActions(PrizeListActions);

View File

@ -22,6 +22,7 @@ class UserActions {
this.actions.updateCurrentUser({}); this.actions.updateCurrentUser({});
}); });
} }
logoutCurrentUser() { logoutCurrentUser() {
return UserFetcher.logout() return UserFetcher.logout()
.then(() => { .then(() => {

View File

@ -36,9 +36,9 @@ let AclProxy = React.createClass({
</span> </span>
); );
} else { } else {
if(typeof this.props.aclObject[this.props.aclName] === 'undefined') { /* if(typeof this.props.aclObject[this.props.aclName] === 'undefined') {
console.warn('The aclName you\'re filtering for was not present (or undefined) in the aclObject.'); console.warn('The aclName you\'re filtering for was not present (or undefined) in the aclObject.');
} } */
return null; return null;
} }
} }

View File

@ -21,7 +21,7 @@ import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions'; import GlobalNotificationActions from '../../actions/global_notification_actions';
import AclProxy from '../acl_proxy'; import AclProxy from '../acl_proxy';
import SubmitToPrizeButton from '../ascribe_buttons/submit_to_prize_button'; import SubmitToPrizeButton from '../whitelabel/prize/components/ascribe_buttons/submit_to_prize_button';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
import { mergeOptions } from '../../utils/general_utils'; import { mergeOptions } from '../../utils/general_utils';
@ -87,14 +87,16 @@ let AccordionListItem = React.createClass({
}, },
handleSubmitPrizeSuccess(response) { handleSubmitPrizeSuccess(response) {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc); PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
let notification = new GlobalNotificationModel(response.notification, 'success', 10000); let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
onPollingSuccess(pieceId) { onPollingSuccess(pieceId) {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc); PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
EditionListActions.toggleEditionList(pieceId); EditionListActions.toggleEditionList(pieceId);
let notification = new GlobalNotificationModel('Editions successfully created', 'success', 10000); let notification = new GlobalNotificationModel('Editions successfully created', 'success', 10000);
@ -178,21 +180,13 @@ let AccordionListItem = React.createClass({
onPollingSuccess={this.onPollingSuccess}/> onPollingSuccess={this.onPollingSuccess}/>
</AclProxy> </AclProxy>
<AclProxy <AclProxy
show={this.props.content.prize === null}> aclObject={this.props.content.acl}
aclName="acl_submit_to_prize">
<SubmitToPrizeButton <SubmitToPrizeButton
className="pull-right" className="pull-right"
piece={this.props.content} piece={this.props.content}
handleSuccess={this.handleSubmitPrizeSuccess}/> handleSuccess={this.handleSubmitPrizeSuccess}/>
</AclProxy> </AclProxy>
<AclProxy
show={this.props.content.prize}>
<button
disabled
className="btn btn-default btn-xs pull-right">
{getLangText('Submitted to prize')} <span className="glyphicon glyphicon-ok"
aria-hidden="true"></span>
</button>
</AclProxy>
{this.getLicences()} {this.getLicences()}
</div> </div>
</div> </div>

View File

@ -6,12 +6,14 @@ import classNames from 'classnames';
import EditionListActions from '../../actions/edition_list_actions'; import EditionListActions from '../../actions/edition_list_actions';
import EditionListStore from '../../stores/edition_list_store'; import EditionListStore from '../../stores/edition_list_store';
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; import PieceListActions from '../../actions/piece_list_actions';
import Tooltip from 'react-bootstrap/lib/Tooltip'; import PieceListStore from '../../stores/piece_list_store';
import Button from 'react-bootstrap/lib/Button'; import Button from 'react-bootstrap/lib/Button';
import CreateEditionsButton from '../ascribe_buttons/create_editions_button'; import CreateEditionsButton from '../ascribe_buttons/create_editions_button';
import { mergeOptions } from '../../utils/general_utils';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
let AccordionListItemEditionWidget = React.createClass({ let AccordionListItemEditionWidget = React.createClass({
@ -23,15 +25,20 @@ let AccordionListItemEditionWidget = React.createClass({
}, },
getInitialState() { getInitialState() {
return EditionListStore.getState(); return mergeOptions(
EditionListStore.getState(),
PieceListStore.getState()
);
}, },
componentDidMount() { componentDidMount() {
EditionListStore.listen(this.onChange); EditionListStore.listen(this.onChange);
PieceListStore.listen(this.onChange);
}, },
componentWillUnmount() { componentWillUnmount() {
EditionListStore.unlisten(this.onChange); EditionListStore.unlisten(this.onChange);
PieceListStore.unlisten(this.onChange);
}, },
onChange(state) { onChange(state) {
@ -49,7 +56,7 @@ let AccordionListItemEditionWidget = React.createClass({
EditionListActions.toggleEditionList(pieceId); EditionListActions.toggleEditionList(pieceId);
} else { } else {
EditionListActions.toggleEditionList(pieceId); EditionListActions.toggleEditionList(pieceId);
EditionListActions.fetchEditionList(pieceId); EditionListActions.fetchEditionList(pieceId, null, null, null, null, this.state.filterBy);
} }
}, },
@ -87,7 +94,7 @@ let AccordionListItemEditionWidget = React.createClass({
let numEditions = piece.num_editions; let numEditions = piece.num_editions;
if(numEditions <= 0) { if(numEditions <= 0) {
if (piece.acl.acl_editions){ if (piece.acl.acl_create_editions){
return ( return (
<CreateEditionsButton <CreateEditionsButton
label={getLangText('Create editions')} label={getLangText('Create editions')}

View File

@ -76,13 +76,9 @@ let AccordionListItemTableEditions = React.createClass({
}); });
let editionList = this.state.editionList[this.props.parentId]; let editionList = this.state.editionList[this.props.parentId];
EditionListActions.fetchEditionList(this.props.parentId, editionList.page + 1, editionList.pageSize); EditionListActions.fetchEditionList(this.props.parentId, editionList.page + 1, editionList.pageSize,
editionList.orderBy, editionList.orderAsc, editionList.filterBy);
}, },
changeEditionListOrder(orderBy, orderAsc) {
EditionListActions.fetchEditionList(this.props.parentId, orderBy, orderAsc);
},
render() { render() {
let selectedEditionsCount = 0; let selectedEditionsCount = 0;
let allEditionsCount = 0; let allEditionsCount = 0;

View File

@ -13,8 +13,10 @@ import AppConstants from '../../constants/application_constants';
import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions'; import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getLangText } from '../../utils/lang_utils.js'; import ApiUrls from '../../constants/api_urls';
import apiUrls from '../../constants/api_urls';
import { getAclFormMessage } from '../../utils/form_utils';
import { getLangText } from '../../utils/lang_utils';
let AclButton = React.createClass({ let AclButton = React.createClass({
propTypes: { propTypes: {
@ -34,15 +36,18 @@ let AclButton = React.createClass({
}, },
actionProperties(){ actionProperties(){
let message = getAclFormMessage(this.props.action, this.getTitlesString(), this.props.currentUser.username);
if (this.props.action === 'acl_consign'){ if (this.props.action === 'acl_consign'){
return { return {
title: getLangText('Consign artwork'), title: getLangText('Consign artwork'),
tooltip: getLangText('Have someone else sell the artwork'), tooltip: getLangText('Have someone else sell the artwork'),
form: ( form: (
<ConsignForm <ConsignForm
message={this.getConsignMessage()} message={message}
id={this.getFormDataId()} id={this.getFormDataId()}
url={apiUrls.ownership_consigns}/> url={ApiUrls.ownership_consigns}/>
), ),
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
@ -53,9 +58,9 @@ let AclButton = React.createClass({
tooltip: getLangText('Have the owner manage his sales again'), tooltip: getLangText('Have the owner manage his sales again'),
form: ( form: (
<UnConsignForm <UnConsignForm
message={this.getUnConsignMessage()} message={message}
id={this.getFormDataId()} id={this.getFormDataId()}
url={apiUrls.ownership_unconsigns}/> url={ApiUrls.ownership_unconsigns}/>
), ),
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
@ -65,9 +70,9 @@ let AclButton = React.createClass({
tooltip: getLangText('Transfer the ownership of the artwork'), tooltip: getLangText('Transfer the ownership of the artwork'),
form: ( form: (
<TransferForm <TransferForm
message={this.getTransferMessage()} message={message}
id={this.getFormDataId()} id={this.getFormDataId()}
url={apiUrls.ownership_transfers}/> url={ApiUrls.ownership_transfers}/>
), ),
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
@ -77,9 +82,9 @@ let AclButton = React.createClass({
title: getLangText('Loan artwork'), title: getLangText('Loan artwork'),
tooltip: getLangText('Loan your artwork for a limited period of time'), tooltip: getLangText('Loan your artwork for a limited period of time'),
form: (<LoanForm form: (<LoanForm
message={this.getLoanMessage()} message={message}
id={this.getFormDataId()} id={this.getFormDataId()}
url={this.isPiece() ? apiUrls.ownership_loans_pieces : apiUrls.ownership_loans_editions}/> url={this.isPiece() ? ApiUrls.ownership_loans_pieces : ApiUrls.ownership_loans_editions}/>
), ),
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
@ -90,9 +95,9 @@ let AclButton = React.createClass({
tooltip: getLangText('Share the artwork'), tooltip: getLangText('Share the artwork'),
form: ( form: (
<ShareForm <ShareForm
message={this.getShareMessage()} message={message}
id={this.getFormDataId()} id={this.getFormDataId()}
url={this.isPiece() ? apiUrls.ownership_shares_pieces : apiUrls.ownership_shares_editions }/> url={this.isPiece() ? ApiUrls.ownership_shares_pieces : ApiUrls.ownership_shares_editions }/>
), ),
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
@ -133,76 +138,6 @@ let AclButton = React.createClass({
} }
}, },
// plz move to transfer form
getTransferMessage(){
return (
`${getLangText('Hi')},
${getLangText('I transfer ownership of')}:
${this.getTitlesString()} ${getLangText('to you')}.
${getLangText('Truly yours')},
${this.props.currentUser.username}
`
);
},
// plz move to transfer form
getLoanMessage(){
return (
`${getLangText('Hi')},
${getLangText('I loan')}:
${this.getTitlesString()} ${getLangText('to you')}.
${getLangText('Truly yours')},
${this.props.currentUser.username}
`
);
},
// plz move to consign form
getConsignMessage(){
return (
`${getLangText('Hi')},
${getLangText('I consign')}:
${this.getTitlesString()} ${getLangText('to you')}.
${getLangText('Truly yours')},
${this.props.currentUser.username}
`
);
},
// plz move to consign form
getUnConsignMessage(){
return (
`${getLangText('Hi')},
${getLangText('I un-consign')}:
${this.getTitlesString()} ${getLangText('from you')}.
${getLangText('Truly yours')},
${this.props.currentUser.username}
`
);
},
// plz move to share form
getShareMessage(){
return (
`${getLangText('Hi')},
${getLangText('I am sharing')}:
${this.getTitlesString()} ${getLangText('with you')}.
${getLangText('Truly yours')},
${this.props.currentUser.username}
`
);
},
// Removes the acl_ prefix and converts to upper case // Removes the acl_ prefix and converts to upper case
sanitizeAction() { sanitizeAction() {
return this.props.action.split('acl_')[1].toUpperCase(); return this.props.action.split('acl_')[1].toUpperCase();
@ -214,14 +149,13 @@ ${this.props.currentUser.username}
return ( return (
<ModalWrapper <ModalWrapper
button={ trigger={
<button className={shouldDisplay ? 'btn btn-default btn-sm ' : 'hidden'}> <button className={shouldDisplay ? 'btn btn-default btn-sm ' : 'hidden'}>
{this.sanitizeAction()} {this.sanitizeAction()}
</button> </button>
} }
handleSuccess={aclProps.handleSuccess} handleSuccess={aclProps.handleSuccess}
title={aclProps.title} title={aclProps.title}>
tooltip={aclProps.tooltip}>
{aclProps.form} {aclProps.form}
</ModalWrapper> </ModalWrapper>
); );

View File

@ -44,14 +44,17 @@ let CreateEditionsButton = React.createClass({
startPolling() { startPolling() {
// start polling until editions are defined // start polling until editions are defined
let pollingIntervalIndex = setInterval(() => { let pollingIntervalIndex = setInterval(() => {
EditionListActions.fetchEditionList(this.props.piece.id)
// requests, will try to merge the filterBy parameter with other parameters (mergeOptions).
// Therefore it can't but null but instead has to be an empty object
EditionListActions.fetchEditionList(this.props.piece.id, null, null, null, null, {})
.then((res) => { .then((res) => {
clearInterval(this.state.pollingIntervalIndex); clearInterval(this.state.pollingIntervalIndex);
this.props.onPollingSuccess(this.props.piece.id, res.editions[0].num_editions); this.props.onPollingSuccess(this.props.piece.id, res.editions[0].num_editions);
}) })
.catch(() => { .catch((err) => {
/* Ignore and keep going */ /* Ignore and keep going */
}); });
}, 5000); }, 5000);
@ -64,7 +67,7 @@ let CreateEditionsButton = React.createClass({
render: function () { render: function () {
let piece = this.props.piece; let piece = this.props.piece;
if (!piece.acl.acl_editions || piece.num_editions > 0){ if (!piece.acl.acl_create_editions || piece.num_editions > 0){
return null; return null;
} }

View File

@ -26,7 +26,7 @@ let DeleteButton = React.createClass({
mixins: [Router.Navigation], mixins: [Router.Navigation],
render: function () { render() {
let availableAcls; let availableAcls;
let btnDelete; let btnDelete;
let content; let content;
@ -61,13 +61,14 @@ let DeleteButton = React.createClass({
} }
btnDelete = <Button bsStyle="danger" className="btn-delete" bsSize="small">{getLangText('REMOVE FROM COLLECTION')}</Button>; btnDelete = <Button bsStyle="danger" className="btn-delete" bsSize="small">{getLangText('REMOVE FROM COLLECTION')}</Button>;
}
else { } else {
return null; return null;
} }
return ( return (
<ModalWrapper <ModalWrapper
button={btnDelete} trigger={btnDelete}
handleSuccess={this.props.handleSuccess} handleSuccess={this.props.handleSuccess}
title={title}> title={title}>
{content} {content}
@ -77,4 +78,3 @@ let DeleteButton = React.createClass({
}); });
export default DeleteButton; export default DeleteButton;

View File

@ -1,42 +0,0 @@
'use strict';
import React from 'react';
import classNames from 'classnames';
import ModalWrapper from '../ascribe_modal/modal_wrapper';
import PieceSubmitToPrizeForm from '../ascribe_forms/form_submit_to_prize';
import { getLangText } from '../../utils/lang_utils';
let SubmitToPrizeButton = React.createClass({
propTypes: {
className: React.PropTypes.string,
handleSuccess: React.PropTypes.func,
piece: React.PropTypes.object.isRequired
},
getSubmitButton() {
return (
<button
className={classNames('btn', 'btn-default', 'btn-xs', this.props.className)}>
{getLangText('Submit to prize')}
</button>
);
},
render() {
return (
<ModalWrapper
button={this.getSubmitButton()}
handleSuccess={this.props.handleSuccess}
title={getLangText('Submit to prize')}>
<PieceSubmitToPrizeForm
piece={this.props.piece}
handleSuccess={this.props.handleSuccess}/>
</ModalWrapper>
);
}
});
export default SubmitToPrizeButton;

View File

@ -8,7 +8,7 @@ import ModalWrapper from '../ascribe_modal/modal_wrapper';
import UnConsignRequestForm from './../ascribe_forms/form_unconsign_request'; import UnConsignRequestForm from './../ascribe_forms/form_unconsign_request';
import { getLangText } from '../../utils/lang_utils.js'; import { getLangText } from '../../utils/lang_utils.js';
import apiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
let UnConsignRequestButton = React.createClass({ let UnConsignRequestButton = React.createClass({
@ -21,16 +21,15 @@ let UnConsignRequestButton = React.createClass({
render: function () { render: function () {
return ( return (
<ModalWrapper <ModalWrapper
button={ trigger={
<Button bsStyle="danger" className="btn-delete pull-center" bsSize="small" type="submit"> <Button bsStyle="danger" className="btn-delete pull-center" bsSize="small" type="submit">
REQUEST UNCONSIGN REQUEST UNCONSIGN
</Button> </Button>
} }
handleSuccess={this.props.handleSuccess} handleSuccess={this.props.handleSuccess}
title='Request to Un-Consign' title='Request to Un-Consign'>
tooltip='Ask the consignee to return the ownership of the work back to you'>
<UnConsignRequestForm <UnConsignRequestForm
url={apiUrls.ownership_unconsigns_request} url={ApiUrls.ownership_unconsigns_request}
id={{'bitcoin_id': this.props.edition.bitcoin_id}} id={{'bitcoin_id': this.props.edition.bitcoin_id}}
message={`${getLangText('Hi')}, message={`${getLangText('Hi')},

View File

@ -2,13 +2,10 @@
import React from 'react'; import React from 'react';
import CollapsibleMixin from 'react-bootstrap/lib/CollapsibleMixin'; import Panel from 'react-bootstrap/lib/Panel';
import classNames from 'classnames';
const CollapsibleParagraph = React.createClass({ const CollapsibleParagraph = React.createClass({
propTypes: { propTypes: {
title: React.PropTypes.string, title: React.PropTypes.string,
children: React.PropTypes.oneOfType([ children: React.PropTypes.oneOfType([
@ -20,18 +17,14 @@ const CollapsibleParagraph = React.createClass({
getDefaultProps() { getDefaultProps() {
return { return {
show: true show: false
}; };
}, },
mixins: [CollapsibleMixin], getInitialState() {
return {
getCollapsibleDOMNode(){ expanded: false
return React.findDOMNode(this.refs.panel); };
},
getCollapsibleDimensionValue(){
return React.findDOMNode(this.refs.panel).scrollHeight;
}, },
handleToggle(e){ handleToggle(e){
@ -40,8 +33,7 @@ const CollapsibleParagraph = React.createClass({
}, },
render() { render() {
let styles = this.getCollapsibleClassSet(); let text = this.state.expanded ? '-' : '+';
let text = this.isExpanded() ? '-' : '+';
if(this.props.show) { if(this.props.show) {
return ( return (
@ -50,9 +42,12 @@ const CollapsibleParagraph = React.createClass({
<div onClick={this.handleToggle}> <div onClick={this.handleToggle}>
<span>{text} {this.props.title}</span> <span>{text} {this.props.title}</span>
</div> </div>
<div ref='panel' className={classNames(styles) + ' ascribe-edition-collapible-content'}> <Panel
collapsible
expanded={this.state.expanded}
className="ascribe-edition-collapsible-content">
{this.props.children} {this.props.children}
</div> </Panel>
</div> </div>
</div> </div>
); );

View File

@ -12,7 +12,7 @@ let DetailProperty = React.createClass({
separator: React.PropTypes.string, separator: React.PropTypes.string,
labelClassName: React.PropTypes.string, labelClassName: React.PropTypes.string,
valueClassName: React.PropTypes.string, valueClassName: React.PropTypes.string,
breakWord: React.PropTypes.bool ellipsis: React.PropTypes.bool
}, },
getDefaultProps() { getDefaultProps() {
@ -25,6 +25,16 @@ let DetailProperty = React.createClass({
render() { render() {
let value = this.props.value; let value = this.props.value;
let styles = {};
if(this.props.ellipsis) {
styles = {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
};
}
if (this.props.children){ if (this.props.children){
value = ( value = (
@ -32,7 +42,9 @@ let DetailProperty = React.createClass({
<div className="col-xs-6 col-xs-height col-bottom no-padding"> <div className="col-xs-6 col-xs-height col-bottom no-padding">
{ this.props.value } { this.props.value }
</div> </div>
<div className="col-xs-6 col-xs-height"> <div
className="col-xs-6 col-xs-height"
style={styles}>
{ this.props.children } { this.props.children }
</div> </div>
</div>); </div>);
@ -43,7 +55,9 @@ let DetailProperty = React.createClass({
<div className={this.props.labelClassName + ' col-xs-height col-bottom ascribe-detail-property-label'}> <div className={this.props.labelClassName + ' col-xs-height col-bottom ascribe-detail-property-label'}>
{ this.props.label + this.props.separator} { this.props.label + this.props.separator}
</div> </div>
<div className={this.props.valueClassName + ' col-xs-height col-bottom ascribe-detail-property-value'}> <div
className={this.props.valueClassName + ' col-xs-height col-bottom ascribe-detail-property-value'}
style={styles}>
{value} {value}
</div> </div>
</div> </div>

View File

@ -36,7 +36,7 @@ import DeleteButton from '../ascribe_buttons/delete_button';
import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions'; import GlobalNotificationActions from '../../actions/global_notification_actions';
import apiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
import AppConstants from '../../constants/application_constants'; import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
@ -86,9 +86,10 @@ let Edition = React.createClass({
}, },
handleDeleteSuccess(response) { handleDeleteSuccess(response) {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc); PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
EditionListActions.refreshEditionList(this.props.edition.parent); EditionListActions.refreshEditionList({pieceId: this.props.edition.parent});
EditionListActions.closeAllEditionLists(); EditionListActions.closeAllEditionLists();
EditionListActions.clearAllEditionSelections(); EditionListActions.clearAllEditionSelections();
@ -232,10 +233,11 @@ let EditionSummary = React.createClass({
if (this.props.edition.status.length > 0 && this.props.edition.pending_new_owner && this.props.edition.acl.acl_withdraw_transfer) { if (this.props.edition.status.length > 0 && this.props.edition.pending_new_owner && this.props.edition.acl.acl_withdraw_transfer) {
withdrawButton = ( withdrawButton = (
<Form <Form
url={apiUrls.ownership_transfers_withdraw} url={ApiUrls.ownership_transfers_withdraw}
getFormData={this.getTransferWithdrawData} getFormData={this.getTransferWithdrawData}
handleSuccess={this.showNotification} handleSuccess={this.showNotification}
className='inline'> className='inline'
isInline={true}>
<Button bsStyle="danger" className="btn-delete pull-center" bsSize="small" type="submit"> <Button bsStyle="danger" className="btn-delete pull-center" bsSize="small" type="submit">
WITHDRAW TRANSFER WITHDRAW TRANSFER
</Button> </Button>
@ -273,10 +275,16 @@ let EditionSummary = React.createClass({
render() { render() {
return ( return (
<div className="ascribe-detail-header"> <div className="ascribe-detail-header">
<EditionDetailProperty label={getLangText('EDITION')} <EditionDetailProperty
label={getLangText('EDITION')}
value={this.props.edition.edition_number + ' ' + getLangText('of') + ' ' + this.props.edition.num_editions} /> value={this.props.edition.edition_number + ' ' + getLangText('of') + ' ' + this.props.edition.num_editions} />
<EditionDetailProperty label={getLangText('ID')} value={ this.props.edition.bitcoin_id } /> <EditionDetailProperty
<EditionDetailProperty label={getLangText('OWNER')} value={ this.props.edition.owner } /> label={getLangText('ID')}
value={ this.props.edition.bitcoin_id }
ellipsis={true} />
<EditionDetailProperty
label={getLangText('OWNER')}
value={ this.props.edition.owner } />
{this.getStatus()} {this.getStatus()}
{this.getActions()} {this.getActions()}
<hr/> <hr/>
@ -328,7 +336,7 @@ let EditionPersonalNote = React.createClass({
if (this.props.currentUser.username && true || false) { if (this.props.currentUser.username && true || false) {
return ( return (
<Form <Form
url={apiUrls.note_notes} url={ApiUrls.note_notes}
handleSuccess={this.showNotification}> handleSuccess={this.showNotification}>
<Property <Property
name='note' name='note'
@ -366,7 +374,7 @@ let EditionPublicEditionNote = React.createClass({
if (isEditable || this.props.edition.public_note){ if (isEditable || this.props.edition.public_note){
return ( return (
<Form <Form
url={apiUrls.note_edition} url={ApiUrls.note_edition}
handleSuccess={this.showNotification}> handleSuccess={this.showNotification}>
<Property <Property
name='note' name='note'

View File

@ -16,7 +16,7 @@ import ReactS3FineUploader from './../ascribe_uploader/react_s3_fine_uploader';
import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions'; import GlobalNotificationActions from '../../actions/global_notification_actions';
import apiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
import AppConstants from '../../constants/application_constants'; import AppConstants from '../../constants/application_constants';
import { getCookie } from '../../utils/fetch_api_utils'; import { getCookie } from '../../utils/fetch_api_utils';
@ -133,7 +133,7 @@ let FileUploader = React.createClass({
pieceId: this.props.pieceId pieceId: this.props.pieceId
}} }}
createBlobRoutine={{ createBlobRoutine={{
url: apiUrls.blob_otherdatas, url: ApiUrls.blob_otherdatas,
pieceId: this.props.pieceId pieceId: this.props.pieceId
}} }}
validation={{ validation={{

View File

@ -46,7 +46,7 @@ let MediaContainer = React.createClass({
} }
panel={ panel={
<pre className=""> <pre className="">
{'<iframe width="560" height="' + height + '" src="http://embed.ascribe.io/content/' {'<iframe width="560" height="' + height + '" src="https://embed.ascribe.io/content/'
+ this.props.content.bitcoin_id + '" frameborder="0" allowfullscreen></iframe>'} + this.props.content.bitcoin_id + '" frameborder="0" allowfullscreen></iframe>'}
</pre> </pre>
}/> }/>

View File

@ -79,12 +79,14 @@ let Piece = React.createClass({
handleEditionCreationSuccess() { handleEditionCreationSuccess() {
PieceActions.updateProperty({key: 'num_editions', value: 0}); PieceActions.updateProperty({key: 'num_editions', value: 0});
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc); PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
this.toggleCreateEditionsDialog(); this.toggleCreateEditionsDialog();
}, },
handleDeleteSuccess(response) { handleDeleteSuccess(response) {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc); PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
// since we're deleting a piece, we just need to close // since we're deleting a piece, we just need to close
// all editions dialogs and not reload them // all editions dialogs and not reload them
@ -113,11 +115,20 @@ let Piece = React.createClass({
}, },
handlePollingSuccess(pieceId, numEditions) { handlePollingSuccess(pieceId, numEditions) {
// we need to refresh the num_editions property of the actual piece we're looking at
PieceActions.updateProperty({ PieceActions.updateProperty({
key: 'num_editions', key: 'num_editions',
value: numEditions value: numEditions
}); });
// as well as its representation in the collection
// btw.: It's not sufficient to just set num_editions to numEditions, since a single accordion
// list item also uses the firstEdition property which we can only get from the server in that case.
// Therefore we need to at least refetch the changed piece from the server or on our case simply all
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
let notification = new GlobalNotificationModel('Editions successfully created', 'success', 10000); let notification = new GlobalNotificationModel('Editions successfully created', 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },

View File

@ -8,12 +8,11 @@ import Property from '../ascribe_forms/property';
import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions'; import GlobalNotificationActions from '../../actions/global_notification_actions';
import apiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
let CreateEditionsForm = React.createClass({ let CreateEditionsForm = React.createClass({
propTypes: { propTypes: {
handleSuccess: React.PropTypes.func, handleSuccess: React.PropTypes.func,
pieceId: React.PropTypes.number pieceId: React.PropTypes.number
@ -38,7 +37,7 @@ let CreateEditionsForm = React.createClass({
return ( return (
<Form <Form
ref='form' ref='form'
url={apiUrls.editions} url={ApiUrls.editions}
getFormData={this.getFormData} getFormData={this.getFormData}
handleSuccess={this.handleSuccess} handleSuccess={this.handleSuccess}
spinner={ spinner={

View File

@ -4,23 +4,46 @@ import React from 'react';
import ReactAddons from 'react/addons'; import ReactAddons from 'react/addons';
import Button from 'react-bootstrap/lib/Button'; import Button from 'react-bootstrap/lib/Button';
import AlertDismissable from './alert';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import requests from '../../utils/requests'; import requests from '../../utils/requests';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
import { mergeOptionsWithDuplicates } from '../../utils/general_utils'; import { mergeOptionsWithDuplicates } from '../../utils/general_utils';
import AlertDismissable from './alert';
let Form = React.createClass({ let Form = React.createClass({
propTypes: { propTypes: {
url: React.PropTypes.string, url: React.PropTypes.string,
method: React.PropTypes.string,
buttonSubmitText: React.PropTypes.string,
handleSuccess: React.PropTypes.func, handleSuccess: React.PropTypes.func,
getFormData: React.PropTypes.func, getFormData: React.PropTypes.func,
children: React.PropTypes.oneOfType([ children: React.PropTypes.oneOfType([
React.PropTypes.object, React.PropTypes.object,
React.PropTypes.array React.PropTypes.array
]), ]),
className: React.PropTypes.string className: React.PropTypes.string,
spinner: React.PropTypes.element,
buttons: React.PropTypes.oneOfType([
React.PropTypes.element,
React.PropTypes.arrayOf(React.PropTypes.element)
]),
// You can use the form for inline requests, like the submit click on a button.
// For the form to then not display the error on top, you need to enable this option.
// It will make use of the GlobalNotification
isInline: React.PropTypes.bool
},
getDefaultProps() {
return {
method: 'post',
buttonSubmitText: 'SAVE'
};
}, },
getInitialState() { getInitialState() {
@ -30,6 +53,7 @@ let Form = React.createClass({
errors: [] errors: []
}; };
}, },
reset(){ reset(){
for (let ref in this.refs){ for (let ref in this.refs){
if (typeof this.refs[ref].reset === 'function'){ if (typeof this.refs[ref].reset === 'function'){
@ -38,22 +62,38 @@ let Form = React.createClass({
} }
this.setState(this.getInitialState()); this.setState(this.getInitialState());
}, },
submit(event){ submit(event){
if (event) {
if(event) {
event.preventDefault(); event.preventDefault();
} }
this.setState({submitted: true}); this.setState({submitted: true});
this.clearErrors(); this.clearErrors();
let action = (this.httpVerb && this.httpVerb()) || 'post';
window.setTimeout(() => this[action](), 100); // selecting http method based on props
if(this[this.props.method]) {
window.setTimeout(() => this[this.props.method](), 100);
} else {
throw new Error('This HTTP method is not supported by form.js (' + this.props.method + ')');
}
}, },
post(){
post() {
requests requests
.post(this.props.url, { body: this.getFormData() }) .post(this.props.url, { body: this.getFormData() })
.then(this.handleSuccess) .then(this.handleSuccess)
.catch(this.handleError); .catch(this.handleError);
}, },
delete() {
requests
.delete(this.props.url, this.getFormData())
.then(this.handleSuccess)
.catch(this.handleError);
},
getFormData(){ getFormData(){
let data = {}; let data = {};
for (let ref in this.refs){ for (let ref in this.refs){
@ -69,6 +109,7 @@ let Form = React.createClass({
handleChangeChild(){ handleChangeChild(){
this.setState({edited: true}); this.setState({edited: true});
}, },
handleSuccess(response){ handleSuccess(response){
if ('handleSuccess' in this.props){ if ('handleSuccess' in this.props){
this.props.handleSuccess(response); this.props.handleSuccess(response);
@ -78,8 +119,12 @@ let Form = React.createClass({
this.refs[ref].handleSuccess(); this.refs[ref].handleSuccess();
} }
} }
this.setState({edited: false, submitted: false}); this.setState({
edited: false,
submitted: false
});
}, },
handleError(err){ handleError(err){
if (err.json) { if (err.json) {
for (var input in err.json.errors){ for (var input in err.json.errors){
@ -91,11 +136,26 @@ let Form = React.createClass({
} }
} }
else { else {
console.logGlobal(err, false, this.getFormData()); let formData = this.getFormData();
this.setState({errors: [getLangText('Something went wrong, please try again later')]});
// sentry shouldn't post the user's password
if(formData.password) {
delete formData.password;
}
console.logGlobal(err, false, formData);
if(this.props.isInline) {
let notification = new GlobalNotificationModel(getLangText('Something went wrong, please try again later'), 'danger');
GlobalNotificationActions.appendGlobalNotification(notification);
} else {
this.setState({errors: [getLangText('Something went wrong, please try again later')]});
}
} }
this.setState({submitted: false}); this.setState({submitted: false});
}, },
clearErrors(){ clearErrors(){
for (var ref in this.refs){ for (var ref in this.refs){
if ('clearErrors' in this.refs[ref]){ if ('clearErrors' in this.refs[ref]){
@ -104,6 +164,7 @@ let Form = React.createClass({
} }
this.setState({errors: []}); this.setState({errors: []});
}, },
getButtons() { getButtons() {
if (this.state.submitted){ if (this.state.submitted){
return this.props.spinner; return this.props.spinner;
@ -117,7 +178,7 @@ let Form = React.createClass({
buttons = ( buttons = (
<div className="row" style={{margin: 0}}> <div className="row" style={{margin: 0}}>
<p className="pull-right"> <p className="pull-right">
<Button className="btn btn-default btn-sm ascribe-margin-1px" type="submit">SAVE</Button> <Button className="btn btn-default btn-sm ascribe-margin-1px" type="submit">{this.props.buttonSubmitText}</Button>
<Button className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" onClick={this.reset}>CANCEL</Button> <Button className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" onClick={this.reset}>CANCEL</Button>
</p> </p>
</div> </div>
@ -126,6 +187,7 @@ let Form = React.createClass({
} }
return buttons; return buttons;
}, },
getErrors() { getErrors() {
let errors = null; let errors = null;
if (this.state.errors.length > 0){ if (this.state.errors.length > 0){
@ -135,6 +197,7 @@ let Form = React.createClass({
} }
return errors; return errors;
}, },
renderChildren() { renderChildren() {
return ReactAddons.Children.map(this.props.children, (child) => { return ReactAddons.Children.map(this.props.children, (child) => {
if (child) { if (child) {
@ -145,6 +208,7 @@ let Form = React.createClass({
} }
}); });
}, },
render() { render() {
let className = 'ascribe-form'; let className = 'ascribe-form';

View File

@ -8,7 +8,6 @@ import Form from './form';
import Property from './property'; import Property from './property';
import InputTextAreaToggable from './input_textarea_toggable'; import InputTextAreaToggable from './input_textarea_toggable';
import AppConstants from '../../constants/application_constants'; import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils.js'; import { getLangText } from '../../utils/lang_utils.js';
@ -18,7 +17,6 @@ let ConsignForm = React.createClass({
url: React.PropTypes.string, url: React.PropTypes.string,
id: React.PropTypes.object, id: React.PropTypes.object,
message: React.PropTypes.string, message: React.PropTypes.string,
onRequestHide: React.PropTypes.func,
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func
}, },
@ -27,7 +25,6 @@ let ConsignForm = React.createClass({
}, },
render() { render() {
return ( return (
<Form <Form
ref='form' ref='form'
@ -39,11 +36,9 @@ let ConsignForm = React.createClass({
<p className="pull-right"> <p className="pull-right">
<Button <Button
className="btn btn-default btn-sm ascribe-margin-1px" className="btn btn-default btn-sm ascribe-margin-1px"
type="submit">{getLangText('CONSIGN')}</Button> type="submit">
<Button {getLangText('CONSIGN')}
className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" </Button>
style={{marginLeft: '0'}}
onClick={this.props.onRequestHide}>{getLangText('CLOSE')}</Button>
</p> </p>
</div>} </div>}
spinner={ spinner={

View File

@ -2,33 +2,65 @@
import React from 'react'; import React from 'react';
import requests from '../../utils/requests'; import Form from './form';
import ApiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin'; import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
let EditionDeleteForm = React.createClass({ let EditionDeleteForm = React.createClass({
mixins: [FormMixin], propTypes: {
editions: React.PropTypes.arrayOf(React.PropTypes.object),
url() { // Propagated by ModalWrapper in most cases
return requests.prepareUrl(ApiUrls.edition_delete, {edition_id: this.getBitcoinIds().join()}); handleSuccess: React.PropTypes.func
},
httpVerb(){
return 'delete';
}, },
renderForm () { getBitcoinIds() {
return this.props.editions.map(function(edition){
return edition.bitcoin_id;
});
},
// Since this form can be used for either deleting a single edition or multiple
// we need to call getBitcoinIds to get the value of edition_id
getFormData() {
return {
edition_id: this.getBitcoinIds().join(',')
};
},
render () {
return ( return (
<div className="modal-body"> <Form
ref='form'
url={ApiUrls.edition_delete}
getFormData={this.getFormData}
method="delete"
handleSuccess={this.props.handleSuccess}
buttons={
<div className="modal-footer">
<p className="pull-right">
<button
type="submit"
className="btn btn-danger btn-delete btn-sm ascribe-margin-1px"
onClick={this.submit}>
{getLangText('YES, DELETE')}
</button>
</p>
</div>
}
spinner={
<div className="modal-footer">
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
</div>
}>
<p>{getLangText('Are you sure you would like to permanently delete this edition')}&#63;</p> <p>{getLangText('Are you sure you would like to permanently delete this edition')}&#63;</p>
<p>{getLangText('This is an irrevocable action%s', '.')}</p> <p>{getLangText('This is an irrevocable action%s', '.')}</p>
<div className="modal-footer"> </Form>
<button type="submit" className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" onClick={this.submit}>{getLangText('YES, DELETE')}</button>
<button className="btn btn-default btn-sm ascribe-margin-1px" style={{marginLeft: '0'}}
onClick={this.props.onRequestHide}>{getLangText('CLOSE')}</button>
</div>
</div>
); );
} }
}); });

View File

@ -2,37 +2,56 @@
import React from 'react'; import React from 'react';
import requests from '../../utils/requests'; import Form from '../ascribe_forms/form';
import ApiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin'; import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
let PieceDeleteForm = React.createClass({ let PieceDeleteForm = React.createClass({
propTypes: { propTypes: {
pieceId: React.PropTypes.number pieceId: React.PropTypes.number,
// Propagated by ModalWrapper in most cases
handleSuccess: React.PropTypes.func
}, },
mixins: [FormMixin], getFormData() {
return {
url() { piece_id: this.props.pieceId
return requests.prepareUrl(ApiUrls.piece, {piece_id: this.props.pieceId}); };
}, },
httpVerb() { render() {
return 'delete';
},
renderForm () {
return ( return (
<div className="modal-body"> <Form
ref='form'
url={ApiUrls.piece}
getFormData={this.getFormData}
method="delete"
handleSuccess={this.props.handleSuccess}
buttons={
<div className="modal-footer">
<p className="pull-right">
<button
type="submit"
className="btn btn-danger btn-delete btn-sm ascribe-margin-1px"
onClick={this.submit}>
{getLangText('YES, DELETE')}
</button>
</p>
</div>
}
spinner={
<div className="modal-footer">
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
</div>
}>
<p>{getLangText('Are you sure you would like to permanently delete this piece')}&#63;</p> <p>{getLangText('Are you sure you would like to permanently delete this piece')}&#63;</p>
<p>{getLangText('This is an irrevocable action%s', '.')}</p> <p>{getLangText('This is an irrevocable action%s', '.')}</p>
<div className="modal-footer"> </Form>
<button type="submit" className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" onClick={this.submit}>{getLangText('YES, DELETE')}</button>
<button className="btn btn-default btn-sm ascribe-margin-1px" style={{marginLeft: '0'}}
onClick={this.props.onRequestHide}>{getLangText('CLOSE')}</button>
</div>
</div>
); );
} }
}); });

View File

@ -23,7 +23,6 @@ let LoanForm = React.createClass({
url: React.PropTypes.string, url: React.PropTypes.string,
id: React.PropTypes.object, id: React.PropTypes.object,
message: React.PropTypes.string, message: React.PropTypes.string,
onRequestHide: React.PropTypes.func,
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func
}, },
@ -33,6 +32,7 @@ let LoanForm = React.createClass({
componentDidMount() { componentDidMount() {
LoanContractStore.listen(this.onChange); LoanContractStore.listen(this.onChange);
LoanContractActions.flushLoanContract();
}, },
componentWillUnmount() { componentWillUnmount() {
@ -53,13 +53,16 @@ let LoanForm = React.createClass({
getContractCheckbox() { getContractCheckbox() {
if(this.state.contractKey && this.state.contractUrl) { if(this.state.contractKey && this.state.contractUrl) {
// we need to define a key on the InputCheckboxes as otherwise
// react is not rerendering them on a store switch and is keeping
// the default value of the component (which is in that case true)
return ( return (
<Property <Property
name="terms" name="terms"
className="ascribe-settings-property-collapsible-toggle" className="ascribe-settings-property-collapsible-toggle"
style={{paddingBottom: 0}}> style={{paddingBottom: 0}}>
<InputCheckbox <InputCheckbox
key='contract_terms' key="terms_explicitly"
defaultChecked={false}> defaultChecked={false}>
<span> <span>
{getLangText('I agree to the')}&nbsp; {getLangText('I agree to the')}&nbsp;
@ -77,27 +80,13 @@ let LoanForm = React.createClass({
style={{paddingBottom: 0}} style={{paddingBottom: 0}}
hidden={true}> hidden={true}>
<InputCheckbox <InputCheckbox
key='implicit_terms' key="terms_implicitly"
defaultChecked={true} /> defaultChecked={true} />
</Property> </Property>
); );
} }
}, },
onRequestHide() {
// Since the modal can be opened without sending it to the server
// and therefore clearing the store,
// we'll need to make sure to flush the store once the
// modal unmounts
LoanContractActions.updateLoanContract({
contractUrl: null,
contractEmail: null,
contractKey: null
});
this.props.onRequestHide();
},
render() { render() {
return ( return (
@ -111,11 +100,9 @@ let LoanForm = React.createClass({
<p className="pull-right"> <p className="pull-right">
<Button <Button
className="btn btn-default btn-sm ascribe-margin-1px" className="btn btn-default btn-sm ascribe-margin-1px"
type="submit">{getLangText('LOAN')}</Button> type="submit">
<Button {getLangText('LOAN')}
className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" </Button>
style={{marginLeft: '0'}}
onClick={this.onRequestHide}>{getLangText('CLOSE')}</Button>
</p> </p>
</div>} </div>}
spinner={ spinner={

View File

@ -11,16 +11,14 @@ import UserActions from '../../actions/user_actions';
import Form from './form'; import Form from './form';
import Property from './property'; import Property from './property';
import FormPropertyHeader from './form_property_header';
import apiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
import AppConstants from '../../constants/application_constants'; import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
let LoginForm = React.createClass({ let LoginForm = React.createClass({
propTypes: { propTypes: {
headerMessage: React.PropTypes.string, headerMessage: React.PropTypes.string,
submitMessage: React.PropTypes.string, submitMessage: React.PropTypes.string,
@ -101,7 +99,7 @@ let LoginForm = React.createClass({
<Form <Form
className="ascribe-form-bordered" className="ascribe-form-bordered"
ref="loginForm" ref="loginForm"
url={apiUrls.users_login} url={ApiUrls.users_login}
handleSuccess={this.handleSuccess} handleSuccess={this.handleSuccess}
buttons={ buttons={
<button <button
@ -114,9 +112,9 @@ let LoginForm = React.createClass({
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" /> <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</span> </span>
}> }>
<FormPropertyHeader> <div className="ascribe-form-header">
<h3>{this.props.headerMessage}</h3> <h3>{this.props.headerMessage}</h3>
</FormPropertyHeader> </div>
<Property <Property
name='email' name='email'
label={getLangText('Email')}> label={getLangText('Email')}>

View File

@ -3,9 +3,9 @@
import React from 'react'; import React from 'react';
import requests from '../../utils/requests'; import requests from '../../utils/requests';
import { getLangText } from '../../utils/lang_utils.js' import { getLangText } from '../../utils/lang_utils.js';
import apiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
import Form from './form'; import Form from './form';
import Property from './property'; import Property from './property';
@ -20,7 +20,8 @@ let PieceExtraDataForm = React.createClass({
title: React.PropTypes.string, title: React.PropTypes.string,
editable: React.PropTypes.bool editable: React.PropTypes.bool
}, },
getFormData(){
getFormData() {
let extradata = {}; let extradata = {};
extradata[this.props.name] = this.refs.form.refs[this.props.name].state.value; extradata[this.props.name] = this.refs.form.refs[this.props.name].state.value;
return { return {
@ -28,12 +29,13 @@ let PieceExtraDataForm = React.createClass({
piece_id: this.props.pieceId piece_id: this.props.pieceId
}; };
}, },
render() { render() {
let defaultValue = this.props.extraData[this.props.name] || ''; let defaultValue = this.props.extraData[this.props.name] || '';
if (defaultValue.length === 0 && !this.props.editable){ if (defaultValue.length === 0 && !this.props.editable){
return null; return null;
} }
let url = requests.prepareUrl(apiUrls.piece_extradata, {piece_id: this.props.pieceId}); let url = requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: this.props.pieceId});
return ( return (
<Form <Form
ref='form' ref='form'

View File

@ -1,22 +0,0 @@
'use strict';
import React from 'react';
let FormPropertyHeader = React.createClass({
propTypes: {
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
])
},
render() {
return (
<div className="ascribe-form-header">
{this.props.children}
</div>
);
}
});
export default FormPropertyHeader;

View File

@ -7,12 +7,11 @@ import UserActions from '../../actions/user_actions';
import Form from './form'; import Form from './form';
import Property from './property'; import Property from './property';
import FormPropertyHeader from './form_property_header';
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader'; import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
import AppConstants from '../../constants/application_constants'; import AppConstants from '../../constants/application_constants';
import apiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
import { getCookie } from '../../utils/fetch_api_utils'; import { getCookie } from '../../utils/fetch_api_utils';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
@ -96,7 +95,7 @@ let RegisterPieceForm = React.createClass({
<Form <Form
className="ascribe-form-bordered" className="ascribe-form-bordered"
ref='form' ref='form'
url={apiUrls.pieces_list} url={ApiUrls.pieces_list}
getFormData={this.getFormData} getFormData={this.getFormData}
handleSuccess={this.props.handleSuccess} handleSuccess={this.props.handleSuccess}
buttons={<button buttons={<button
@ -110,9 +109,9 @@ let RegisterPieceForm = React.createClass({
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" /> <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</span> </span>
}> }>
<FormPropertyHeader> <div className="ascribe-form-header">
<h3>{this.props.headerMessage}</h3> <h3>{this.props.headerMessage}</h3>
</FormPropertyHeader> </div>
<Property <Property
ignoreFocus={true}> ignoreFocus={true}>
<FileUploader <FileUploader
@ -180,7 +179,7 @@ let FileUploader = React.createClass({
fileClass: 'digitalwork' fileClass: 'digitalwork'
}} }}
createBlobRoutine={{ createBlobRoutine={{
url: apiUrls.blob_digitalworks url: ApiUrls.blob_digitalworks
}} }}
submitKey={this.props.submitKey} submitKey={this.props.submitKey}
validation={{ validation={{

View File

@ -2,34 +2,63 @@
import React from 'react'; import React from 'react';
import { getLangText } from '../../utils/lang_utils.js'; import Form from './form';
import requests from '../../utils/requests';
import apiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin'; import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils';
let EditionRemoveFromCollectionForm = React.createClass({ let EditionRemoveFromCollectionForm = React.createClass({
propTypes: {
editions: React.PropTypes.arrayOf(React.PropTypes.object),
mixins: [FormMixin], // Propagated by ModalWrapper in most cases
handleSuccess: React.PropTypes.func
url() {
return requests.prepareUrl(apiUrls.edition_remove_from_collection, {edition_id: this.getBitcoinIds().join()});
},
httpVerb(){
return 'delete';
}, },
renderForm () { getBitcoinIds() {
return this.props.editions.map(function(edition){
return edition.bitcoin_id;
});
},
// Since this form can be used for either removing a single edition or multiple
// we need to call getBitcoinIds to get the value of edition_id
getFormData() {
return {
edition_id: this.getBitcoinIds().join(',')
};
},
render() {
return ( return (
<div className="modal-body"> <Form
ref='form'
url={ApiUrls.edition_remove_from_collection}
getFormData={this.getFormData}
method="delete"
handleSuccess={this.props.handleSuccess}
buttons={
<div className="modal-footer">
<p className="pull-right">
<button
type="submit"
className="btn btn-danger btn-delete btn-sm ascribe-margin-1px"
onClick={this.submit}>
{getLangText('YES, REMOVE')}
</button>
</p>
</div>
}
spinner={
<div className="modal-footer">
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
</div>
}>
<p>{getLangText('Are you sure you would like to remove these editions from your collection')}&#63;</p> <p>{getLangText('Are you sure you would like to remove these editions from your collection')}&#63;</p>
<p>{getLangText('This is an irrevocable action%s', '.')}</p> <p>{getLangText('This is an irrevocable action%s', '.')}</p>
<div className="modal-footer"> </Form>
<button type="submit" className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" onClick={this.submit}>{getLangText('YES, REMOVE')}</button>
<button className="btn btn-default btn-sm ascribe-margin-1px" style={{marginLeft: '0'}}
onClick={this.props.onRequestHide}>{getLangText('CLOSE')}</button>
</div>
</div>
); );
} }
}); });

View File

@ -2,38 +2,56 @@
import React from 'react'; import React from 'react';
import { getLangText } from '../../utils/lang_utils.js'; import Form from './form';
import requests from '../../utils/requests';
import apiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin'; import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils';
let PieceRemoveFromCollectionForm = React.createClass({ let PieceRemoveFromCollectionForm = React.createClass({
propTypes: { propTypes: {
pieceId: React.PropTypes.number pieceId: React.PropTypes.number,
// Propagated by ModalWrapper in most cases
handleSuccess: React.PropTypes.func
}, },
mixins: [FormMixin], getFormData() {
return {
url() { piece_id: this.props.pieceId
return requests.prepareUrl(apiUrls.piece_remove_from_collection, {piece_id: this.props.pieceId}); };
},
httpVerb(){
return 'delete';
}, },
renderForm () { render () {
return ( return (
<div className="modal-body"> <Form
ref='form'
url={ApiUrls.piece_remove_from_collection}
getFormData={this.getFormData}
method="delete"
handleSuccess={this.props.handleSuccess}
buttons={
<div className="modal-footer">
<p className="pull-right">
<button
type="submit"
className="btn btn-danger btn-delete btn-sm ascribe-margin-1px"
onClick={this.submit}>
{getLangText('YES, REMOVE')}
</button>
</p>
</div>
}
spinner={
<div className="modal-footer">
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
</div>
}>
<p>{getLangText('Are you sure you would like to remove this piece from your collection')}&#63;</p> <p>{getLangText('Are you sure you would like to remove this piece from your collection')}&#63;</p>
<p>{getLangText('This is an irrevocable action%s', '.')}</p> <p>{getLangText('This is an irrevocable action%s', '.')}</p>
<div className="modal-footer"> </Form>
<button type="submit" className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" onClick={this.submit}>{getLangText('YES, REMOVE')}</button>
<button className="btn btn-default btn-sm ascribe-margin-1px" style={{marginLeft: '0'}}
onClick={this.props.onRequestHide}>{getLangText('CLOSE')}</button>
</div>
</div>
); );
} }
}); });

View File

@ -2,48 +2,48 @@
import React from 'react'; import React from 'react';
import Alert from 'react-bootstrap/lib/Alert';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import AclButton from './../ascribe_buttons/acl_button'; import AclButton from './../ascribe_buttons/acl_button';
import ActionPanel from '../ascribe_panel/action_panel';
import Form from './form';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import ApiUrls from '../../constants/api_urls';
import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils.js'; import { getLangText } from '../../utils/lang_utils.js';
let RequestActionForm = React.createClass({
mixins: [FormMixin],
url(e){ let RequestActionForm = React.createClass({
let edition = this.props.editions[0]; propTypes: {
if (e.target.id === 'request_accept'){ editions: React.PropTypes.arrayOf(React.PropTypes.object),
if (edition.request_action === 'consign'){ currentUser: React.PropTypes.object,
return apiUrls.ownership_consigns_confirm; handleSuccess: React.PropTypes.func
}
else if (edition.request_action === 'unconsign'){
return apiUrls.ownership_unconsigns;
}
else if (edition.request_action === 'loan'){
return apiUrls.ownership_loans_confirm;
}
}
else if(e.target.id === 'request_deny'){
if (edition.request_action === 'consign') {
return apiUrls.ownership_consigns_deny;
}
else if (edition.request_action === 'unconsign') {
return apiUrls.ownership_unconsigns_deny;
}
else if (edition.request_action === 'loan') {
return apiUrls.ownership_loans_deny;
}
}
}, },
handleRequest: function(e){ getUrls() {
e.preventDefault(); let edition = this.props.editions[0];
this.submit(e); let urls = {};
if (edition.request_action === 'consign'){
urls.accept = ApiUrls.ownership_consigns_confirm;
urls.deny = ApiUrls.ownership_consigns_deny;
} else if (edition.request_action === 'unconsign'){
urls.accept = ApiUrls.ownership_unconsigns;
urls.deny = ApiUrls.ownership_unconsigns_deny;
} else if (edition.request_action === 'loan'){
urls.accept = ApiUrls.ownership_loans_confirm;
urls.deny = ApiUrls.ownership_loans_deny;
}
return urls;
},
getBitcoinIds(){
return this.props.editions.map(function(edition){
return edition.bitcoin_id;
});
}, },
getFormData() { getFormData() {
@ -52,16 +52,35 @@ let RequestActionForm = React.createClass({
}; };
}, },
renderForm() { showNotification(option, action, owner) {
return () => {
let message = getLangText('You have successfully') + ' ' + option + ' the ' + action + ' request ' + getLangText('from') + ' ' + owner;
let notification = new GlobalNotificationModel(message, 'success');
GlobalNotificationActions.appendGlobalNotification(notification);
if(this.props.handleSuccess) {
this.props.handleSuccess();
}
};
},
getContent() {
let edition = this.props.editions[0]; let edition = this.props.editions[0];
let buttonAccept = ( let message = edition.owner + ' ' + getLangText('requests you') + ' ' + edition.request_action + ' ' + getLangText('this edition%s', '.');
<div id="request_accept"
onClick={this.handleRequest} return (
className='btn btn-default btn-sm ascribe-margin-1px'>{getLangText('ACCEPT')} <span>
</div>); {message}
if (edition.request_action === 'unconsign'){ </span>
console.log(this.props) );
buttonAccept = ( },
getAcceptButtonForm(urls) {
let edition = this.props.editions[0];
if(edition.request_action === 'unconsign') {
return (
<AclButton <AclButton
availableAcls={{'acl_unconsign': true}} availableAcls={{'acl_unconsign': true}}
action="acl_unconsign" action="acl_unconsign"
@ -69,31 +88,54 @@ let RequestActionForm = React.createClass({
currentUser={this.props.currentUser} currentUser={this.props.currentUser}
handleSuccess={this.props.handleSuccess} /> handleSuccess={this.props.handleSuccess} />
); );
} } else {
let buttons = ( return (
<span> <Form
<span> url={urls.accept}
{buttonAccept} getFormData={this.getFormData}
</span> handleSuccess={this.showNotification(getLangText('accepted'), edition.request_action, edition.owner)}
<span> isInline={true}
<div id="request_deny" onClick={this.handleRequest} className='btn btn-danger btn-delete btn-sm ascribe-margin-1px'>{getLangText('REJECT')}</div> className='inline pull-right'>
</span> <button
</span> type="submit"
); className='btn btn-default btn-sm ascribe-margin-1px'>
if (this.state.submitted){ {getLangText('ACCEPT')}
buttons = ( </button>
<span> </Form>
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
</span>
); );
} }
},
getButtonForm() {
let edition = this.props.editions[0];
let urls = this.getUrls();
let acceptButtonForm = this.getAcceptButtonForm(urls);
return ( return (
<Alert bsStyle='warning'> <div>
<div style={{textAlign: 'center'}}> <Form
<div>{ edition.owner } {getLangText('requests you')} { edition.request_action } {getLangText('this edition%s', '.')}&nbsp;&nbsp;</div> url={urls.deny}
{buttons} isInline={true}
</div> getFormData={this.getFormData}
</Alert> handleSuccess={this.showNotification(getLangText('denied'), edition.request_action, edition.owner)}
className='inline pull-right'>
<button
type="submit"
className='btn btn-danger btn-delete btn-sm ascribe-margin-1px'>
{getLangText('REJECT')}
</button>
</Form>
{acceptButtonForm}
</div>
);
},
render() {
return (
<ActionPanel
content={this.getContent()}
buttons={this.getButtonForm()}/>
); );
} }
}); });

View File

@ -2,14 +2,14 @@
import React from 'react'; import React from 'react';
import Form from './form'; import Form from './form';
import Property from './property'; import Property from './property';
import InputTextAreaToggable from './input_textarea_toggable'; import InputTextAreaToggable from './input_textarea_toggable';
import Button from 'react-bootstrap/lib/Button'; import Button from 'react-bootstrap/lib/Button';
import AppConstants from '../../constants/application_constants'; import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils.js'; import { getLangText } from '../../utils/lang_utils.js';
@ -20,7 +20,6 @@ let ShareForm = React.createClass({
message: React.PropTypes.string, message: React.PropTypes.string,
editions: React.PropTypes.array, editions: React.PropTypes.array,
currentUser: React.PropTypes.object, currentUser: React.PropTypes.object,
onRequestHide: React.PropTypes.func,
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func
}, },
@ -41,11 +40,9 @@ let ShareForm = React.createClass({
<p className="pull-right"> <p className="pull-right">
<Button <Button
className="btn btn-default btn-sm ascribe-margin-1px" className="btn btn-default btn-sm ascribe-margin-1px"
type="submit">SHARE</Button> type="submit">
<Button SHARE
className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" </Button>
style={{marginLeft: '0'}}
onClick={this.props.onRequestHide}>CLOSE</Button>
</p> </p>
</div>} </div>}
spinner={ spinner={

View File

@ -12,10 +12,9 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
import Form from './form'; import Form from './form';
import Property from './property'; import Property from './property';
import FormPropertyHeader from './form_property_header';
import InputCheckbox from './input_checkbox'; import InputCheckbox from './input_checkbox';
import apiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
let SignupForm = React.createClass({ let SignupForm = React.createClass({
@ -27,7 +26,7 @@ let SignupForm = React.createClass({
children: React.PropTypes.element children: React.PropTypes.element
}, },
mixins: [Router.Navigation], mixins: [Router.Navigation, Router.State],
getDefaultProps() { getDefaultProps() {
return { return {
@ -35,7 +34,6 @@ let SignupForm = React.createClass({
submitMessage: getLangText('Sign up') submitMessage: getLangText('Sign up')
}; };
}, },
getInitialState() { getInitialState() {
return UserStore.getState(); return UserStore.getState();
}, },
@ -58,21 +56,27 @@ let SignupForm = React.createClass({
}, },
handleSuccess(response){ handleSuccess(response){
let notification = new GlobalNotificationModel(getLangText('Sign up successful'), 'success', 50000); if (response.user) {
GlobalNotificationActions.appendGlobalNotification(notification); let notification = new GlobalNotificationModel(getLangText('Sign up successful'), 'success', 50000);
this.props.handleSuccess(getLangText('We sent an email to your address') + ' ' + response.user.email + ', ' + getLangText('please confirm') + '.'); GlobalNotificationActions.appendGlobalNotification(notification);
this.props.handleSuccess(getLangText('We sent an email to your address') + ' ' + response.user.email + ', ' + getLangText('please confirm') + '.');
}
else if (response.redirect) {
this.transitionTo('pieces');
}
}, },
render() { render() {
let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' + let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' +
getLangText('This password is securing your digital property like a bank account') + '.\n ' + getLangText('This password is securing your digital property like a bank account') + '.\n ' +
getLangText('Store it in a safe place') + '!'; getLangText('Store it in a safe place') + '!';
let email = this.getQuery().email ? this.getQuery().email : null;
return ( return (
<Form <Form
className="ascribe-form-bordered" className="ascribe-form-bordered"
ref='form' ref='form'
url={apiUrls.users_signup} url={ApiUrls.users_signup}
getFormData={this.getQuery}
handleSuccess={this.handleSuccess} handleSuccess={this.handleSuccess}
buttons={ buttons={
<button type="submit" className="btn ascribe-btn ascribe-btn-login"> <button type="submit" className="btn ascribe-btn ascribe-btn-login">
@ -83,9 +87,9 @@ let SignupForm = React.createClass({
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" /> <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</span> </span>
}> }>
<FormPropertyHeader> <div className="ascribe-form-header">
<h3>{this.props.headerMessage}</h3> <h3>{this.props.headerMessage}</h3>
</FormPropertyHeader> </div>
<Property <Property
name='email' name='email'
label={getLangText('Email')}> label={getLangText('Email')}>
@ -93,6 +97,7 @@ let SignupForm = React.createClass({
type="email" type="email"
placeholder={getLangText('(e.g. andy@warhol.co.uk)')} placeholder={getLangText('(e.g. andy@warhol.co.uk)')}
autoComplete="on" autoComplete="on"
defaultValue={email}
required/> required/>
</Property> </Property>
<Property <Property

View File

@ -19,10 +19,7 @@ import requests from '../../utils/requests';
let PieceSubmitToPrizeForm = React.createClass({ let PieceSubmitToPrizeForm = React.createClass({
propTypes: { propTypes: {
piece: React.PropTypes.object, piece: React.PropTypes.object,
handleSuccess: React.PropTypes.func, handleSuccess: React.PropTypes.func
// this is set by ModalWrapper automatically
onRequestHide: React.PropTypes.func
}, },
render() { render() {
@ -36,7 +33,9 @@ let PieceSubmitToPrizeForm = React.createClass({
<p className="pull-right"> <p className="pull-right">
<button <button
className="btn btn-default btn-sm ascribe-margin-1px" className="btn btn-default btn-sm ascribe-margin-1px"
type="submit">{getLangText('SUBMIT TO PRIZE')}</button> type="submit">
{getLangText('SUBMIT TO PRIZE')}
</button>
</p> </p>
</div>} </div>}
spinner={ spinner={
@ -80,7 +79,6 @@ let PieceSubmitToPrizeForm = React.createClass({
<p>{getLangText('Are you sure you want to submit to the prize?')}</p> <p>{getLangText('Are you sure you want to submit to the prize?')}</p>
<p>{getLangText('This is an irrevocable action%s', '.')}</p> <p>{getLangText('This is an irrevocable action%s', '.')}</p>
</Alert> </Alert>
</Form> </Form>
); );
} }

View File

@ -21,7 +21,6 @@ let TransferForm = React.createClass({
message: React.PropTypes.string, message: React.PropTypes.string,
editions: React.PropTypes.array, editions: React.PropTypes.array,
currentUser: React.PropTypes.object, currentUser: React.PropTypes.object,
onRequestHide: React.PropTypes.func,
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func
}, },
@ -42,11 +41,9 @@ let TransferForm = React.createClass({
<p className="pull-right"> <p className="pull-right">
<Button <Button
className="btn btn-default btn-sm ascribe-margin-1px" className="btn btn-default btn-sm ascribe-margin-1px"
type="submit">{getLangText('TRANSFER')}</Button> type="submit">
<Button {getLangText('TRANSFER')}
className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" </Button>
style={{marginLeft: '0'}}
onClick={this.props.onRequestHide}>{getLangText('CLOSE')}</Button>
</p> </p>
</div>} </div>}
spinner={ spinner={

View File

@ -18,7 +18,6 @@ let UnConsignForm = React.createClass({
id: React.PropTypes.object, id: React.PropTypes.object,
message: React.PropTypes.string, message: React.PropTypes.string,
editions: React.PropTypes.array, editions: React.PropTypes.array,
onRequestHide: React.PropTypes.func,
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func
}, },
@ -39,11 +38,9 @@ let UnConsignForm = React.createClass({
<p className="pull-right"> <p className="pull-right">
<Button <Button
className="btn btn-default btn-sm ascribe-margin-1px" className="btn btn-default btn-sm ascribe-margin-1px"
type="submit">{getLangText('UNCONSIGN')}</Button> type="submit">
<Button {getLangText('UNCONSIGN')}
className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" </Button>
style={{marginLeft: '0'}}
onClick={this.props.onRequestHide}>{getLangText('CLOSE')}</Button>
</p> </p>
</div>} </div>}
spinner={ spinner={

View File

@ -3,7 +3,6 @@
import React from 'react'; import React from 'react';
import Button from 'react-bootstrap/lib/Button'; import Button from 'react-bootstrap/lib/Button';
import Alert from 'react-bootstrap/lib/Alert';
import Form from './form'; import Form from './form';
import Property from './property'; import Property from './property';
@ -19,7 +18,6 @@ let UnConsignRequestForm = React.createClass({
url: React.PropTypes.string, url: React.PropTypes.string,
id: React.PropTypes.object, id: React.PropTypes.object,
message: React.PropTypes.string, message: React.PropTypes.string,
onRequestHide: React.PropTypes.func,
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func
}, },
@ -40,11 +38,9 @@ let UnConsignRequestForm = React.createClass({
<p className="pull-right"> <p className="pull-right">
<Button <Button
className="btn btn-default btn-sm ascribe-margin-1px" className="btn btn-default btn-sm ascribe-margin-1px"
type="submit">{getLangText('REQUEST UNCONSIGN')}</Button> type="submit">
<Button {getLangText('REQUEST UNCONSIGN')}
className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" </Button>
style={{marginLeft: '0'}}
onClick={this.props.onRequestHide}>{getLangText('CLOSE')}</Button>
</p> </p>
</div>} </div>}
spinner={ spinner={

View File

@ -7,7 +7,8 @@ import DatePicker from 'react-datepicker/dist/react-datepicker';
let InputDate = React.createClass({ let InputDate = React.createClass({
propTypes: { propTypes: {
submitted: React.PropTypes.bool, submitted: React.PropTypes.bool,
placeholderText: React.PropTypes.string placeholderText: React.PropTypes.string,
onChange: React.PropTypes.func
}, },
getInitialState() { getInitialState() {

View File

@ -5,7 +5,6 @@ import React from 'react';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
let InputTextAreaToggable = React.createClass({ let InputTextAreaToggable = React.createClass({
propTypes: { propTypes: {
editable: React.PropTypes.bool.isRequired, editable: React.PropTypes.bool.isRequired,
rows: React.PropTypes.number.isRequired, rows: React.PropTypes.number.isRequired,

View File

@ -70,23 +70,22 @@ let Property = React.createClass({
}); });
} }
if(!this.state.initialValue) { if(!this.state.initialValue && childInput.props.defaultValue) {
this.setState({ this.setState({
initialValue: childInput.defaultValue initialValue: childInput.props.defaultValue
}); });
} }
}, },
reset(){ reset() {
// maybe do reset by reload instead of front end state? // maybe do reset by reload instead of front end state?
this.setState({value: this.state.initialValue}); this.setState({value: this.state.initialValue});
if (this.refs.input.state){
// This is probably not the right way but easy fix // resets the value of a custom react component input
this.refs.input.state.value = this.state.initialValue; this.refs.input.state.value = this.state.initialValue;
}
else{ // resets the value of a plain HTML5 input
this.refs.input.getDOMNode().value = this.state.initialValue; this.refs.input.getDOMNode().value = this.state.initialValue;
}
}, },

View File

@ -3,11 +3,9 @@
import React from 'react'; import React from 'react';
import ReactAddons from 'react/addons'; import ReactAddons from 'react/addons';
import CollapsibleMixin from 'react-bootstrap/lib/CollapsibleMixin';
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
import Tooltip from 'react-bootstrap/lib/Tooltip'; import Tooltip from 'react-bootstrap/lib/Tooltip';
import Panel from 'react-bootstrap/lib/Panel';
import classNames from 'classnames';
let PropertyCollapsile = React.createClass({ let PropertyCollapsile = React.createClass({
@ -17,22 +15,12 @@ let PropertyCollapsile = React.createClass({
tooltip: React.PropTypes.string tooltip: React.PropTypes.string
}, },
mixins: [CollapsibleMixin],
getInitialState() { getInitialState() {
return { return {
show: false show: false
}; };
}, },
getCollapsibleDOMNode(){
return React.findDOMNode(this.refs.panel);
},
getCollapsibleDimensionValue(){
return React.findDOMNode(this.refs.panel).scrollHeight;
},
handleFocus() { handleFocus() {
this.refs.checkboxCollapsible.getDOMNode().checked = !this.refs.checkboxCollapsible.getDOMNode().checked; this.refs.checkboxCollapsible.getDOMNode().checked = !this.refs.checkboxCollapsible.getDOMNode().checked;
this.setState({ this.setState({
@ -85,11 +73,14 @@ let PropertyCollapsile = React.createClass({
<span className="checkbox"> {this.props.checkboxLabel}</span> <span className="checkbox"> {this.props.checkboxLabel}</span>
</div> </div>
</OverlayTrigger> </OverlayTrigger>
<div <Panel
className={classNames(this.getCollapsibleClassSet()) + ' ascribe-settings-property'} collapsible
ref="panel"> expanded={this.state.show}
className="bs-custom-panel">
<div className="ascribe-settings-property">
{this.renderChildren()} {this.renderChildren()}
</div> </div>
</Panel>
</div> </div>
); );
} }

View File

@ -3,6 +3,8 @@
import React from 'react'; import React from 'react';
import Q from 'q'; import Q from 'q';
import { escapeHTML } from '../../utils/general_utils';
import InjectInHeadMixin from '../../mixins/inject_in_head_mixin'; import InjectInHeadMixin from '../../mixins/inject_in_head_mixin';
import Panel from 'react-bootstrap/lib/Panel'; import Panel from 'react-bootstrap/lib/Panel';
import ProgressBar from 'react-bootstrap/lib/ProgressBar'; import ProgressBar from 'react-bootstrap/lib/ProgressBar';
@ -86,7 +88,29 @@ let Audio = React.createClass({
} }
}); });
let Video = React.createClass({ let Video = React.createClass({
/**
* The solution here is a bit convoluted.
* ReactJS is responsible for DOM manipulation but VideoJS updates the DOM
* to install itself to display the video, therefore ReactJS complains that we are
* changing the DOM under its feet.
*
* What we do is the following:
* 1) set `state.ready = false`
* 2) render the cover using the `<Image />` component (because ready is false)
* 3) on `componentDidMount`, we load the external `css` and `js` resources using
* the `InjectInHeadMixin`, attaching a function to `Promise.then` to change
* `state.ready` to true
* 4) when the promise is succesfully resolved, we change `state.ready` triggering
* a re-render
* 5) the new render calls `prepareVideoHTML` to get the raw HTML of the video tag
* (that will be later processed and expanded by VideoJS)
* 6) `componentDidUpdate` is called after `render`, setting `state.videoMounted` to true,
* to avoid re-installing the VideoJS library
* 7) to close the lifecycle, `componentWillUnmount` is called removing VideoJS from the DOM.
*/
propTypes: { propTypes: {
preview: React.PropTypes.string.isRequired, preview: React.PropTypes.string.isRequired,
url: React.PropTypes.string.isRequired, url: React.PropTypes.string.isRequired,
@ -97,7 +121,7 @@ let Video = React.createClass({
mixins: [InjectInHeadMixin], mixins: [InjectInHeadMixin],
getInitialState() { getInitialState() {
return { ready: false }; return { ready: false, videoMounted: false };
}, },
componentDidMount() { componentDidMount() {
@ -108,24 +132,40 @@ let Video = React.createClass({
}, },
componentDidUpdate() { componentDidUpdate() {
if (this.state.ready && !this.state.videojs) { if (this.state.ready && !this.state.videoMounted) {
window.videojs(React.findDOMNode(this.refs.video)); window.videojs('#mainvideo');
/* eslint-disable */
this.setState({videoMounted: true});
/* eslint-enable*/
} }
}, },
componentWillUnmount() {
window.videojs('#mainvideo').dispose();
},
ready() { ready() {
this.setState({ready: true, videojs: false}); this.setState({ready: true, videoMounted: false});
},
prepareVideoHTML() {
let sources = this.props.extraData.map((data) => '<source type="video/' + data.type + '" src="' + escapeHTML(data.url) + '" />');
let html = [
'<video id="mainvideo" class="video-js vjs-default-skin" poster="' + escapeHTML(this.props.preview) + '"',
'controls preload="none" width="auto" height="auto">',
sources.join('\n'),
'</video>'];
return html.join('\n');
},
shouldComponentUpdate(nextProps, nextState) {
return nextState.videoMounted === false;
}, },
render() { render() {
if (this.state.ready) { if (this.state.ready) {
return ( return (
<video ref="video" className="video-js vjs-default-skin" poster={this.props.preview} <div dangerouslySetInnerHTML={{__html: this.prepareVideoHTML() }}/>
controls preload="none" width="auto" height="auto">
{this.props.extraData.map((data, i) =>
<source key={i} type={'video/' + data.type} src={data.url} />
)}
</video>
); );
} else { } else {
return ( return (

View File

@ -7,9 +7,13 @@ import PasswordResetRequestForm from '../ascribe_forms/form_password_reset_reque
import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions'; import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getLangText } from '../../utils/lang_utils.js' import { getLangText } from '../../utils/lang_utils.js';
let PasswordResetRequestModal = React.createClass({ let PasswordResetRequestModal = React.createClass({
propTypes: {
button: React.PropTypes.element
},
handleResetSuccess(){ handleResetSuccess(){
let notificationText = getLangText('Request successfully sent, check your email'); let notificationText = getLangText('Request successfully sent, check your email');
let notification = new GlobalNotificationModel(notificationText, 'success', 50000); let notification = new GlobalNotificationModel(notificationText, 'success', 50000);
@ -18,10 +22,9 @@ let PasswordResetRequestModal = React.createClass({
render() { render() {
return ( return (
<ModalWrapper <ModalWrapper
button={this.props.button} trigger={this.props.button}
title={getLangText('Reset your password')} title={getLangText('Reset your password')}
handleSuccess={this.handleResetSuccess} handleSuccess={this.handleResetSuccess}>
tooltip={getLangText('Reset your password')}>
<PasswordResetRequestForm /> <PasswordResetRequestForm />
</ModalWrapper> </ModalWrapper>
); );

View File

@ -4,92 +4,74 @@ import React from 'react';
import ReactAddons from 'react/addons'; import ReactAddons from 'react/addons';
import Modal from 'react-bootstrap/lib/Modal'; 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({ let ModalWrapper = React.createClass({
propTypes: { propTypes: {
title: React.PropTypes.string.isRequired, trigger: React.PropTypes.element.isRequired,
onRequestHide: React.PropTypes.func, title: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element,
React.PropTypes.string
]).isRequired,
handleSuccess: React.PropTypes.func.isRequired, handleSuccess: React.PropTypes.func.isRequired,
button: React.PropTypes.object.isRequired, children: React.PropTypes.oneOfType([
children: React.PropTypes.object, React.PropTypes.arrayOf(React.PropTypes.element),
tooltip: React.PropTypes.string React.PropTypes.element
])
}, },
getModalTrigger() { getInitialState() {
return ( return {
<ModalTrigger modal={ showModal: false
<ModalBody };
title={this.props.title}
handleSuccess={this.props.handleSuccess}>
{this.props.children}
</ModalBody>
}>
{this.props.button}
</ModalTrigger>
);
}, },
render() { show() {
if(this.props.tooltip) { this.setState({
return ( showModal: true
<OverlayTrigger });
delay={500}
placement="left"
overlay={<Tooltip>{this.props.tooltip}</Tooltip>}>
{this.getModalTrigger()}
</OverlayTrigger>
);
} else {
return (
<span>
{/* This needs to be some kind of inline-block */}
{this.getModalTrigger()}
</span>
);
}
}
});
let ModalBody = React.createClass({
propTypes: {
onRequestHide: React.PropTypes.func,
handleSuccess: React.PropTypes.func,
children: React.PropTypes.object,
title: React.PropTypes.string.isRequired
}, },
mixins: [ModalMixin], hide() {
this.setState({
showModal: false
});
},
handleSuccess(response){ handleSuccess(response){
this.props.handleSuccess(response); this.props.handleSuccess(response);
this.props.onRequestHide(); this.hide();
}, },
renderChildren() { renderChildren() {
return ReactAddons.Children.map(this.props.children, (child) => { return ReactAddons.Children.map(this.props.children, (child) => {
return ReactAddons.addons.cloneWithProps(child, { return ReactAddons.addons.cloneWithProps(child, {
onRequestHide: this.props.onRequestHide,
handleSuccess: this.handleSuccess handleSuccess: this.handleSuccess
}); });
}); });
}, },
render() { render() {
// this adds the onClick method show of modal_wrapper to the trigger component
// which is in most cases a button.
let trigger = React.cloneElement(this.props.trigger, {onClick: this.show});
return ( return (
<Modal {...this.props} title={this.props.title}> <span>
<div className="modal-body"> {trigger}
{this.renderChildren()} <Modal show={this.state.showModal} onHide={this.hide}>
</div> <Modal.Header closeButton>
</Modal> <Modal.Title>
{this.props.title}
</Modal.Title>
</Modal.Header>
<div className="modal-body">
{this.renderChildren()}
</div>
</Modal>
</span>
); );
} }
}); });
export default ModalWrapper; export default ModalWrapper;

View File

@ -0,0 +1,61 @@
'use strict';
import React from 'react';
import classnames from 'classnames';
let ActionPanel = React.createClass({
propTypes: {
title: React.PropTypes.string,
content: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.element
]),
buttons: React.PropTypes.element,
onClick: React.PropTypes.func,
ignoreFocus: React.PropTypes.bool
},
getInitialState() {
return {
isFocused: false
};
},
handleFocus() {
// if ignoreFocus (bool) is defined, then just ignore focusing on
// the property and input
if(this.props.ignoreFocus) {
return;
}
// if onClick is defined from the outside,
// just call it
if(this.props.onClick) {
this.props.onClick();
}
this.refs.input.getDOMNode().focus();
this.setState({
isFocused: true
});
},
render() {
return (
<div className={classnames('ascribe-panel-wrapper', {'is-focused': this.state.isFocused})}>
<div className="ascribe-panel-table">
<div className="ascribe-panel-content">
{this.props.content}
</div>
</div>
<div className="ascribe-panel-table">
<div className="ascribe-panel-content">
{this.props.buttons}
</div>
</div>
</div>
);
}
});
export default ActionPanel;

View File

@ -76,11 +76,12 @@ let PieceListBulkModal = React.createClass({
}, },
handleSuccess() { handleSuccess() {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc); PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
this.fetchSelectedPieceEditionList() this.fetchSelectedPieceEditionList()
.forEach((pieceId) => { .forEach((pieceId) => {
EditionListActions.refreshEditionList(pieceId); EditionListActions.refreshEditionList({pieceId, filterBy: {}});
}); });
EditionListActions.clearAllEditionSelections(); EditionListActions.clearAllEditionSelections();
}, },

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import { getLangText } from '../../utils/lang_utils.js' import { getLangText } from '../../utils/lang_utils.js';
let PieceListBulkModalSelectedEditionsWidget = React.createClass({ let PieceListBulkModalSelectedEditionsWidget = React.createClass({
propTypes: { propTypes: {

View File

@ -2,6 +2,8 @@
import React from 'react'; import React from 'react';
import PieceListToolbarFilterWidget from './piece_list_toolbar_filter_widget';
import Input from 'react-bootstrap/lib/Input'; import Input from 'react-bootstrap/lib/Input';
import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
@ -11,6 +13,8 @@ let PieceListToolbar = React.createClass({
propTypes: { propTypes: {
className: React.PropTypes.string, className: React.PropTypes.string,
searchFor: React.PropTypes.func, searchFor: React.PropTypes.func,
filterBy: React.PropTypes.object,
applyFilterBy: React.PropTypes.func,
children: React.PropTypes.oneOfType([ children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element React.PropTypes.element
@ -41,6 +45,15 @@ let PieceListToolbar = React.createClass({
onChange={this.searchFor} onChange={this.searchFor}
addonAfter={searchIcon} /> addonAfter={searchIcon} />
</span> </span>
<span className="pull-right">
<PieceListToolbarFilterWidget
filterParams={['acl_transfer', 'acl_consign', {
key: 'acl_create_editions',
label: 'create editions'
}]}
filterBy={this.props.filterBy}
applyFilterBy={this.props.applyFilterBy}/>
</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,30 +2,114 @@
import React from 'react'; import React from 'react';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import DropdownButton from 'react-bootstrap/lib/DropdownButton'; import DropdownButton from 'react-bootstrap/lib/DropdownButton';
import MenuItem from 'react-bootstrap/lib/MenuItem'; import MenuItem from 'react-bootstrap/lib/MenuItem';
import { getLangText } from '../../utils/lang_utils.js'
import { getLangText } from '../../utils/lang_utils.js';
let PieceListToolbarFilterWidgetFilter = React.createClass({ let PieceListToolbarFilterWidgetFilter = React.createClass({
propTypes: {
// An array of either strings (which represent acl enums) or objects of the form
//
// {
// key: <acl enum>,
// label: <a human readable string>
// }
//
filterParams: React.PropTypes.arrayOf(React.PropTypes.any).isRequired,
filterBy: React.PropTypes.object,
applyFilterBy: React.PropTypes.func
},
generateFilterByStatement(param) {
let filterBy = this.props.filterBy;
if(filterBy) {
// we need hasOwnProperty since the values are all booleans
if(filterBy.hasOwnProperty(param)) {
filterBy[param] = !filterBy[param];
// if the parameter is false, then we want to remove it again
// from the list of queryParameters as this component is only about
// which actions *CAN* be done and not what *CANNOT*
if(!filterBy[param]) {
delete filterBy[param];
}
} else {
filterBy[param] = true;
}
}
return filterBy;
},
/**
* We need overloading here to find the correct parameter of the label
* the user is clicking on.
*/
filterBy(param) {
return () => {
let filterBy = this.generateFilterByStatement(param);
this.props.applyFilterBy(filterBy);
};
},
isFilterActive() {
let trueValuesOnly = Object.keys(this.props.filterBy).filter((acl) => acl);
// We're hiding the star in that complicated matter so that,
// the surrounding button is not resized up on appearance
if(trueValuesOnly.length > 0) {
return { visibility: 'visible'};
} else {
return { visibility: 'hidden' };
}
},
render() { render() {
let filterIcon = <Glyphicon glyph='filter' className="filter-glyph"/>; let filterIcon = (
<span>
<span className="glyphicon glyphicon-filter" aria-hidden="true"></span>
<span style={this.isFilterActive()}>*</span>
</span>
);
return ( return (
<DropdownButton title={filterIcon}> <DropdownButton
title={filterIcon}
className="ascribe-piece-list-toolbar-filter-widget">
<li style={{'textAlign': 'center'}}> <li style={{'textAlign': 'center'}}>
<em>{getLangText('Show Pieces that')}:</em> <em>{getLangText('Show works I can')}:</em>
</li> </li>
<MenuItem eventKey='1'> {this.props.filterParams.map((param, i) => {
<div className="checkbox"> let label;
{getLangText('I can transfer')} <input type="checkbox" />
</div> if(typeof param !== 'string') {
</MenuItem> label = param.label;
<MenuItem eventKey='2'> param = param.key;
<div className="checkbox"> } else {
{getLangText('I can consign')} <input type="checkbox" /> param = param;
</div> label = param.split('_')[1];
</MenuItem> }
return (
<MenuItem
key={i}
onClick={this.filterBy(param)}
className="filter-widget-item">
<div className="checkbox-line">
<span>
{getLangText(label)}
</span>
<input
readOnly
type="checkbox"
checked={this.props.filterBy[param]} />
</div>
</MenuItem>
);
})}
</DropdownButton> </DropdownButton>
); );
} }

View File

@ -0,0 +1,82 @@
'use strict';
import React from 'react';
import PrizeListActions from '../../actions/prize_list_actions';
import PrizeListStore from '../../stores/prize_list_store';
import Table from '../ascribe_table/table';
import TableItem from '../ascribe_table/table_item';
import TableItemText from '../ascribe_table/table_item_text';
import { ColumnModel} from '../ascribe_table/models/table_models';
import { getLangText } from '../../utils/lang_utils';
let PrizesDashboard = React.createClass({
getInitialState() {
return PrizeListStore.getState();
},
componentDidMount() {
PrizeListStore.listen(this.onChange);
PrizeListActions.fetchPrizeList();
},
componentWillUnmount() {
PrizeListStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
getColumnList() {
return [
new ColumnModel(
(item) => {
return {
'content': item.name
}; },
'name',
getLangText('Name'),
TableItemText,
6,
false,
null
),
new ColumnModel(
(item) => {
return {
'content': item.domain
}; },
'domain',
getLangText('Domain'),
TableItemText,
1,
false,
null
)
];
},
render() {
return (
<Table
responsive
className="ascribe-table"
columnList={this.getColumnList()}
itemList={this.state.prizeList}>
{this.state.prizeList.map((item, i) => {
return (
<TableItem
className="ascribe-table-item-selectable"
key={i}/>
);
})}
</Table>
);
}
});
export default PrizesDashboard;

View File

@ -2,7 +2,6 @@
import React from 'react'; import React from 'react';
import TableColumnMixin from '../../mixins/table_column_mixin';
import TableHeaderItem from './table_header_item'; import TableHeaderItem from './table_header_item';
import { ColumnModel } from './models/table_models'; import { ColumnModel } from './models/table_models';
@ -17,15 +16,12 @@ let TableHeader = React.createClass({
orderBy: React.PropTypes.string orderBy: React.PropTypes.string
}, },
mixins: [TableColumnMixin],
render() { render() {
return ( return (
<thead> <thead>
<tr> <tr>
{this.props.columnList.map((column, i) => { {this.props.columnList.map((column, i) => {
let columnClasses = this.calcColumnClasses(this.props.columnList, i, 12);
let columnName = column.columnName; let columnName = column.columnName;
let canBeOrdered = column.canBeOrdered; let canBeOrdered = column.canBeOrdered;
@ -33,7 +29,6 @@ let TableHeader = React.createClass({
<TableHeaderItem <TableHeaderItem
className={column.className} className={column.className}
key={i} key={i}
columnClasses={columnClasses}
displayName={column.displayName} displayName={column.displayName}
columnName={columnName} columnName={columnName}
canBeOrdered={canBeOrdered} canBeOrdered={canBeOrdered}

View File

@ -7,7 +7,6 @@ import TableHeaderItemCarret from './table_header_item_carret';
let TableHeaderItem = React.createClass({ let TableHeaderItem = React.createClass({
propTypes: { propTypes: {
columnClasses: React.PropTypes.string.isRequired,
displayName: React.PropTypes.oneOfType([ displayName: React.PropTypes.oneOfType([
React.PropTypes.string, React.PropTypes.string,
React.PropTypes.element React.PropTypes.element

View File

@ -1,113 +0,0 @@
'use strict';
import React from 'react';
import { ColumnModel } from './models/table_models';
import EditionListStore from '../../stores/edition_list_store';
import EditionListActions from '../../actions/edition_list_actions';
import Table from './table';
import TableItemWrapper from './table_item_wrapper';
import TableItemText from './table_item_text';
import TableItemAcl from './table_item_acl';
import TableItemSelectable from './table_item_selectable';
import TableItemSubtableButton from './table_item_subtable_button';
let TableItemSubtable = React.createClass({
propTypes: {
columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(ColumnModel)),
columnContent: React.PropTypes.object
},
getInitialState() {
return {
'open': false
};
},
componentDidMount() {
EditionListStore.listen(this.onChange);
},
componentWillUnmount() {
EditionListStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
loadEditionList() {
if(this.state.open) {
this.setState({
'open': false
});
} else {
EditionListActions.fetchEditionList(this.props.columnContent.id);
this.setState({
'open': true,
'editionList': EditionListStore.getState()
});
}
},
selectItem(parentId, itemId) {
EditionListActions.selectEdition({
'pieceId': parentId,
'editionId': itemId
});
},
render() {
let renderEditionListTable = () => {
let columnList = [
new ColumnModel('edition_number', 'Number', TableItemText, 2, false),
new ColumnModel('user_registered', 'User', TableItemText, 4, true),
new ColumnModel('acl', 'Actions', TableItemAcl, 4, true)
];
if(this.state.open && this.state.editionList[this.props.columnContent.id] && this.state.editionList[this.props.columnContent.id].length) {
return (
<div className="row">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<Table itemList={this.state.editionList[this.props.columnContent.id]} columnList={columnList}>
{this.state.editionList[this.props.columnContent.id].map((edition, i) => {
return (
<TableItemSelectable
className="ascribe-table-item-selectable"
selectItem={this.selectItem}
parentId={this.props.columnContent.id}
key={i} />
);
})}
</Table>
</div>
</div>
);
}
};
return (
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12 ascribe-table-item">
<div className="row">
<TableItemWrapper
columnList={this.props.columnList}
columnContent={this.props.columnContent}
columnWidth={12} />
<div className="col-xs-1 col-sm-1 col-md-1 col-lg-1 ascribe-table-item-column">
<TableItemSubtableButton content="+" onClick={this.loadEditionList} />
</div>
</div>
{renderEditionListTable()}
</div>
);
}
});
export default TableItemSubtable;

View File

@ -1,23 +0,0 @@
'use strict';
import React from 'react';
let TableItemSubtableButton = React.createClass({
propTypes: {
content: React.PropTypes.string.isRequired,
onClick: React.PropTypes.func.isRequired
},
render() {
return (
<span>
<button type="button" className="btn btn-default btn-sm ascribe-table-expand-button" onClick={this.props.onClick}>
{this.props.content}
</button>
</span>
);
}
});
export default TableItemSubtableButton;

View File

@ -2,7 +2,6 @@
import React from 'react/addons'; import React from 'react/addons';
import Router from 'react-router'; import Router from 'react-router';
import Raven from 'raven-js';
import Q from 'q'; import Q from 'q';
import { getCookie } from '../../utils/fetch_api_utils'; import { getCookie } from '../../utils/fetch_api_utils';
@ -171,9 +170,13 @@ var ReactS3FineUploader = React.createClass({
filesToUpload: [], filesToUpload: [],
uploader: new fineUploader.s3.FineUploaderBasic(this.propsToConfig()), uploader: new fineUploader.s3.FineUploaderBasic(this.propsToConfig()),
csrfToken: getCookie(AppConstants.csrftoken), csrfToken: getCookie(AppConstants.csrftoken),
hashingProgress: -2
// -1: aborted // -1: aborted
// -2: uninitialized // -2: uninitialized
hashingProgress: -2,
// this is for logging
chunks: {}
}; };
}, },
@ -225,7 +228,9 @@ var ReactS3FineUploader = React.createClass({
onDeleteComplete: this.onDeleteComplete, onDeleteComplete: this.onDeleteComplete,
onSessionRequestComplete: this.onSessionRequestComplete, onSessionRequestComplete: this.onSessionRequestComplete,
onError: this.onError, onError: this.onError,
onValidate: this.onValidate onValidate: this.onValidate,
onUploadChunk: this.onUploadChunk,
onUploadChunkSuccess: this.onUploadChunkSuccess
} }
}; };
}, },
@ -257,6 +262,10 @@ var ReactS3FineUploader = React.createClass({
resolve(res.key); resolve(res.key);
}) })
.catch((err) => { .catch((err) => {
console.logGlobal(err, false, {
files: this.state.filesToUpload,
chunks: this.state.chunks
});
reject(err); reject(err);
}); });
}); });
@ -294,15 +303,66 @@ var ReactS3FineUploader = React.createClass({
resolve(res); resolve(res);
}) })
.catch((err) => { .catch((err) => {
console.logGlobal(err, false, {
files: this.state.filesToUpload,
chunks: this.state.chunks
});
reject(err); reject(err);
console.logGlobal(err);
}); });
}); });
}, },
/* FineUploader specific callback function handlers */ /* FineUploader specific callback function handlers */
onComplete(id) { onUploadChunk(id, name, chunkData) {
let chunks = this.state.chunks;
chunks[id + '-' + chunkData.startByte + '-' + chunkData.endByte] = {
id,
name,
chunkData,
completed: false
};
let newState = React.addons.update(this.state, {
startedChunks: { $set: chunks }
});
this.setState(newState);
},
onUploadChunkSuccess(id, chunkData, responseJson, xhr) {
let chunks = this.state.chunks;
let chunkKey = id + '-' + chunkData.startByte + '-' + chunkData.endByte;
if(chunks[chunkKey]) {
chunks[chunkKey].completed = true;
chunks[chunkKey].responseJson = responseJson;
chunks[chunkKey].xhr = xhr;
let newState = React.addons.update(this.state, {
startedChunks: { $set: chunks }
});
this.setState(newState);
}
},
onComplete(id, name, res, xhr) {
// there has been an issue with the server's connection
if(xhr.status === 0) {
console.logGlobal(new Error('Complete was called but there wasn\t a success'), false, {
files: this.state.filesToUpload,
chunks: this.state.chunks
});
return;
}
let files = this.state.filesToUpload; let files = this.state.filesToUpload;
// Set the state of the completed file to 'upload successful' in order to // Set the state of the completed file to 'upload successful' in order to
@ -342,7 +402,10 @@ var ReactS3FineUploader = React.createClass({
} }
}) })
.catch((err) => { .catch((err) => {
console.logGlobal(err); console.logGlobal(err, false, {
files: this.state.filesToUpload,
chunks: this.state.chunks
});
let notification = new GlobalNotificationModel(err.message, 'danger', 5000); let notification = new GlobalNotificationModel(err.message, 'danger', 5000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}); });
@ -351,7 +414,12 @@ var ReactS3FineUploader = React.createClass({
}, },
onError(id, name, errorReason) { onError(id, name, errorReason) {
Raven.captureException(errorReason); console.logGlobal(errorReason, false, {
files: this.state.filesToUpload,
chunks: this.state.chunks
});
this.state.uploader.cancelAll();
let notification = new GlobalNotificationModel(this.props.defaultErrorMessage, 'danger', 5000); let notification = new GlobalNotificationModel(this.props.defaultErrorMessage, 'danger', 5000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },

View File

@ -9489,6 +9489,12 @@ qq.s3.XhrUploadHandler = function(spec, proxy) {
result.success, result.success,
function failure(reason, xhr) { function failure(reason, xhr) {
console.logGlobal(reason + 'in chunked.combine', false, {
uploadId,
etagMap,
result
});
result.failure(upload.done(id, xhr).response, xhr); result.failure(upload.done(id, xhr).response, xhr);
} }
); );

View File

@ -10,7 +10,7 @@ import Form from './ascribe_forms/form';
import Property from './ascribe_forms/property'; import Property from './ascribe_forms/property';
import InputTextAreaToggable from './ascribe_forms/input_textarea_toggable'; import InputTextAreaToggable from './ascribe_forms/input_textarea_toggable';
import apiUrls from '../constants/api_urls'; import ApiUrls from '../constants/api_urls';
import { getLangText } from '../utils/lang_utils'; import { getLangText } from '../utils/lang_utils';
@ -59,7 +59,7 @@ let CoaVerifyForm = React.createClass({
return ( return (
<div> <div>
<Form <Form
url={apiUrls.coa_verify} url={ApiUrls.coa_verify}
handleSuccess={this.handleSuccess} handleSuccess={this.handleSuccess}
buttons={ buttons={
<button <button

View File

@ -1,43 +0,0 @@
'use strict';
import React from 'react';
import Button from 'react-bootstrap/lib/Button';
import Modal from 'react-bootstrap/lib/Modal';
import OverlayMixin from 'react-bootstrap/lib/OverlayMixin';
import { getLangText } from '../utils/lang_utils.js';
let LoginModalHandler = React.createClass({
mixins: [OverlayMixin],
getInitialState() {
return {
isModalOpen: true
};
},
handleToggle() {
this.setState({
isModalOpen: !this.state.isModalOpen
});
},
render() {
if(!this.state.isModalOpen || !(this.props.query.login === '')) {
return <span/>;
}
return (
<Modal title='Modal heading' onRequestHide={this.handleToggle}>
<div className='modal-body'>
This modal is controlled by our custom trigger component.
</div>
<div className='modal-footer'>
<Button onClick={this.handleToggle}>{getLangText('Close')}</Button>
</div>
</Modal>
);
}
});
export default LoginModalHandler;

View File

@ -5,8 +5,7 @@ import Router from 'react-router';
import Form from './ascribe_forms/form'; import Form from './ascribe_forms/form';
import Property from './ascribe_forms/property'; import Property from './ascribe_forms/property';
import FormPropertyHeader from './ascribe_forms/form_property_header'; import ApiUrls from '../constants/api_urls';
import apiUrls from '../constants/api_urls';
import GlobalNotificationModel from '../models/global_notification_model'; import GlobalNotificationModel from '../models/global_notification_model';
import GlobalNotificationActions from '../actions/global_notification_actions'; import GlobalNotificationActions from '../actions/global_notification_actions';
@ -15,12 +14,15 @@ import { getLangText } from '../utils/lang_utils';
let PasswordResetContainer = React.createClass({ let PasswordResetContainer = React.createClass({
mixins: [Router.Navigation], mixins: [Router.Navigation],
getInitialState() { getInitialState() {
return {isRequested: false}; return {isRequested: false};
}, },
handleRequestSuccess(email){
handleRequestSuccess(email) {
this.setState({isRequested: email}); this.setState({isRequested: email});
}, },
render() { render() {
if (this.props.query.email && this.props.query.token) { if (this.props.query.email && this.props.query.token) {
return ( return (
@ -57,17 +59,22 @@ let PasswordResetContainer = React.createClass({
}); });
let PasswordRequestResetForm = React.createClass({ let PasswordRequestResetForm = React.createClass({
propTypes: {
handleRequestSuccess: React.PropTypes.func
},
handleSuccess() { handleSuccess() {
let notificationText = getLangText('If your email address exists in our database, you will receive a password recovery link in a few minutes.'); let notificationText = getLangText('If your email address exists in our database, you will receive a password recovery link in a few minutes.');
let notification = new GlobalNotificationModel(notificationText, 'success', 50000); let notification = new GlobalNotificationModel(notificationText, 'success', 50000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
this.props.handleRequestSuccess(this.refs.form.refs.email.state.value); this.props.handleRequestSuccess(this.refs.form.refs.email.state.value);
}, },
render() { render() {
return ( return (
<Form <Form
ref="form" ref="form"
url={apiUrls.users_password_reset_request} url={ApiUrls.users_password_reset_request}
handleSuccess={this.handleSuccess} handleSuccess={this.handleSuccess}
buttons={ buttons={
<button <button
@ -80,15 +87,15 @@ let PasswordRequestResetForm = React.createClass({
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" /> <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</span> </span>
}> }>
<FormPropertyHeader> <div className="ascribe-form-header">
<h3>{getLangText('Reset your password')}</h3> <h3>{getLangText('Reset your password')}</h3>
</FormPropertyHeader> </div>
<Property <Property
name='email' name='email'
label={getLangText('Email')}> label={getLangText('Email')}>
<input <input
type="email" type="email"
placeholder={getLangText("Enter your email and we'll send a link")} placeholder={getLangText('Enter your email and we\'ll send a link')}
name="email" name="email"
required/> required/>
</Property> </Property>
@ -99,24 +106,31 @@ let PasswordRequestResetForm = React.createClass({
}); });
let PasswordResetForm = React.createClass({ let PasswordResetForm = React.createClass({
propTypes: {
email: React.PropTypes.string,
token: React.PropTypes.string
},
mixins: [Router.Navigation], mixins: [Router.Navigation],
getFormData(){ getFormData() {
return { return {
email: this.props.email, email: this.props.email,
token: this.props.token token: this.props.token
}; };
}, },
handleSuccess() { handleSuccess() {
this.transitionTo('pieces'); this.transitionTo('pieces');
let notification = new GlobalNotificationModel(getLangText('password successfully updated'), 'success', 10000); let notification = new GlobalNotificationModel(getLangText('password successfully updated'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
render() { render() {
return ( return (
<Form <Form
ref="form" ref="form"
url={apiUrls.users_password_reset} url={ApiUrls.users_password_reset}
handleSuccess={this.handleSuccess} handleSuccess={this.handleSuccess}
getFormData={this.getFormData} getFormData={this.getFormData}
buttons={ buttons={
@ -130,9 +144,9 @@ let PasswordResetForm = React.createClass({
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" /> <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</span> </span>
}> }>
<FormPropertyHeader> <div className="ascribe-form-header">
<h3>{getLangText('Reset the password for')} {this.props.email}</h3> <h3>{getLangText('Reset the password for')} {this.props.email}</h3>
</FormPropertyHeader> </div>
<Property <Property
name='password' name='password'
label={getLangText('Password')}> label={getLangText('Password')}>

View File

@ -6,6 +6,9 @@ import Router from 'react-router';
import PieceListStore from '../stores/piece_list_store'; import PieceListStore from '../stores/piece_list_store';
import PieceListActions from '../actions/piece_list_actions'; import PieceListActions from '../actions/piece_list_actions';
import EditionListStore from '../stores/edition_list_store';
import EditionListActions from '../actions/edition_list_actions';
import AccordionList from './ascribe_accordion_list/accordion_list'; import AccordionList from './ascribe_accordion_list/accordion_list';
import AccordionListItem from './ascribe_accordion_list/accordion_list_item'; import AccordionListItem from './ascribe_accordion_list/accordion_list_item';
import AccordionListItemTableEditions from './ascribe_accordion_list/accordion_list_item_table_editions'; import AccordionListItemTableEditions from './ascribe_accordion_list/accordion_list_item_table_editions';
@ -17,6 +20,7 @@ import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar';
import AppConstants from '../constants/application_constants'; import AppConstants from '../constants/application_constants';
import { mergeOptions } from '../utils/general_utils';
let PieceList = React.createClass({ let PieceList = React.createClass({
propTypes: { propTypes: {
@ -27,15 +31,22 @@ let PieceList = React.createClass({
mixins: [Router.Navigation, Router.State], mixins: [Router.Navigation, Router.State],
getInitialState() { getInitialState() {
return PieceListStore.getState(); return mergeOptions(
PieceListStore.getState(),
EditionListStore.getState()
);
}, },
componentDidMount() { componentDidMount() {
let page = this.getQuery().page || 1; let page = this.getQuery().page || 1;
PieceListStore.listen(this.onChange); PieceListStore.listen(this.onChange);
EditionListStore.listen(this.onChange);
if (this.state.pieceList.length === 0){ if (this.state.pieceList.length === 0){
PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc) PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search,
.then(PieceListActions.fetchPieceRequestActions()); this.state.orderBy, this.state.orderAsc, this.state.filterBy)
.then(() => PieceListActions.fetchPieceRequestActions());
} }
}, },
@ -48,6 +59,7 @@ let PieceList = React.createClass({
componentWillUnmount() { componentWillUnmount() {
PieceListStore.unlisten(this.onChange); PieceListStore.unlisten(this.onChange);
EditionListStore.unlisten(this.onChange);
}, },
onChange(state) { onChange(state) {
@ -55,13 +67,14 @@ let PieceList = React.createClass({
}, },
paginationGoToPage(page) { paginationGoToPage(page) {
// if the users clicks a pager of the pagination, return () => {
// the site should go to the top // if the users clicks a pager of the pagination,
document.body.scrollTop = document.documentElement.scrollTop = 0; // the site should go to the top
document.body.scrollTop = document.documentElement.scrollTop = 0;
return () => PieceListActions.fetchPieceList(page, this.state.pageSize, PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search,
this.state.search, this.state.orderBy, this.state.orderBy, this.state.orderAsc,
this.state.orderAsc); this.state.filterBy);
};
}, },
getPagination() { getPagination() {
@ -79,23 +92,49 @@ let PieceList = React.createClass({
}, },
searchFor(searchTerm) { searchFor(searchTerm) {
PieceListActions.fetchPieceList(1, this.state.pageSize, searchTerm, this.state.orderBy, this.state.orderAsc); PieceListActions.fetchPieceList(1, this.state.pageSize, searchTerm, this.state.orderBy,
this.state.orderAsc, this.state.filterBy);
this.transitionTo(this.getPathname(), {page: 1}); this.transitionTo(this.getPathname(), {page: 1});
}, },
applyFilterBy(filterBy) {
// first we need to apply the filter on the piece list
PieceListActions.fetchPieceList(1, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, filterBy)
.then(() => {
// but also, we need to filter all the open edition lists
this.state.pieceList
.forEach((piece) => {
// but only if they're actually open
if(this.state.isEditionListOpenForPieceId[piece.id].show) {
EditionListActions.refreshEditionList({
pieceId: piece.id,
filterBy
});
}
});
});
// we have to redirect the user always to page one as it could be that there is no page two
// for filtered pieces
this.transitionTo(this.getPathname(), {page: 1});
},
accordionChangeOrder(orderBy, orderAsc) { accordionChangeOrder(orderBy, orderAsc) {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.search, orderBy, orderAsc); orderBy, orderAsc, this.state.filterBy);
}, },
render() { render() {
let loadingElement = (<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />); let loadingElement = (<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />);
return ( return (
<div> <div>
<PieceListToolbar <PieceListToolbar
className="ascribe-piece-list-toolbar" className="ascribe-piece-list-toolbar"
searchFor={this.searchFor}> searchFor={this.searchFor}
filterBy={this.state.filterBy}
applyFilterBy={this.applyFilterBy}>
{this.props.customSubmitButton} {this.props.customSubmitButton}
</PieceListToolbar> </PieceListToolbar>
<PieceListBulkModal className="ascribe-piece-list-bulk-modal" /> <PieceListBulkModal className="ascribe-piece-list-bulk-modal" />

View File

@ -1,92 +0,0 @@
'use strict';
/**
* This component is essentially a port of https://github.com/simplefocus/FlowType.JS
* to Reactjs in order to not being forced to use jQuery
*
* Author: Tim Daubenschütz
*
* Thanks to the guys at Simple Focus http://simplefocus.com/
*/
import React from 'react';
import ReactAddons from 'react/addons';
let FlowType = React.createClass({
propTypes: {
// standard FlowTypes.JS options
maximum: React.PropTypes.number,
minimum: React.PropTypes.number,
maxFont: React.PropTypes.number,
minFont: React.PropTypes.number,
fontRatio: React.PropTypes.number,
// react specific options
children: React.PropTypes.element.isRequired // only supporting one child element at once right now
},
getDefaultProps() {
return {
maximum: 9999,
minimum: 1,
maxFont: 9999,
minFont: 1,
fontRatio: 35
};
},
getInitialState() {
return {
// 32 because that's the default font display size
// doesn't really matter though
fontSize: 0
};
},
componentDidMount() {
// Make changes upon resize, calculate changes and rerender
this.handleResize();
window.addEventListener('resize', this.handleResize);
},
componentWillUnmount() {
// stop listening to window once the component was unmounted
window.removeEventListener('resize', this.handleResize);
},
handleResize() {
let elemWidth = this.refs.flowTypeElement.getDOMNode().offsetWidth;
let width = elemWidth > this.props.maximum ? this.props.maximum : elemWidth < this.props.minimum ? this.props.minimum : elemWidth;
let fontBase = width / this.props.fontRatio;
let fontSize = fontBase > this.props.maxFont ? this.props.maxFont : fontBase < this.props.minFont ? this.props.minFont : fontBase;
this.setState({ fontSize });
},
// The child the user passes to this component needs to have it's
// style.fontSize property to be updated
renderChildren() {
return ReactAddons.Children.map(this.props.children, (child) => {
return ReactAddons.addons.cloneWithProps(child, {
ref: 'flowTypeFontElement',
});
});
},
render() {
return (
<div
ref="flowTypeElement"
style={{
width: '100%',
height: '100%',
fontSize: this.state.fontSize
}}>
{this.props.children}
</div>
);
}
});
export default FlowType;

View File

@ -23,7 +23,6 @@ import GlobalNotificationActions from '../actions/global_notification_actions';
import Property from './ascribe_forms/property'; import Property from './ascribe_forms/property';
import PropertyCollapsible from './ascribe_forms/property_collapsible'; import PropertyCollapsible from './ascribe_forms/property_collapsible';
import RegisterPieceForm from './ascribe_forms/form_register_piece'; import RegisterPieceForm from './ascribe_forms/form_register_piece';
//import FormPropertyHeader from './ascribe_forms/form_property_header';
import LoginContainer from './login_container'; import LoginContainer from './login_container';
import SlidesContainer from './ascribe_slides_container/slides_container'; import SlidesContainer from './ascribe_slides_container/slides_container';
@ -40,7 +39,9 @@ let RegisterPiece = React.createClass( {
submitMessage: React.PropTypes.string, submitMessage: React.PropTypes.string,
children: React.PropTypes.oneOfType([ children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element]) React.PropTypes.element,
React.PropTypes.string
])
}, },
mixins: [Router.Navigation], mixins: [Router.Navigation],
@ -101,7 +102,8 @@ let RegisterPiece = React.createClass( {
this.state.pageSize, this.state.pageSize,
this.state.searchTerm, this.state.searchTerm,
this.state.orderBy, this.state.orderBy,
this.state.orderAsc this.state.orderAsc,
this.state.filterBy
); );
this.transitionTo('piece', {pieceId: response.piece.id}); this.transitionTo('piece', {pieceId: response.piece.id});
@ -139,7 +141,7 @@ let RegisterPiece = React.createClass( {
}, },
getSpecifyEditions() { getSpecifyEditions() {
if(this.state.whitelabel && this.state.whitelabel.acl_editions || Object.keys(this.state.whitelabel).length === 0) { if(this.state.whitelabel && this.state.whitelabel.acl_create_editions || Object.keys(this.state.whitelabel).length === 0) {
return ( return (
<PropertyCollapsible <PropertyCollapsible
name="num_editions" name="num_editions"

View File

@ -22,19 +22,28 @@ import Form from './ascribe_forms/form';
import Property from './ascribe_forms/property'; import Property from './ascribe_forms/property';
import InputCheckbox from './ascribe_forms/input_checkbox'; import InputCheckbox from './ascribe_forms/input_checkbox';
import apiUrls from '../constants/api_urls'; import ActionPanel from './ascribe_panel/action_panel';
import AppConstants from '../constants/application_constants';
import { getLangText } from '../utils/lang_utils';
import ApiUrls from '../constants/api_urls';
import AppConstants from '../constants/application_constants';
import { getLangText } from '../utils/lang_utils';
import { getCookie } from '../utils/fetch_api_utils'; import { getCookie } from '../utils/fetch_api_utils';
let SettingsContainer = React.createClass({ let SettingsContainer = React.createClass({
propTypes: {
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element])
},
mixins: [Router.Navigation], mixins: [Router.Navigation],
render() { render() {
return ( return (
<div className="settings-container"> <div className="settings-container">
<AccountSettings /> <AccountSettings />
{this.props.children}
<APISettings /> <APISettings />
<BitcoinWalletSettings /> <BitcoinWalletSettings />
<LoanContractSettings /> <LoanContractSettings />
@ -81,7 +90,7 @@ let AccountSettings = React.createClass({
if (this.state.currentUser.username) { if (this.state.currentUser.username) {
content = ( content = (
<Form <Form
url={apiUrls.users_username} url={ApiUrls.users_username}
handleSuccess={this.handleSuccess}> handleSuccess={this.handleSuccess}>
<Property <Property
name='username' name='username'
@ -107,7 +116,7 @@ let AccountSettings = React.createClass({
); );
profile = ( profile = (
<Form <Form
url={apiUrls.users_profile} url={ApiUrls.users_profile}
handleSuccess={this.handleSuccess} handleSuccess={this.handleSuccess}
getFormData={this.getFormDataProfile}> getFormData={this.getFormDataProfile}>
<Property <Property
@ -117,11 +126,11 @@ let AccountSettings = React.createClass({
<InputCheckbox <InputCheckbox
defaultChecked={this.state.currentUser.profile.hash_locally}> defaultChecked={this.state.currentUser.profile.hash_locally}>
<span> <span>
{' ' + getLangText('Enable hash option for slow connections. ' + {' ' + getLangText('Enable hash option, e.g. slow connections or to keep piece private')}
'Computes and uploads a hash of the work instead.')}
</span> </span>
</InputCheckbox> </InputCheckbox>
</Property> </Property>
<hr />
{/*<Property {/*<Property
name='language' name='language'
label={getLangText('Choose your Language')} label={getLangText('Choose your Language')}
@ -136,7 +145,6 @@ let AccountSettings = React.createClass({
</option> </option>
</select> </select>
</Property>*/} </Property>*/}
<hr />
</Form> </Form>
); );
} }
@ -258,14 +266,14 @@ let FileUploader = React.createClass({
fileClass: 'contract' fileClass: 'contract'
}} }}
createBlobRoutine={{ createBlobRoutine={{
url: apiUrls.ownership_loans_contract url: ApiUrls.ownership_loans_contract
}} }}
validation={{ validation={{
itemLimit: 100000, itemLimit: 100000,
sizeLimit: '10000000' sizeLimit: '10000000'
}} }}
session={{ session={{
endpoint: apiUrls.ownership_loans_contract, endpoint: ApiUrls.ownership_loans_contract,
customHeaders: { customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken) 'X-CSRFToken': getCookie(AppConstants.csrftoken)
}, },
@ -333,45 +341,50 @@ let APISettings = React.createClass({
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
render() { getApplications(){
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />; let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
if (this.state.applications.length > -1) { if (this.state.applications.length > -1) {
content = this.state.applications.map(function(app, i) { content = this.state.applications.map(function(app, i) {
return ( return (
<Property <ActionPanel
name={app.name} name={app.name}
label={app.name} key={i}
key={i}> content={
<div className="row-same-height"> <div>
<div className="no-padding col-xs-6 col-sm-10 col-xs-height col-middle"> <div className='ascribe-panel-title'>
{'Bearer ' + app.bearer_token.token} {app.name}
</div>
<div className="ascribe-panel-subtitle">
{'Bearer ' + app.bearer_token.token}
</div>
</div> </div>
<div className="col-xs-6 col-sm-2 col-xs-height"> }
<button buttons={
className="pull-right btn btn-default btn-sm" <div className="pull-right">
onClick={this.handleTokenRefresh} <div className="pull-right">
data-id={app.name}> <button
{getLangText('REFRESH')} className="pull-right btn btn-default btn-sm"
</button> onClick={this.handleTokenRefresh}
data-id={app.name}>
{getLangText('REFRESH')}
</button>
</div>
</div> </div>
</div> }/>
</Property>); );
}, this); }, this);
content = (
<div>
<Form>
{content}
<hr />
</Form>
</div>);
} }
return content;
},
render() {
return ( return (
<CollapsibleParagraph <CollapsibleParagraph
title={getLangText('API Integration')} title={getLangText('API Integration')}
show={true} show={true}
defaultExpanded={this.props.defaultExpanded}> defaultExpanded={this.props.defaultExpanded}>
<Form <Form
url={apiUrls.applications} url={ApiUrls.applications}
handleSuccess={this.handleCreateSuccess}> handleSuccess={this.handleCreateSuccess}>
<Property <Property
name='name' name='name'
@ -386,7 +399,7 @@ let APISettings = React.createClass({
<pre> <pre>
Usage: curl &lt;url&gt; -H 'Authorization: Bearer &lt;token&gt;' Usage: curl &lt;url&gt; -H 'Authorization: Bearer &lt;token&gt;'
</pre> </pre>
{content} {this.getApplications()}
</CollapsibleParagraph> </CollapsibleParagraph>
); );
} }

View File

@ -0,0 +1,33 @@
'use strict';
import alt from '../../../../alt';
import Q from 'q';
import PrizeFetcher from '../fetchers/prize_fetcher';
class PrizeActions {
constructor() {
this.generateActions(
'updatePrize'
);
}
fetchPrize() {
return Q.Promise((resolve, reject) => {
PrizeFetcher
.fetch()
.then((res) => {
this.actions.updatePrize({
prize: res.prize
});
resolve(res);
})
.catch((err) => {
console.logGlobal(err);
reject(err);
});
});
}
}
export default alt.createActions(PrizeActions);

View File

@ -0,0 +1,76 @@
'use strict';
import alt from '../../../../alt';
import Q from 'q';
import PrizeJuryFetcher from '../fetchers/prize_jury_fetcher';
class PrizeJuryActions {
constructor() {
this.generateActions(
'updatePrizeJury',
'removePrizeJury',
'activatePrizeJury'
);
}
fetchJury() {
return Q.Promise((resolve, reject) => {
PrizeJuryFetcher
.fetch()
.then((res) => {
this.actions.updatePrizeJury(res.members);
resolve(res);
})
.catch((err) => {
console.logGlobal(err);
reject(err);
});
});
}
activateJury(email) {
return Q.Promise((resolve, reject) => {
PrizeJuryFetcher
.activate(email)
.then((res) => {
resolve(res);
})
.catch((err) => {
console.logGlobal(err);
reject(err);
});
});
}
revokeJury(email) {
return Q.Promise((resolve, reject) => {
PrizeJuryFetcher
.delete(email)
.then((res) => {
this.actions.removePrizeJury(email);
resolve(res);
})
.catch((err) => {
console.logGlobal(err);
reject(err);
});
});
}
resendJuryInvitation(email) {
return Q.Promise((resolve, reject) => {
PrizeJuryFetcher
.resend(email)
.then((res) => {
resolve(res);
})
.catch((err) => {
console.logGlobal(err);
reject(err);
});
});
}
}
export default alt.createActions(PrizeJuryActions);

View File

@ -0,0 +1,66 @@
'use strict';
import alt from '../../../../alt';
import Q from 'q';
import PrizeRatingFetcher from '../fetchers/prize_rating_fetcher';
class PrizeRatingActions {
constructor() {
this.generateActions(
'updatePrizeRatings',
'updatePrizeRating'
);
}
fetch() {
return Q.Promise((resolve, reject) => {
PrizeRatingFetcher
.fetch()
.then((res) => {
this.actions.updatePrizeRatings(res.ratings);
resolve(res);
})
.catch((err) => {
console.logGlobal(err);
reject(err);
});
});
}
fetchOne(pieceId) {
return Q.Promise((resolve, reject) => {
PrizeRatingFetcher
.fetchOne(pieceId)
.then((res) => {
this.actions.updatePrizeRating(res.rating.rating);
resolve(res);
})
.catch((err) => {
console.logGlobal(err);
reject(err);
});
});
}
createRating(pieceId, rating) {
return Q.Promise((resolve, reject) => {
PrizeRatingFetcher
.rate(pieceId, rating)
.then((res) => {
this.actions.updatePrizeRating(res.rating.rating);
resolve(res);
})
.catch((err) => {
console.logGlobal(err);
reject(err);
});
});
}
updateRating(rating) {
this.actions.updatePrizeRating(rating);
}
}
export default alt.createActions(PrizeRatingActions);

View File

@ -0,0 +1,54 @@
'use strict';
import React from 'react';
import classNames from 'classnames';
import ModalWrapper from '../../../../ascribe_modal/modal_wrapper';
import PieceSubmitToPrizeForm from '../../../../ascribe_forms/form_submit_to_prize';
import { getLangText } from '../../../../../utils/lang_utils';
let SubmitToPrizeButton = React.createClass({
propTypes: {
className: React.PropTypes.string,
handleSuccess: React.PropTypes.func,
piece: React.PropTypes.object.isRequired
},
getSubmitButton() {
if (this.props.piece.prize) {
return (
<button
disabled
className="btn btn-default btn-xs pull-right">
{getLangText('Submitted to prize')} <span className="glyphicon glyphicon-ok"
aria-hidden="true"></span>
</button>
);
}
else {
return (
<button
className={classNames('btn', 'btn-default', 'btn-xs', this.props.className)}>
{getLangText('Submit to prize')}
</button>
);
}
},
render() {
return (
<ModalWrapper
trigger={this.getSubmitButton()}
handleSuccess={this.props.handleSuccess}
title={getLangText('Submit to prize')}>
<PieceSubmitToPrizeForm
piece={this.props.piece}
handleSuccess={this.props.handleSuccess}/>
</ModalWrapper>
);
}
});
export default SubmitToPrizeButton;

View File

@ -2,9 +2,14 @@
import React from 'react'; import React from 'react';
import StarRating from 'react-star-rating';
import PieceActions from '../../../../../actions/piece_actions'; import PieceActions from '../../../../../actions/piece_actions';
import PieceStore from '../../../../../stores/piece_store'; import PieceStore from '../../../../../stores/piece_store';
import PrizeRatingActions from '../../actions/prize_rating_actions';
import PrizeRatingStore from '../../stores/prize_rating_store';
import Piece from '../../../../../components/ascribe_detail/piece'; import Piece from '../../../../../components/ascribe_detail/piece';
import AppConstants from '../../../../../constants/application_constants'; import AppConstants from '../../../../../constants/application_constants';
@ -70,6 +75,34 @@ let PrizePieceDetails = React.createClass({
propTypes: { propTypes: {
piece: React.PropTypes.object piece: React.PropTypes.object
}, },
getInitialState() {
return PrizeRatingStore.getState();
},
onChange(state) {
this.setState(state);
},
componentDidMount() {
PrizeRatingStore.listen(this.onChange);
PrizeRatingActions.fetchOne(this.props.piece.id);
},
componentWillUnmount() {
// Every time we're leaving the piece detail page,
// just reset the piece that is saved in the piece store
// as it will otherwise display wrong/old data once the user loads
// the piece detail a second time
PrizeRatingActions.updateRating({});
PrizeRatingStore.unlisten(this.onChange);
},
onRatingClick(event, args) {
event.preventDefault();
PrizeRatingActions.createRating(this.props.piece.id, args.rating);
},
render() { render() {
if (this.props.piece.prize if (this.props.piece.prize
&& this.props.piece.prize.name && this.props.piece.prize.name

View File

@ -6,9 +6,37 @@ import Router from 'react-router';
import ButtonLink from 'react-router-bootstrap/lib/ButtonLink'; import ButtonLink from 'react-router-bootstrap/lib/ButtonLink';
import ButtonGroup from 'react-bootstrap/lib/ButtonGroup'; import ButtonGroup from 'react-bootstrap/lib/ButtonGroup';
let Link = Router.Link; import UserStore from '../../../../stores/user_store';
import UserActions from '../../../../actions/user_actions';
let Landing = React.createClass({ let Landing = React.createClass({
mixins: [Router.Navigation],
getInitialState() {
return UserStore.getState();
},
componentDidMount() {
UserStore.listen(this.onChange);
UserActions.fetchCurrentUser();
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
// if user is already logged in, redirect him to piece list
if(this.state.currentUser && this.state.currentUser.email) {
// FIXME: hack to redirect out of the dispatch cycle
window.setTimeout(() => this.replaceWith('pieces'), 0);
}
},
render() { render() {
return ( return (
<div className="container"> <div className="container">
@ -18,7 +46,7 @@ let Landing = React.createClass({
<p> <p>
This is the submission page for Sluice_screens ↄc Prize 2015. This is the submission page for Sluice_screens ↄc Prize 2015.
</p> </p>
<ButtonGroup className="enter" bsSize="large" vertical block> <ButtonGroup className="enter" bsSize="large" vertical>
<ButtonLink to="signup"> <ButtonLink to="signup">
Sign up to submit Sign up to submit
</ButtonLink> </ButtonLink>

View File

@ -0,0 +1,316 @@
'use strict';
import React from 'react';
import UserStore from '../../../../stores/user_store';
import UserActions from '../../../../actions/user_actions';
import PrizeActions from '../actions/prize_actions';
import PrizeStore from '../stores/prize_store';
import PrizeJuryActions from '../actions/prize_jury_actions';
import PrizeJuryStore from '../stores/prize_jury_store';
import SettingsContainer from '../../../settings_container';
import CollapsibleParagraph from '../../../ascribe_collapsible/collapsible_paragraph';
import Form from '../../../ascribe_forms/form';
import Property from '../../../ascribe_forms/property';
import ActionPanel from '../../../ascribe_panel/action_panel';
import GlobalNotificationModel from '../../../../models/global_notification_model';
import GlobalNotificationActions from '../../../../actions/global_notification_actions';
import AppConstants from '../../../../constants/application_constants';
import ApiUrls from '../../../../constants/api_urls';
import { getLangText } from '../../../../utils/lang_utils';
let Settings = React.createClass({
getInitialState() {
return UserStore.getState();
},
componentDidMount() {
UserStore.listen(this.onChange);
UserActions.fetchCurrentUser();
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
render() {
let prizeSettings = null;
if (this.state.currentUser.is_admin){
prizeSettings = <PrizeSettings />;
}
return (
<SettingsContainer>
{prizeSettings}
</SettingsContainer>
);
}
});
let PrizeSettings = React.createClass({
getInitialState() {
return PrizeStore.getState();
},
componentDidMount() {
PrizeStore.listen(this.onChange);
PrizeActions.fetchPrize();
},
componentWillUnmount() {
PrizeStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
render() {
return (
<CollapsibleParagraph
title={'Prize Settings for ' + this.state.prize.name}
show={true}
defaultExpanded={true}>
<Form >
<Property
name='prize_name'
label={getLangText('Prize name')}
editable={false}>
<pre className="ascribe-pre">{this.state.prize.name}</pre>
</Property>
<Property
name='prize_rounds'
label={getLangText('Active round/Number of rounds')}
editable={false}>
<pre className="ascribe-pre">{this.state.prize.active_round}/{this.state.prize.rounds}</pre>
</Property>
<Property
name='num_submissions'
label={getLangText('Allowed number of submissions per user')}
editable={false}>
<pre className="ascribe-pre">{this.state.prize.num_submissions}</pre>
</Property>
<hr />
</Form>
<PrizeJurySettings
prize={this.state.prize}/>
</CollapsibleParagraph>
);
}
});
let PrizeJurySettings = React.createClass({
propTypes: {
prize: React.PropTypes.object
},
getInitialState() {
return PrizeJuryStore.getState();
},
componentDidMount() {
PrizeJuryStore.listen(this.onChange);
PrizeJuryActions.fetchJury();
},
componentWillUnmount() {
PrizeJuryStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
handleCreateSuccess(response) {
PrizeJuryActions.fetchJury();
let notification = new GlobalNotificationModel(response.notification, 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
this.refs.form.refs.email.refs.input.getDOMNode().value = null;
},
handleActivate(event) {
let email = event.target.getAttribute('data-id');
PrizeJuryActions.activateJury(email).then((response) => {
PrizeJuryActions.fetchJury();
let notification = new GlobalNotificationModel(response.notification, 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
});
},
handleRevoke(event) {
let email = event.target.getAttribute('data-id');
PrizeJuryActions.revokeJury(email).then((response) => {
let notification = new GlobalNotificationModel(response.notification, 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
});
},
handleResend(event) {
let email = event.target.getAttribute('data-id');
PrizeJuryActions.resendJuryInvitation(email).then((response) => {
let notification = new GlobalNotificationModel(response.notification, 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
});
},
getMembersPending() {
return this.state.membersPending.map(function(member, i) {
return (
<ActionPanel
name={member.email}
key={i}
content={
<div>
<div className='ascribe-panel-title'>
{member.email}
</div>
<div className="ascribe-panel-subtitle">
{member.status}
</div>
</div>
}
buttons={
<div className="pull-right">
<button
className="btn btn-default btn-sm margin-left-2px"
onClick={this.handleResend}
data-id={member.email}>
{getLangText('RESEND')}
</button>
<button
className="btn btn-default btn-sm ascribe-btn-gray margin-left-2px"
onClick={this.handleRevoke}
data-id={member.email}>
{getLangText('REVOKE')}
</button>
</div>
}/>
);
}, this);
},
getMembersActive() {
return this.state.membersActive.map(function(member, i) {
return (
<ActionPanel
name={member.email}
key={i}
content={
<div>
<div className='ascribe-panel-title'>
{member.email}
</div>
<div className="ascribe-panel-subtitle">
{member.status}
</div>
</div>
}
buttons={
<button
className="btn btn-default btn-sm ascribe-btn-gray"
onClick={this.handleRevoke}
data-id={member.email}>
{getLangText('REVOKE')}
</button>
}/>
);
}, this);
},
getMembersInactive() {
return this.state.membersInactive.map(function(member, i) {
return (
<ActionPanel
name={member.email}
key={i}
content={
<div>
<div className='ascribe-panel-title'>
{member.email}
</div>
<div className="ascribe-panel-subtitle">
{member.status}
</div>
</div>
}
buttons={
<button
className="btn btn-default btn-sm"
onClick={this.handleActivate}
data-id={member.email}>
{getLangText('ACTIVATE')}
</button>
}/>
);
}, this);
},
getMembers() {
let content = (
<div style={{textAlign: 'center'}}>
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
</div>);
if (this.state.members.length > -1) {
content = (
<div style={{padding: '1em'}}>
<CollapsibleParagraph
title={'Active Jury Members'}
show={true}
defaultExpanded={true}>
{this.getMembersActive()}
</CollapsibleParagraph>
<CollapsibleParagraph
title={'Pending Jury Invitations'}
show={true}
defaultExpanded={true}>
{this.getMembersPending()}
</CollapsibleParagraph>
<CollapsibleParagraph
title={'Deactivated Jury Members'}
show={true}
defaultExpanded={false}>
{this.getMembersInactive()}
</CollapsibleParagraph>
</div>);
}
return content;
},
render() {
return (
<div>
<Form
url={ApiUrls.jurys}
handleSuccess={this.handleCreateSuccess}
ref='form'
buttonSubmitText='INVITE'>
<div className="ascribe-form-header">
<h4 style={{margin: '30px 0px 10px 10px'}}>Jury Members</h4>
</div>
<Property
name='email'
label={getLangText('New jury member')}>
<input
type="email"
placeholder={getLangText('Enter an email to invite a jury member')}
required/>
</Property>
<hr />
</Form>
{this.getMembers()}
</div>
);
}
});
export default Settings;

View File

@ -1,15 +1,22 @@
'use strict'; 'use strict';
import AppConstants from '../../../../constants/application_constants'; import AppPrizeConstants from './application_prize_constants';
function getApiUrls(subdomain) { function getApiUrls(subdomain) {
return { return {
'pieces_list': AppConstants.apiEndpoint + 'prize/' + subdomain + '/pieces/', 'pieces_list': AppPrizeConstants.prizeApiEndpoint + subdomain + '/pieces/',
'users_login': AppConstants.apiEndpoint + 'prize/' + subdomain + '/users/login/', 'users_login': AppPrizeConstants.prizeApiEndpoint + subdomain + '/users/login/',
'users_signup': AppConstants.apiEndpoint + 'prize/' + subdomain + '/users/', 'users_signup': AppPrizeConstants.prizeApiEndpoint + subdomain + '/users/',
'user': AppConstants.apiEndpoint + 'prize/' + subdomain + '/users/', 'user': AppPrizeConstants.prizeApiEndpoint + subdomain + '/users/',
'piece_submit_to_prize': AppConstants.apiEndpoint + 'prize/' + subdomain + '/pieces/${piece_id}/submit/', 'piece_submit_to_prize': AppPrizeConstants.prizeApiEndpoint + subdomain + '/pieces/${piece_id}/submit/',
'piece': AppConstants.apiEndpoint + 'prize/' + subdomain + '/pieces/${piece_id}/' 'piece': AppPrizeConstants.prizeApiEndpoint + subdomain + '/pieces/${piece_id}/',
'prize': AppPrizeConstants.prizeApiEndpoint + subdomain + '/',
'jurys': AppPrizeConstants.prizeApiEndpoint + subdomain + '/jury/',
'jury': AppPrizeConstants.prizeApiEndpoint + subdomain + '/jury/${email}/',
'jury_activate': AppPrizeConstants.prizeApiEndpoint + subdomain + '/jury/${email}/activate/',
'jury_resend': AppPrizeConstants.prizeApiEndpoint + subdomain + '/jury/${email}/resend/',
'ratings': AppPrizeConstants.prizeApiEndpoint + subdomain + '/ratings/',
'rating': AppPrizeConstants.prizeApiEndpoint + subdomain + '/ratings/${piece_id}/'
}; };
} }

View File

@ -0,0 +1,9 @@
'use strict';
import AppConstants from '../../../../constants/application_constants';
let constants = {
prizeApiEndpoint: AppConstants.apiEndpoint + 'prizes/'
};
export default constants;

View File

@ -0,0 +1,12 @@
'use strict';
import requests from '../../../../utils/requests';
let PrizeFetcher = {
fetch() {
return requests.get('prize');
}
};
export default PrizeFetcher;

View File

@ -0,0 +1,24 @@
'use strict';
import requests from '../../../../utils/requests';
let PrizeJuryFetcher = {
fetch() {
return requests.get('jurys');
},
activate(email) {
return requests.post('jury_activate', {'email': email});
},
delete(email) {
return requests.delete('jury', {'email': email});
},
resend(email) {
return requests.post('jury_resend', {'email': email});
}
};
export default PrizeJuryFetcher;

View File

@ -0,0 +1,20 @@
'use strict';
import requests from '../../../../utils/requests';
let PrizeRatingFetcher = {
fetch() {
return requests.get('rating');
},
fetchOne(pieceId) {
return requests.get('rating', {'piece_id': pieceId});
},
rate(pieceId, rating) {
return requests.post('ratings', {body: {'piece_id': pieceId, 'rating': rating}});
}
};
export default PrizeRatingFetcher;

View File

@ -12,7 +12,7 @@ import PrizeRegisterPiece from './components/register_piece';
import PrizePieceList from './components/piece_list'; import PrizePieceList from './components/piece_list';
import PrizePieceContainer from './components/ascribe_detail/piece_container'; import PrizePieceContainer from './components/ascribe_detail/piece_container';
import EditionContainer from '../../ascribe_detail/edition_container'; import EditionContainer from '../../ascribe_detail/edition_container';
import SettingsContainer from '../../../components/settings_container'; import SettingsContainer from './components/settings_container';
import App from './app'; import App from './app';
import AppConstants from '../../../constants/application_constants'; import AppConstants from '../../../constants/application_constants';
@ -21,7 +21,7 @@ let Route = Router.Route;
let baseUrl = AppConstants.baseUrl; let baseUrl = AppConstants.baseUrl;
function getRoutes(commonRoutes) { function getRoutes() {
return ( return (
<Route name="app" path={baseUrl} handler={App}> <Route name="app" path={baseUrl} handler={App}>
<Route name="landing" path={baseUrl} handler={Landing} /> <Route name="landing" path={baseUrl} handler={Landing} />

View File

@ -0,0 +1,35 @@
'use strict';
import alt from '../../../../alt';
import PrizeJuryActions from '../actions/prize_jury_actions';
class PrizeJuryStore {
constructor() {
this.members = [];
this.membersActive = [];
this.membersPending = [];
this.membersInactive = [];
this.bindActions(PrizeJuryActions);
}
onUpdatePrizeJury( members ) {
this.members = members;
this.splitJuryMembers();
}
onRemovePrizeJury( email ) {
let memberInactive = this.members.filter((item)=> item.email === email );
this.membersActive = this.membersActive.filter((item)=> item.email !== email );
this.membersPending = this.membersPending.filter((item)=> item.email !== email );
this.membersInactive = this.membersInactive.concat(memberInactive);
}
splitJuryMembers(){
this.membersActive = this.members.filter((item)=> item.status === 'Invitation accepted' );
this.membersPending = this.members.filter((item)=> item.status === 'Invitation pending' );
this.membersInactive = this.members.filter((item)=> item.status === 'Deactivated' );
}
}
export default alt.createStore(PrizeJuryStore, 'PrizeJuryStore');

View File

@ -0,0 +1,23 @@
'use strict';
import alt from '../../../../alt';
import PrizeRatingActions from '../actions/prize_rating_actions';
class PrizeRatingStore {
constructor() {
this.ratings = [];
this.currentRating = null;
this.bindActions(PrizeRatingActions);
}
onUpdatePrizeRatings( ratings ) {
this.ratings = ratings;
}
onUpdatePrizeRating( rating ) {
this.currentRating = parseInt(rating, 10);
}
}
export default alt.createStore(PrizeRatingStore, 'PrizeRatingStore');

View File

@ -0,0 +1,18 @@
'use strict';
import alt from '../../../../alt';
import PrizeActions from '../actions/prize_actions';
class PrizeStore {
constructor() {
this.prize = [];
this.bindActions(PrizeActions);
}
onUpdatePrize({ prize }) {
this.prize = prize;
}
}
export default alt.createStore(PrizeStore, 'PrizeStore');

View File

@ -5,7 +5,7 @@ import getPrizeApiUrls from '../components/whitelabel/prize/constants/api_urls';
import { update } from '../utils/general_utils'; import { update } from '../utils/general_utils';
let apiUrls = { let ApiUrls = {
'applications': AppConstants.apiEndpoint + 'applications/', 'applications': AppConstants.apiEndpoint + 'applications/',
'application_token_refresh': AppConstants.apiEndpoint + 'applications/refresh_token/', 'application_token_refresh': AppConstants.apiEndpoint + 'applications/refresh_token/',
'blob_digitalworks': AppConstants.apiEndpoint + 'blob/digitalworks/', 'blob_digitalworks': AppConstants.apiEndpoint + 'blob/digitalworks/',
@ -52,7 +52,8 @@ let apiUrls = {
'users_profile': AppConstants.apiEndpoint + 'users/profile/', 'users_profile': AppConstants.apiEndpoint + 'users/profile/',
'wallet_settings': AppConstants.apiEndpoint + 'users/wallet_settings/', 'wallet_settings': AppConstants.apiEndpoint + 'users/wallet_settings/',
'whitelabel_settings': AppConstants.apiEndpoint + 'whitelabel/settings/${subdomain}/', 'whitelabel_settings': AppConstants.apiEndpoint + 'whitelabel/settings/${subdomain}/',
'delete_s3_file': AppConstants.serverUrl + 's3/delete/' 'delete_s3_file': AppConstants.serverUrl + 's3/delete/',
'prize_list': AppConstants.apiEndpoint + 'prize/'
}; };
@ -62,7 +63,7 @@ export function updateApiUrls(type, subdomain) {
if (type === 'prize') { if (type === 'prize') {
newUrls = getPrizeApiUrls(subdomain); newUrls = getPrizeApiUrls(subdomain);
} }
update(apiUrls, newUrls); update(ApiUrls, newUrls);
} }
export default apiUrls; export default ApiUrls;

View File

@ -10,9 +10,9 @@ let constants = {
'apiEndpoint': window.API_ENDPOINT, 'apiEndpoint': window.API_ENDPOINT,
'serverUrl': window.SERVER_URL, 'serverUrl': window.SERVER_URL,
'baseUrl': window.BASE_URL, 'baseUrl': window.BASE_URL,
'aclList': ['acl_coa', 'acl_consign', 'acl_delete', 'acl_download', 'acl_edit', 'acl_editions', 'aclList': ['acl_coa', 'acl_consign', 'acl_delete', 'acl_download', 'acl_edit', 'acl_create_editions', 'acl_view_editions',
'acl_loan', 'acl_share', 'acl_transfer', 'acl_unconsign', 'acl_unshare', 'acl_view', 'acl_loan', 'acl_share', 'acl_transfer', 'acl_unconsign', 'acl_unshare', 'acl_view',
'acl_withdraw_transfer'], 'acl_withdraw_transfer', 'acl_submit_to_prize'],
'version': 0.1, 'version': 0.1,
'csrftoken': 'csrftoken2', 'csrftoken': 'csrftoken2',

View File

@ -3,20 +3,26 @@
import requests from '../utils/requests'; import requests from '../utils/requests';
import { generateOrderingQueryParams } from '../utils/fetch_api_utils'; import { generateOrderingQueryParams } from '../utils/fetch_api_utils';
import { mergeOptions } from '../utils/general_utils';
let EditionListFetcher = { let EditionListFetcher = {
/** /**
* Fetches a list of editions from the API. * Fetches a list of editions from the API.
*/ */
fetch(pieceId, page, pageSize, orderBy, orderAsc) { fetch(pieceId, page, pageSize, orderBy, orderAsc, filterBy) {
let ordering = generateOrderingQueryParams(orderBy, orderAsc); let ordering = generateOrderingQueryParams(orderBy, orderAsc);
return requests.get('editions_list', {
'piece_id': pieceId, let queryParams = mergeOptions(
page, {
pageSize, page,
ordering pageSize,
}); ordering,
piece_id: pieceId
},
filterBy
);
return requests.get('editions_list', queryParams);
} }
}; };

View File

@ -1,17 +1,31 @@
'use strict'; 'use strict';
import { generateOrderingQueryParams } from '../utils/fetch_api_utils';
import requests from '../utils/requests'; import requests from '../utils/requests';
import { mergeOptions } from '../utils/general_utils';
import { generateOrderingQueryParams } from '../utils/fetch_api_utils';
let PieceListFetcher = { let PieceListFetcher = {
/** /**
* Fetches a list of pieces from the API. * Fetches a list of pieces from the API.
* Can be called with all supplied queryparams the API. * Can be called with all supplied queryparams the API.
*/ */
fetch(page, pageSize, search, orderBy, orderAsc) { fetch(page, pageSize, search, orderBy, orderAsc, filterBy) {
let ordering = generateOrderingQueryParams(orderBy, orderAsc); let ordering = generateOrderingQueryParams(orderBy, orderAsc);
return requests.get('pieces_list', { page, pageSize, search, ordering });
// filterBy is an object of acl key-value pairs.
// The values are booleans
let queryParams = mergeOptions(
{
page,
pageSize,
search,
ordering
},
filterBy
);
return requests.get('pieces_list', queryParams);
}, },
fetchRequestActions() { fetchRequestActions() {

View File

@ -0,0 +1,12 @@
'use strict';
import requests from '../utils/requests';
let PrizeListFetcher = {
fetch() {
return requests.get('prize_list');
}
};
export default PrizeListFetcher;

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import requests from '../utils/requests'; import requests from '../utils/requests';
import apiUrls from '../constants/api_urls'; import ApiUrls from '../constants/api_urls';
let UserFetcher = { let UserFetcher = {
/** /**
@ -13,7 +13,7 @@ let UserFetcher = {
}, },
logout() { logout() {
return requests.get(apiUrls.users_logout); return requests.get(ApiUrls.users_logout);
} }
}; };

View File

@ -1,21 +0,0 @@
'use strict';
import React from 'react';
import AlertDismissable from '../components/ascribe_forms/alert';
let AlertMixin = {
setAlerts(errors){
let alerts = errors.map((error) => {
return <AlertDismissable error={error} key={error}/>;
});
this.setState({alerts: alerts});
},
clearAlerts(){
this.setState({alerts: null});
}
};
export default AlertMixin;

View File

@ -1,108 +0,0 @@
'use strict';
import requests from '../utils/requests';
import React from 'react';
import AlertDismissable from '../components/ascribe_forms/alert';
import { getLangText } from '../utils/lang_utils.js';
export const FormMixin = {
propTypes: {
editions: React.PropTypes.array,
currentUser: React.PropTypes.object
},
getInitialState() {
return {
submitted: false,
errors: []
};
},
submit(e) {
if (e) {
e.preventDefault();
}
this.setState({submitted: true});
this.clearErrors();
let action = (this.httpVerb && this.httpVerb()) || 'post';
this[action](e);
},
post(e){
requests
.post(this.url(e), { body: this.getFormData() })
.then(this.handleSuccess)
.catch(this.handleError);
},
delete(e){
requests
.delete(this.url(e))
.then(this.handleSuccess)
.catch(this.handleError);
},
clearErrors(){
for (var ref in this.refs){
if ('clearAlerts' in this.refs[ref]){
this.refs[ref].clearAlerts();
}
}
this.setState({errors: []});
},
handleSuccess(response){
if ('handleSuccess' in this.props){
this.props.handleSuccess(response);
}
},
handleError(err){
if (err.json) {
for (var input in err.json.errors){
if (this.refs && this.refs[input] && this.refs[input].state) {
this.refs[input].setAlerts( err.json.errors[input]);
} else {
this.setState({errors: this.state.errors.concat(err.json.errors[input])});
}
}
}
else {
// TODO translate?
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 + ', ' + getLangText('edition') + ' ' + edition.edition_number + '\"\n';
});
},
render(){
let alert = null;
if (this.state.errors.length > 0){
alert = this.state.errors.map((error) => {
return <AlertDismissable error={error} key={error}/>;
});
}
return (
<div>
{alert}
{this.renderForm()}
</div>
);
}
};
export default FormMixin;

View File

@ -1,12 +0,0 @@
'use strict';
let ModalMixin = {
onRequestHide(e){
if (e) {
e.preventDefault();
}
this.props.onRequestHide();
}
};
export default ModalMixin;

View File

@ -1,24 +0,0 @@
'use strict';
import { sumNumList } from '../utils/general_utils';
let TableColumnMixin = {
/**
* Generates the bootstrap grid column declarations automatically using
* the columnMap.
*/
calcColumnClasses(list, i, numOfColumns) {
let bootstrapClasses = ['col-xs-', 'col-sm-', 'col-md-', 'col-lg-'];
let listOfRowValues = list.map((column) => column.rowWidth );
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.');
} else {
return bootstrapClasses.join( listOfRowValues[i] + ' ') + listOfRowValues[i];
}
}
};
export default TableColumnMixin;

View File

@ -20,6 +20,8 @@ import CoaVerifyContainer from './components/coa_verify_container';
import RegisterPiece from './components/register_piece'; import RegisterPiece from './components/register_piece';
import PrizesDashboard from './components/ascribe_prizes_dashboard/prizes_dashboard';
let Route = Router.Route; let Route = Router.Route;
@ -35,6 +37,7 @@ const COMMON_ROUTES = (
<Route name="register_piece" path="register_piece" handler={RegisterPiece} /> <Route name="register_piece" path="register_piece" handler={RegisterPiece} />
<Route name="settings" path="settings" handler={SettingsContainer} /> <Route name="settings" path="settings" handler={SettingsContainer} />
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} /> <Route name="coa_verify" path="verify" handler={CoaVerifyContainer} />
<Route name="prizes" path="prizes" handler={PrizesDashboard} />
</Route> </Route>
); );

View File

@ -12,7 +12,7 @@ class EditionListStore {
this.bindActions(EditionsListActions); this.bindActions(EditionsListActions);
} }
onUpdateEditionList({pieceId, editionListOfPiece, page, pageSize, orderBy, orderAsc, count}) { onUpdateEditionList({pieceId, editionListOfPiece, page, pageSize, orderBy, orderAsc, count, filterBy}) {
/* /*
Basically there are two modes an edition list can be updated. Basically there are two modes an edition list can be updated.
@ -54,14 +54,14 @@ class EditionListStore {
this.editionList[pieceId].orderBy = orderBy; this.editionList[pieceId].orderBy = orderBy;
this.editionList[pieceId].orderAsc = orderAsc; this.editionList[pieceId].orderAsc = orderAsc;
this.editionList[pieceId].count = count; this.editionList[pieceId].count = count;
this.editionList[pieceId].filterBy = filterBy;
} }
/** /**
* We often just have to refresh the edition list for a certain pieceId, * We often just have to refresh the edition list for a certain pieceId,
* this method provides exactly that functionality without any side effects * this method provides exactly that functionality without any side effects
*/ */
onRefreshEditionList(pieceId) { onRefreshEditionList({pieceId, filterBy}) {
// It may happen that the user enters the site logged in already // It may happen that the user enters the site logged in already
// through /editions // through /editions
// If he then tries to delete a piece/edition and this method is called, // If he then tries to delete a piece/edition and this method is called,
@ -71,16 +71,28 @@ class EditionListStore {
return; return;
} }
const prevEditionListLength = this.editionList[pieceId].length; let prevEditionListLength = this.editionList[pieceId].length;
const prevEditionListPage = this.editionList[pieceId].page; let prevEditionListPage = this.editionList[pieceId].page;
const prevEditionListPageSize = this.editionList[pieceId].pageSize; let prevEditionListPageSize = this.editionList[pieceId].pageSize;
// we can also refresh the edition list using filterBy,
// if we decide not to do that then the old filter will just be applied.
if(filterBy && Object.keys(filterBy).length <= 0) {
filterBy = this.editionList[pieceId].filterBy;
prevEditionListLength = 10;
prevEditionListPage = 1;
prevEditionListPageSize = 10;
}
// to clear an array, david walsh recommends to just set it's length to zero // to clear an array, david walsh recommends to just set it's length to zero
// http://davidwalsh.name/empty-array // http://davidwalsh.name/empty-array
this.editionList[pieceId].length = 0; this.editionList[pieceId].length = 0;
// refetch editions with adjusted page size // refetch editions with adjusted page size
EditionsListActions.fetchEditionList(pieceId, 1, prevEditionListLength, this.editionList[pieceId].orderBy, this.editionList[pieceId].orderAsc) EditionsListActions.fetchEditionList(pieceId, 1, prevEditionListLength,
this.editionList[pieceId].orderBy,
this.editionList[pieceId].orderAsc,
filterBy)
.then(() => { .then(() => {
// reset back to the normal pageSize and page // reset back to the normal pageSize and page
this.editionList[pieceId].page = prevEditionListPage; this.editionList[pieceId].page = prevEditionListPage;
@ -121,9 +133,21 @@ class EditionListStore {
} }
onToggleEditionList(pieceId) { onToggleEditionList(pieceId) {
this.isEditionListOpenForPieceId[pieceId] = { this.isEditionListOpenForPieceId[pieceId] = {
show: this.isEditionListOpenForPieceId[pieceId] ? !this.isEditionListOpenForPieceId[pieceId].show : true show: this.isEditionListOpenForPieceId[pieceId] ? !this.isEditionListOpenForPieceId[pieceId].show : true
}; };
// When loading all editions of a piece, closing the table and then applying the filter
// the merge fails, as the edition list is not refreshed when closed.
// Therefore in the case of a filter application when closed, we need to reload the
// edition list
if(!this.isEditionListOpenForPieceId[pieceId].show) {
// to clear an array, david walsh recommends to just set it's length to zero
// http://davidwalsh.name/empty-array
this.editionList[pieceId].length = 0;
}
} }
onCloseAllEditionLists() { onCloseAllEditionLists() {

View File

@ -17,6 +17,12 @@ class LoanContractStore {
this.contractUrl = contractUrl; this.contractUrl = contractUrl;
this.contractEmail = contractEmail; this.contractEmail = contractEmail;
} }
onFlushLoanContract() {
this.contractKey = null;
this.contractUrl = null;
this.contractEmail = null;
}
} }
export default alt.createStore(LoanContractStore, 'LoanContractStore'); export default alt.createStore(LoanContractStore, 'LoanContractStore');

View File

@ -26,16 +26,18 @@ class PieceListStore {
this.search = ''; this.search = '';
this.orderBy = 'artist_name'; this.orderBy = 'artist_name';
this.orderAsc = true; this.orderAsc = true;
this.filterBy = {};
this.bindActions(PieceListActions); this.bindActions(PieceListActions);
} }
onUpdatePieceList({ page, pageSize, search, pieceList, orderBy, orderAsc, pieceListCount }) { onUpdatePieceList({ page, pageSize, search, pieceList, orderBy, orderAsc, pieceListCount, filterBy }) {
this.page = page; this.page = page;
this.pageSize = pageSize; this.pageSize = pageSize;
this.search = search; this.search = search;
this.orderAsc = orderAsc; this.orderAsc = orderAsc;
this.orderBy = orderBy; this.orderBy = orderBy;
this.pieceListCount = pieceListCount; this.pieceListCount = pieceListCount;
this.filterBy = filterBy;
/** /**
* Pagination - Known Issue: * Pagination - Known Issue:
@ -80,7 +82,6 @@ class PieceListStore {
let piece = filteredPieceList[0]; let piece = filteredPieceList[0];
piece[key] = value; piece[key] = value;
} else { } else {
throw new Error('Could not find a matching piece in piece list since its either not there or piecelist contains duplicates.'); throw new Error('Could not find a matching piece in piece list since its either not there or piecelist contains duplicates.');
} }

View File

@ -0,0 +1,20 @@
'use strict';
import alt from '../alt';
import PrizeListActions from '../actions/prize_list_actions';
class PrizeListStore {
constructor() {
this.prizeList = [];
this.prizeListCount = -1;
this.bindActions(PrizeListActions);
}
onUpdatePrizeList({ prizeList, prizeListCount }) {
this.prizeList = prizeList;
this.prizeListCount = prizeListCount;
}
}
export default alt.createStore(PrizeListStore, 'PrizeListStore');

51
js/utils/form_utils.js Normal file
View File

@ -0,0 +1,51 @@
'use strict';
import { getLangText } from './lang_utils';
/**
* Generates a message for submitting a form
* @param {string} aclName Enum name of a acl
* @param {string} entities Already computed name of entities
* @param {string} senderName Name of the sender
* @return {string} Completed message
*/
export function getAclFormMessage(aclName, entities, senderName) {
let message = '';
message += getLangText('Hi');
message += ',\n\n';
if(aclName === 'acl_transfer') {
message += getLangText('I transfer ownership of');
} else if(aclName === 'acl_consign') {
message += getLangText('I consign');
} else if(aclName === 'acl_unconsign') {
message += getLangText('I un-consign');
} else if(aclName === 'acl_loan') {
message += getLangText('I loan');
} else if(aclName === 'acl_share') {
message += getLangText('I share');
} else {
throw new Error('Your specified aclName did not match a an acl class.');
}
message += ':\n';
message += entities;
if(aclName === 'acl_transfer' || aclName === 'acl_loan' || aclName === 'acl_consign') {
message += getLangText('to you');
} else if(aclName === 'acl_unconsign') {
message += getLangText('from you');
} else if(aclName === 'acl_share') {
message += getLangText('with you');
} else {
throw new Error('Your specified aclName did not match a an acl class.');
}
message += '\n\n';
message += getLangText('Truly yours,');
message += '\n';
message += senderName;
return message;
}

Some files were not shown because too many files have changed in this diff Show More