1
0
mirror of https://github.com/ascribe/onion.git synced 2024-11-15 01:25:17 +01:00

Merge branch 'master' of bitbucket.org:ascribe/onion

This commit is contained in:
Tim Daubenschütz 2015-08-06 15:15:19 +02:00
commit 158f91f3f2
51 changed files with 994 additions and 317 deletions

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

@ -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

@ -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,10 +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 PieceListActions from '../../actions/piece_list_actions';
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({
@ -21,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) {
@ -47,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);
} }
}, },
@ -85,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

@ -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

@ -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

@ -86,7 +86,8 @@ 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(this.props.edition.parent);
EditionListActions.closeAllEditionLists(); EditionListActions.closeAllEditionLists();

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
@ -124,7 +126,8 @@ let Piece = React.createClass({
// btw.: It's not sufficient to just set num_editions to numEditions, since a single accordion // 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. // 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 // 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); 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

@ -15,6 +15,9 @@ import { mergeOptionsWithDuplicates } from '../../utils/general_utils';
let Form = React.createClass({ let Form = React.createClass({
propTypes: { propTypes: {
url: React.PropTypes.string, url: React.PropTypes.string,
buttons: React.PropTypes.object,
buttonSubmitText: React.PropTypes.string,
spinner: React.PropTypes.object,
handleSuccess: React.PropTypes.func, handleSuccess: React.PropTypes.func,
getFormData: React.PropTypes.func, getFormData: React.PropTypes.func,
children: React.PropTypes.oneOfType([ children: React.PropTypes.oneOfType([
@ -24,6 +27,12 @@ let Form = React.createClass({
className: React.PropTypes.string className: React.PropTypes.string
}, },
getDefaultProps() {
return {
buttonSubmitText: 'SAVE'
};
},
getInitialState() { getInitialState() {
return { return {
edited: false, edited: false,
@ -125,7 +134,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>

View File

@ -27,7 +27,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 +35,6 @@ let SignupForm = React.createClass({
submitMessage: getLangText('Sign up') submitMessage: getLangText('Sign up')
}; };
}, },
getInitialState() { getInitialState() {
return UserStore.getState(); return UserStore.getState();
}, },
@ -57,22 +56,32 @@ let SignupForm = React.createClass({
} }
}, },
getFormData() {
return this.getQuery();
},
handleSuccess(response){ handleSuccess(response){
if (response.user) {
let notification = new GlobalNotificationModel(getLangText('Sign up successful'), 'success', 50000); let notification = new GlobalNotificationModel(getLangText('Sign up successful'), 'success', 50000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
this.props.handleSuccess(getLangText('We sent an email to your address') + ' ' + response.user.email + ', ' + getLangText('please confirm') + '.'); 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.getFormData}
handleSuccess={this.handleSuccess} handleSuccess={this.handleSuccess}
buttons={ buttons={
<button type="submit" className="btn ascribe-btn ascribe-btn-login"> <button type="submit" className="btn ascribe-btn ascribe-btn-login">
@ -93,6 +102,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

@ -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

@ -0,0 +1,71 @@
'use strict';
import React from 'react';
let ActionPanel = React.createClass({
propTypes: {
title: React.PropTypes.string,
content: React.PropTypes.string,
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
});
},
getClassName() {
if(this.state.isFocused) {
return 'is-focused';
} else {
return '';
}
},
render() {
return (
<div
className={'ascribe-panel-wrapper ' + this.getClassName()}
onClick={this.handleFocus}
onFocus={this.handleFocus}>
<div className='ascribe-panel-title'>
{this.props.title}
</div>
<div className='ascribe-panel-content-wrapper'>
<span className="ascribe-panel-content pull-left">
{this.props.content}
</span>
<span className='ascribe-panel-buttons pull-right'>
{this.props.buttons}
</span>
</div>
</div>
);
}
});
export default ActionPanel;

View File

@ -76,7 +76,8 @@ 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) => {

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 that')}:</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;
label = param.split('_')[1];
}
return (
<MenuItem
key={i}
onClick={this.filterBy(param)}
className="filter-widget-item">
<div className="checkbox-line">
<span>
{getLangText('I can') + ' ' + getLangText(label)}
</span>
<input
readOnly
type="checkbox"
checked={this.props.filterBy[param]} />
</div> </div>
</MenuItem> </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

@ -34,7 +34,8 @@ let PieceList = React.createClass({
let page = this.getQuery().page || 1; let page = this.getQuery().page || 1;
PieceListStore.listen(this.onChange); PieceListStore.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,
this.state.orderBy, this.state.orderAsc, this.state.filterBy)
.then(PieceListActions.fetchPieceRequestActions()); .then(PieceListActions.fetchPieceRequestActions());
} }
}, },
@ -59,9 +60,9 @@ let PieceList = React.createClass({
// the site should go to the top // the site should go to the top
document.body.scrollTop = document.documentElement.scrollTop = 0; document.body.scrollTop = document.documentElement.scrollTop = 0;
return () => PieceListActions.fetchPieceList(page, this.state.pageSize, return () => 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,13 +80,22 @@ 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});
},
applyFilterBy(filterBy) {
PieceListActions.fetchPieceList(1, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, 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}); 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() {
@ -95,7 +105,9 @@ let PieceList = React.createClass({
<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

@ -101,7 +101,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 +140,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

@ -29,12 +29,19 @@ 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 />
@ -121,6 +128,7 @@ let AccountSettings = React.createClass({
</span> </span>
</InputCheckbox> </InputCheckbox>
</Property> </Property>
<hr />
{/*<Property {/*<Property
name='language' name='language'
label={getLangText('Choose your Language')} label={getLangText('Choose your Language')}
@ -135,7 +143,6 @@ let AccountSettings = React.createClass({
</option> </option>
</select> </select>
</Property>*/} </Property>*/}
<hr />
</Form> </Form>
); );
} }

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,31 @@
'use strict';
import alt from '../../../../alt';
import Q from 'q';
import PrizeJuryFetcher from '../fetchers/prize_jury_fetcher';
class PrizeJuryActions {
constructor() {
this.generateActions(
'updatePrizeJury'
);
}
fetchJury() {
return Q.Promise((resolve, reject) => {
PrizeJuryFetcher
.fetch()
.then((res) => {
this.actions.updatePrizeJury(res.members);
resolve(res);
})
.catch((err) => {
console.logGlobal(err);
reject(err);
});
});
}
}
export default alt.createActions(PrizeJuryActions);

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
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

@ -0,0 +1,250 @@
'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 FormPropertyHeader from '../../../ascribe_forms/form_property_header';
import ActionPanel from '../../../ascribe_panel/action_panel';
import Table from '../../../ascribe_table/table';
import TableItem from '../../../ascribe_table/table_item';
import TableItemText from '../../../ascribe_table/table_item_text';
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 { ColumnModel} from '../../../ascribe_table/models/table_models';
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;
},
render() {
let content = (
<div style={{textAlign: 'center'}}>
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
</div>);
if (this.state.members.length > -1) {
content = this.state.members.map(function(member, i) {
return (
<ActionPanel
name={member.email}
key={i}
title={member.email}
content={member.status}
buttons={<button
className="pull-right btn btn-default btn-sm"
data-id={member.name}>
{getLangText('RESEND INVITATION')}
</button>}
/>);
}, this);
content = (
<div>
{content}
</div>);
}
return (
<div>
<Form
url={apiUrls.jury}
handleSuccess={this.handleCreateSuccess}
ref='form'
buttonSubmitText='INVITE'>
<FormPropertyHeader>
<h4 style={{margin: '30px 0px 10px 10px'}}>Jury Members</h4>
</FormPropertyHeader>
<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>
{content}
</div>
);
}
});
let PrizesDashboard = React.createClass({
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 Settings;

View File

@ -1,15 +1,17 @@
'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 + '/',
'jury': AppPrizeConstants.prizeApiEndpoint + subdomain + '/jury/'
}; };
} }

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,12 @@
'use strict';
import requests from '../../../../utils/requests';
let PrizeJuryFetcher = {
fetch() {
return requests.get('jury');
}
};
export default PrizeJuryFetcher;

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,18 @@
'use strict';
import alt from '../../../../alt';
import PrizeJuryActions from '../actions/prize_jury_actions';
class PrizeJuryStore {
constructor() {
this.members = [];
this.bindActions(PrizeJuryActions);
}
onUpdatePrizeJury( members ) {
this.members = members;
}
}
export default alt.createStore(PrizeJuryStore, 'PrizeJuryStore');

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

@ -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/'
}; };

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, page,
pageSize, pageSize,
ordering 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,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,6 +54,7 @@ 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;
} }
/** /**
@ -80,7 +81,10 @@ class EditionListStore {
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,
this.editionList[pieceId].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;

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:

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');

13
sass/ascribe_panel.scss Normal file
View File

@ -0,0 +1,13 @@
.ascribe-panel-wrapper {
border: 1px solid #F5F5F5;
}
.ascribe-panel-title {
padding: 1em 0 1em 1.5em;
}
.ascribe-panel-content {
padding: .75em 0 .75em 1em;
font-size: 1em;
color: #616161;
}

View File

@ -5,5 +5,41 @@
} }
.search-bar { .search-bar {
max-width: 160px; max-width: 200px;
input {
height: 33px;
}
}
.ascribe-piece-list-toolbar-filter-widget {
margin-right: 1em;
.filter-widget-item {
> a {
padding-left: 0;
padding-right: 0;
}
}
.checkbox-line {
position: relative;
height: 25px;
span {
position: absolute;
left: 9px;
top: 3px;
cursor: pointer;
margin-right: 10px;
}
input {
position: absolute;
top: 2px;
right: 9px;
margin-left: 10px;
}
}
} }

View File

@ -22,10 +22,11 @@
} }
.ascribe-table-item-column { .ascribe-table-item-column {
display: table;
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro';
font-size: .8em; font-size: .8em;
height:3em; height:3em;
vertical-align: middle;
display: table-cell;
} }
.ascribe-table-item-column > * { .ascribe-table-item-column > * {
@ -37,7 +38,8 @@
} }
.ascribe-table-item-column > span > input { .ascribe-table-item-column > span > input {
margin-top:18px; margin-top:10px;
margin-left:2px;
} }
.ascribe-table-item-selected { .ascribe-table-item-selected {

View File

@ -27,6 +27,7 @@ $BASE_URL: '<%= BASE_URL %>';
@import 'ascribe_settings'; @import 'ascribe_settings';
@import 'ascribe_slides_container'; @import 'ascribe_slides_container';
@import 'ascribe_form'; @import 'ascribe_form';
@import 'ascribe_panel';
@import 'whitelabel/index'; @import 'whitelabel/index';