diff --git a/gulpfile.js b/gulpfile.js index 22252fe8..3a053013 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -53,12 +53,12 @@ var config = { ] }; -var SERVER_URL = process.env.ONION_SERVER_URL || 'http://staging.ascribe.io/'; +var SERVER_URL = process.env.ONION_SERVER_URL || 'https://staging.ascribe.io/'; var constants = { BASE_URL: (function () { var baseUrl = process.env.ONION_BASE_URL || '/'; return baseUrl + (baseUrl.match(/\/$/) ? '' : '/'); })(), SERVER_URL: SERVER_URL, - API_ENDPOINT: SERVER_URL + 'api/' || 'http://staging.ascribe.io/api/', + API_ENDPOINT: SERVER_URL + 'api/' || 'https://staging.ascribe.io/api/', DEBUG: !argv.production, CREDENTIALS: 'ZGltaUBtYWlsaW5hdG9yLmNvbTowMDAwMDAwMDAw' // dimi@mailinator:0000000000 }; diff --git a/js/components/ascribe_accordion_list/accordion_list_item.js b/js/components/ascribe_accordion_list/accordion_list_item.js index 6c07a891..37803ad2 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item.js +++ b/js/components/ascribe_accordion_list/accordion_list_item.js @@ -3,151 +3,24 @@ import React from 'react'; import Router from 'react-router'; -import Glyphicon from 'react-bootstrap/lib/Glyphicon'; -import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; -import Tooltip from 'react-bootstrap/lib/Tooltip'; - -import AccordionListItemEditionWidget from './accordion_list_item_edition_widget'; -import CreateEditionsForm from '../ascribe_forms/create_editions_form'; - -import PieceListActions from '../../actions/piece_list_actions'; -import PieceListStore from '../../stores/piece_list_store'; - -import WhitelabelStore from '../../stores/whitelabel_store'; - -import EditionListActions from '../../actions/edition_list_actions'; - -import GlobalNotificationModel from '../../models/global_notification_model'; -import GlobalNotificationActions from '../../actions/global_notification_actions'; - -import AclProxy from '../acl_proxy'; -import SubmitToPrizeButton from '../whitelabel/prize/components/ascribe_buttons/submit_to_prize_button'; - -import { getLangText } from '../../utils/lang_utils'; -import { mergeOptions } from '../../utils/general_utils'; - -let Link = Router.Link; - let AccordionListItem = React.createClass({ propTypes: { + badge: React.PropTypes.object, className: React.PropTypes.string, - content: React.PropTypes.object, - children: React.PropTypes.object + thumbnail: React.PropTypes.object, + heading: React.PropTypes.object, + subheading: React.PropTypes.object, + subsubheading: React.PropTypes.object, + buttons: React.PropTypes.object, + children: React.PropTypes.oneOfType([ + React.PropTypes.arrayOf(React.PropTypes.element), + React.PropTypes.element + ]) }, mixins: [Router.Navigation], - getInitialState() { - return mergeOptions( - { - showCreateEditionsDialog: false - }, - PieceListStore.getState(), - WhitelabelStore.getState() - ); - }, - - componentDidMount() { - PieceListStore.listen(this.onChange); - WhitelabelStore.listen(this.onChange); - }, - - componentWillUnmount() { - PieceListStore.unlisten(this.onChange); - WhitelabelStore.unlisten(this.onChange); - }, - - onChange(state) { - this.setState(state); - }, - - getGlyphicon(){ - if (this.props.content.requestAction) { - return ( - {getLangText('You have actions pending in one of your editions')}}> - - ); - } - return null; - }, - - toggleCreateEditionsDialog() { - this.setState({ - showCreateEditionsDialog: !this.state.showCreateEditionsDialog - }); - }, - - handleEditionCreationSuccess() { - PieceListActions.updatePropertyForPiece({pieceId: this.props.content.id, key: 'num_editions', value: 0}); - - this.toggleCreateEditionsDialog(); - }, - - handleSubmitPrizeSuccess(response) { - 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); - GlobalNotificationActions.appendGlobalNotification(notification); - }, - - onPollingSuccess(pieceId) { - PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, - this.state.orderBy, this.state.orderAsc, this.state.filterBy); - EditionListActions.toggleEditionList(pieceId); - - let notification = new GlobalNotificationModel('Editions successfully created', 'success', 10000); - GlobalNotificationActions.appendGlobalNotification(notification); - }, - - getCreateEditionsDialog() { - if (this.props.content.num_editions < 1 && this.state.showCreateEditionsDialog) { - return ( -
- -
- ); - } - }, - - getLicences() { - // convert this to acl_view_licences later - if (this.state.whitelabel && this.state.whitelabel.name === 'Creative Commons France') { - return ( - - , - - {getLangText('%s license', this.props.content.license_type.code)} - - - ); - } - }, - render() { - let linkData; - - if (this.props.content.num_editions < 1 || !this.props.content.first_edition) { - linkData = { - to: 'piece', - params: { - pieceId: this.props.content.id - } - }; - } else { - linkData = { - to: 'edition', - params: { - editionId: this.props.content.first_edition.bitcoin_id - } - }; - } return (
@@ -155,52 +28,22 @@ let AccordionListItem = React.createClass({
- - - + {this.props.thumbnail}
- -

{this.props.content.title}

- - -

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

- -
- {this.props.content.date_created.split('-')[0]} - - - - - - - - {this.getLicences()} -
+ {this.props.heading} + {this.props.subheading} + {this.props.subsubheading} + {this.props.buttons}
- {this.getGlyphicon()} + {this.props.badge}
- - {this.getCreateEditionsDialog()} - - {/* this.props.children is AccordionListItemTableEditions */} {this.props.children} ); diff --git a/js/components/ascribe_accordion_list/accordion_list_item_piece.js b/js/components/ascribe_accordion_list/accordion_list_item_piece.js new file mode 100644 index 00000000..e1271b0a --- /dev/null +++ b/js/components/ascribe_accordion_list/accordion_list_item_piece.js @@ -0,0 +1,77 @@ +'use strict'; + +import React from 'react'; +import Router from 'react-router'; + +import AccordionListItem from './accordion_list_item'; + +import { getLangText } from '../../utils/lang_utils'; + +let Link = Router.Link; + + +let AccordionListItemPiece = React.createClass({ + propTypes: { + className: React.PropTypes.string, + piece: React.PropTypes.object, + children: React.PropTypes.oneOfType([ + React.PropTypes.arrayOf(React.PropTypes.element), + React.PropTypes.element + ]), + subsubheading: React.PropTypes.object, + buttons: React.PropTypes.object, + badge: React.PropTypes.object + }, + + mixins: [Router.Navigation], + + getLinkData(){ + let linkData; + + if (this.props.piece.num_editions < 1 || !this.props.piece.first_edition) { + linkData = { + to: 'piece', + params: { + pieceId: this.props.piece.id + } + }; + } else { + linkData = { + to: 'edition', + params: { + editionId: this.props.piece.first_edition.bitcoin_id + } + }; + } + return linkData; + }, + + render() { + return ( + + + } + heading={ + +

{this.props.piece.title}

+ } + subheading={ +

+ {getLangText('by ')} + {this.props.artistName ? this.props.artistName : this.props.piece.artist_name} +

+ } + subsubheading={this.props.subsubheading} + buttons={this.props.buttons} + badge={this.props.badge} + > + {this.props.children} +
+ ); + } +}); + +export default AccordionListItemPiece; diff --git a/js/components/ascribe_accordion_list/accordion_list_item_wallet.js b/js/components/ascribe_accordion_list/accordion_list_item_wallet.js new file mode 100644 index 00000000..3cb557ef --- /dev/null +++ b/js/components/ascribe_accordion_list/accordion_list_item_wallet.js @@ -0,0 +1,156 @@ +'use strict'; + +import React from 'react'; + +import Glyphicon from 'react-bootstrap/lib/Glyphicon'; +import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; +import Tooltip from 'react-bootstrap/lib/Tooltip'; + +import AccordionListItemPiece from './accordion_list_item_piece'; +import AccordionListItemEditionWidget from './accordion_list_item_edition_widget'; +import CreateEditionsForm from '../ascribe_forms/create_editions_form'; + +import PieceListActions from '../../actions/piece_list_actions'; +import PieceListStore from '../../stores/piece_list_store'; + +import WhitelabelStore from '../../stores/whitelabel_store'; + +import EditionListActions from '../../actions/edition_list_actions'; + +import GlobalNotificationModel from '../../models/global_notification_model'; +import GlobalNotificationActions from '../../actions/global_notification_actions'; + +import AclProxy from '../acl_proxy'; + +import { getLangText } from '../../utils/lang_utils'; +import { mergeOptions } from '../../utils/general_utils'; + + +let AccordionListItemWallet = React.createClass({ + propTypes: { + className: React.PropTypes.string, + content: React.PropTypes.object, + children: React.PropTypes.oneOfType([ + React.PropTypes.arrayOf(React.PropTypes.element), + React.PropTypes.element + ]) + }, + + getInitialState() { + return mergeOptions( + { + showCreateEditionsDialog: false + }, + PieceListStore.getState(), + WhitelabelStore.getState() + ); + }, + + componentDidMount() { + PieceListStore.listen(this.onChange); + WhitelabelStore.listen(this.onChange); + }, + + componentWillUnmount() { + PieceListStore.unlisten(this.onChange); + WhitelabelStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + getGlyphicon(){ + if (this.props.content.requestAction) { + return ( + {getLangText('You have actions pending in one of your editions')}}> + + ); + } + return null; + }, + + toggleCreateEditionsDialog() { + this.setState({ + showCreateEditionsDialog: !this.state.showCreateEditionsDialog + }); + }, + + handleEditionCreationSuccess() { + PieceListActions.updatePropertyForPiece({pieceId: this.props.content.id, key: 'num_editions', value: 0}); + + this.toggleCreateEditionsDialog(); + }, + + onPollingSuccess(pieceId) { + PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, + this.state.orderBy, this.state.orderAsc, this.state.filterBy); + EditionListActions.toggleEditionList(pieceId); + + let notification = new GlobalNotificationModel('Editions successfully created', 'success', 10000); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + + getCreateEditionsDialog() { + if (this.props.content.num_editions < 1 && this.state.showCreateEditionsDialog) { + return ( +
+ +
+ ); + } + }, + + getLicences() { + // convert this to acl_view_licences later + if (this.state.whitelabel && this.state.whitelabel.name === 'Creative Commons France') { + return ( + + , + + {getLangText('%s license', this.props.content.license_type.code)} + + + ); + } + }, + + render() { + + return ( + + {this.props.content.date_created.split('-')[0]} + {this.getLicences()} + } + buttons={ +
+ + + +
} + badge={this.getGlyphicon()}> + {this.getCreateEditionsDialog()} + {/* this.props.children is AccordionListItemTableEditions */} + {this.props.children} +
+ ); + } +}); + +export default AccordionListItemWallet; diff --git a/js/components/ascribe_collapsible/collapsible_paragraph.js b/js/components/ascribe_collapsible/collapsible_paragraph.js index e04e82c2..8b3b3cf4 100644 --- a/js/components/ascribe_collapsible/collapsible_paragraph.js +++ b/js/components/ascribe_collapsible/collapsible_paragraph.js @@ -23,7 +23,7 @@ const CollapsibleParagraph = React.createClass({ getInitialState() { return { - expanded: false + expanded: this.props.defaultExpanded }; }, diff --git a/js/components/ascribe_detail/detail_property.js b/js/components/ascribe_detail/detail_property.js index f220fc98..828ed81a 100644 --- a/js/components/ascribe_detail/detail_property.js +++ b/js/components/ascribe_detail/detail_property.js @@ -17,9 +17,9 @@ let DetailProperty = React.createClass({ getDefaultProps() { return { - separator: ':', - labelClassName: 'col-xs-3 col-sm-3 col-md-2 col-lg-2', - valueClassName: 'col-xs-9 col-sm-9 col-md-10 col-lg-10' + separator: '', + labelClassName: 'col-xs-3 col-sm-3 col-md-2 col-lg-2 col-xs-height col-bottom ascribe-detail-property-label', + valueClassName: 'col-xs-9 col-sm-9 col-md-10 col-lg-10 col-xs-height col-bottom ascribe-detail-property-value' }; }, @@ -52,11 +52,11 @@ let DetailProperty = React.createClass({ return (
-
- { this.props.label + this.props.separator} +
+ { this.props.label } { this.props.separator}
{value}
diff --git a/js/components/ascribe_detail/media_container.js b/js/components/ascribe_detail/media_container.js index 529817c3..6ac2f745 100644 --- a/js/components/ascribe_detail/media_container.js +++ b/js/components/ascribe_detail/media_container.js @@ -19,7 +19,33 @@ const EMBED_IFRAME_HEIGHT = { let MediaContainer = React.createClass({ propTypes: { - content: React.PropTypes.object + content: React.PropTypes.object, + refreshObject: React.PropTypes.func + }, + + getInitialState() { + return {timerId: null}; + }, + + componentDidMount() { + if (!this.props.content.digital_work) { + return; + } + let isEncoding = this.props.content.digital_work.isEncoding; + if (this.props.content.digital_work.mime === 'video' && typeof isEncoding === 'number' && isEncoding !== 100 && !this.state.timerId) { + let timerId = window.setInterval(this.props.refreshObject, 10000); + this.setState({timerId: timerId}); + } + }, + + componentWillUpdate() { + if (this.props.content.digital_work.isEncoding === 100) { + window.clearInterval(this.state.timerId); + } + }, + + componentWillUnmount() { + window.clearInterval(this.state.timerId); }, render() { diff --git a/js/components/ascribe_detail/piece.js b/js/components/ascribe_detail/piece.js index 3fabb055..ed312f5f 100644 --- a/js/components/ascribe_detail/piece.js +++ b/js/components/ascribe_detail/piece.js @@ -1,38 +1,14 @@ 'use strict'; import React from 'react'; -import Router from 'react-router'; import Row from 'react-bootstrap/lib/Row'; import Col from 'react-bootstrap/lib/Col'; -import DetailProperty from './detail_property'; - -import UserActions from '../../actions/user_actions'; -import UserStore from '../../stores/user_store'; - -import PieceListActions from '../../actions/piece_list_actions'; -import PieceListStore from '../../stores/piece_list_store'; - -import EditionListActions from '../../actions/edition_list_actions'; - import PieceActions from '../../actions/piece_actions'; import MediaContainer from './media_container'; -import EditionDetailProperty from './detail_property'; - -import AclButtonList from './../ascribe_buttons/acl_button_list'; -import CreateEditionsForm from '../ascribe_forms/create_editions_form'; -import CreateEditionsButton from '../ascribe_buttons/create_editions_button'; -import DeleteButton from '../ascribe_buttons/delete_button'; - -import GlobalNotificationModel from '../../models/global_notification_model'; -import GlobalNotificationActions from '../../actions/global_notification_actions'; - -import { getLangText } from '../../utils/lang_utils'; -import { mergeOptions } from '../../utils/general_utils'; - /** * This is the component that implements display-specific functionality @@ -40,97 +16,16 @@ import { mergeOptions } from '../../utils/general_utils'; let Piece = React.createClass({ propTypes: { piece: React.PropTypes.object, + header: React.PropTypes.object, + subheader: React.PropTypes.object, + buttons: React.PropTypes.object, loadPiece: React.PropTypes.func, children: React.PropTypes.object }, - mixins: [Router.Navigation], - getInitialState() { - return mergeOptions( - UserStore.getState(), - PieceListStore.getState(), - { - showCreateEditionsDialog: false - } - ); - }, - - componentDidMount() { - UserStore.listen(this.onChange); - PieceListStore.listen(this.onChange); - UserActions.fetchCurrentUser(); - }, - - componentWillUnmount() { - UserStore.unlisten(this.onChange); - PieceListStore.unlisten(this.onChange); - }, - - onChange(state) { - this.setState(state); - }, - - toggleCreateEditionsDialog() { - this.setState({ - showCreateEditionsDialog: !this.state.showCreateEditionsDialog - }); - }, - - handleEditionCreationSuccess() { - PieceActions.updateProperty({key: 'num_editions', value: 0}); - PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, - this.state.orderBy, this.state.orderAsc, this.state.filterBy); - this.toggleCreateEditionsDialog(); - }, - - handleDeleteSuccess(response) { - 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 - // all editions dialogs and not reload them - EditionListActions.closeAllEditionLists(); - EditionListActions.clearAllEditionSelections(); - - let notification = new GlobalNotificationModel(response.notification, 'success'); - GlobalNotificationActions.appendGlobalNotification(notification); - - this.transitionTo('pieces'); - }, - - getCreateEditionsDialog() { - if(this.props.piece.num_editions < 1 && this.state.showCreateEditionsDialog) { - return ( -
- -
-
- ); - } else { - return (
); - } - }, - - handlePollingSuccess(pieceId, numEditions) { - - // we need to refresh the num_editions property of the actual piece we're looking at - PieceActions.updateProperty({ - key: 'num_editions', - 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); - GlobalNotificationActions.appendGlobalNotification(notification); + updateObject() { + return PieceActions.fetchOne(this.props.piece.id); }, render() { @@ -138,38 +33,14 @@ let Piece = React.createClass({ -
-

{this.props.piece.title}

-
- - - {this.props.piece.num_editions > 0 ? : null} -
-
-
- -
+ {this.props.header} + {this.props.subheader} + {this.props.buttons} - - - - - - {this.getCreateEditionsDialog()} {this.props.children} diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index 8e3a5750..2b607516 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -1,37 +1,59 @@ 'use strict'; import React from 'react'; +import Router from 'react-router'; import PieceActions from '../../actions/piece_actions'; import PieceStore from '../../stores/piece_store'; +import PieceListActions from '../../actions/piece_list_actions'; +import PieceListStore from '../../stores/piece_list_store'; + +import UserActions from '../../actions/user_actions'; +import UserStore from '../../stores/user_store'; + +import EditionListActions from '../../actions/edition_list_actions'; + import Piece from './piece'; import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph'; import FurtherDetails from './further_details'; +import DetailProperty from './detail_property'; + +import AclButtonList from './../ascribe_buttons/acl_button_list'; +import CreateEditionsForm from '../ascribe_forms/create_editions_form'; +import CreateEditionsButton from '../ascribe_buttons/create_editions_button'; +import DeleteButton from '../ascribe_buttons/delete_button'; + +import GlobalNotificationModel from '../../models/global_notification_model'; +import GlobalNotificationActions from '../../actions/global_notification_actions'; + import AppConstants from '../../constants/application_constants'; +import { mergeOptions } from '../../utils/general_utils'; +import { getLangText } from '../../utils/lang_utils'; /** * This is the component that implements resource/data specific functionality */ let PieceContainer = React.createClass({ - getInitialState() { - return PieceStore.getState(); - }, - onChange(state) { - this.setState(state); - if (!state.piece.digital_work) { - return; - } - let isEncoding = state.piece.digital_work.isEncoding; - if (state.piece.digital_work.mime === 'video' && typeof isEncoding === 'number' && isEncoding !== 100 && !this.state.timerId) { - let timerId = window.setInterval(() => PieceActions.fetchOne(this.props.params.pieceId), 10000); - this.setState({timerId: timerId}); - } + mixins: [Router.Navigation], + + getInitialState() { + return mergeOptions( + UserStore.getState(), + PieceListStore.getState(), + PieceStore.getState(), + { + showCreateEditionsDialog: false + } + ); }, componentDidMount() { + UserStore.listen(this.onChange); + PieceListStore.listen(this.onChange); + UserActions.fetchCurrentUser(); PieceStore.listen(this.onChange); PieceActions.fetchOne(this.props.params.pieceId); }, @@ -42,21 +64,121 @@ let PieceContainer = React.createClass({ // as it will otherwise display wrong/old data once the user loads // the piece detail a second time PieceActions.updatePiece({}); - window.clearInterval(this.state.timerId); PieceStore.unlisten(this.onChange); + UserStore.unlisten(this.onChange); + PieceListStore.unlisten(this.onChange); }, + onChange(state) { + this.setState(state); + }, loadPiece() { PieceActions.fetchOne(this.props.params.pieceId); }, + + toggleCreateEditionsDialog() { + this.setState({ + showCreateEditionsDialog: !this.state.showCreateEditionsDialog + }); + }, + + handleEditionCreationSuccess() { + PieceActions.updateProperty({key: 'num_editions', value: 0}); + PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, + this.state.orderBy, this.state.orderAsc, this.state.filterBy); + this.toggleCreateEditionsDialog(); + }, + + handleDeleteSuccess(response) { + 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 + // all editions dialogs and not reload them + EditionListActions.closeAllEditionLists(); + EditionListActions.clearAllEditionSelections(); + + let notification = new GlobalNotificationModel(response.notification, 'success'); + GlobalNotificationActions.appendGlobalNotification(notification); + + this.transitionTo('pieces'); + }, + + getCreateEditionsDialog() { + if(this.state.piece.num_editions < 1 && this.state.showCreateEditionsDialog) { + return ( +
+ +
+
+ ); + } else { + return (
); + } + }, + + handlePollingSuccess(pieceId, numEditions) { + + // we need to refresh the num_editions property of the actual piece we're looking at + PieceActions.updateProperty({ + key: 'num_editions', + 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); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + render() { if('title' in this.state.piece) { return ( + loadPiece={this.loadPiece} + header={ +
+
+

{this.state.piece.title}

+ + + {this.state.piece.num_editions > 0 ? : null} +
+
+ } + subheader={ +
+ +
+ } + buttons={ + + + + + }> + {this.getCreateEditionsDialog()} + ); + } + return null; + }, + getOrderWidget(){ + if (this.props.orderParams){ + return ( + + ); + } + return null; + }, + render() { let searchIcon = ; @@ -37,7 +65,7 @@ let PieceListToolbar = React.createClass({ {this.props.children} - + - + {this.getOrderWidget()} + {this.getFilterWidget()}
diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js new file mode 100644 index 00000000..38b92b6d --- /dev/null +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js @@ -0,0 +1,86 @@ +'use strict'; + +import React from 'react'; + +import DropdownButton from 'react-bootstrap/lib/DropdownButton'; +import MenuItem from 'react-bootstrap/lib/MenuItem'; + +import { getLangText } from '../../utils/lang_utils.js'; + +let PieceListToolbarOrderWidget = React.createClass({ + propTypes: { + // An array of either strings (which represent acl enums) or objects of the form + // + // { + // key: , + // label: + // } + orderParams: React.PropTypes.arrayOf(React.PropTypes.any).isRequired, + orderBy: React.PropTypes.string, + applyOrderBy: React.PropTypes.func + }, + + generateOrderByStatement(param) { + let orderBy = this.props.orderBy; + return orderBy; + }, + + /** + * We need overloading here to find the correct parameter of the label + * the user is clicking on. + */ + orderBy(orderBy) { + return () => { + this.props.applyOrderBy(orderBy); + }; + }, + + isOrderActive() { + // We're hiding the star in that complicated matter so that, + // the surrounding button is not resized up on appearance + if(this.props.orderBy.length > 0) { + return { visibility: 'visible'}; + } else { + return { visibility: 'hidden' }; + } + }, + + render() { + let filterIcon = ( + + + * + + ); + return ( + + +
  • + {getLangText('Sort by')}: +
  • + {this.props.orderParams.map((param) => { + return ( + +
    + + {getLangText(param.replace('_', ' '))} + + -1} /> +
    +
    + ); + })} +
    + ); + } +}); + +export default PieceListToolbarOrderWidget; \ No newline at end of file diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 78917f61..eac6ca15 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -10,7 +10,7 @@ import EditionListStore from '../stores/edition_list_store'; import EditionListActions from '../actions/edition_list_actions'; import AccordionList from './ascribe_accordion_list/accordion_list'; -import AccordionListItem from './ascribe_accordion_list/accordion_list_item'; +import AccordionListItemWallet from './ascribe_accordion_list/accordion_list_item_wallet'; import AccordionListItemTableEditions from './ascribe_accordion_list/accordion_list_item_table_editions'; import Pagination from './ascribe_pagination/pagination'; @@ -24,12 +24,29 @@ import { mergeOptions } from '../utils/general_utils'; let PieceList = React.createClass({ propTypes: { + accordionListItemType: React.PropTypes.func, redirectTo: React.PropTypes.string, - customSubmitButton: React.PropTypes.element + customSubmitButton: React.PropTypes.element, + filterParams: React.PropTypes.array, + orderParams: React.PropTypes.array + }, mixins: [Router.Navigation, Router.State], + getDefaultProps() { + return { + accordionListItemType: AccordionListItemWallet, + orderParams: ['artist_name', 'title'], + filterParams: [ + 'acl_transfer', + 'acl_consign', + { + key: 'acl_create_editions', + label: 'create editions' + }] + }; + }, getInitialState() { return mergeOptions( PieceListStore.getState(), @@ -97,7 +114,7 @@ let PieceList = React.createClass({ this.transitionTo(this.getPathname(), {page: 1}); }, - applyFilterBy(filterBy) { + 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) @@ -121,20 +138,25 @@ let PieceList = React.createClass({ this.transitionTo(this.getPathname(), {page: 1}); }, - accordionChangeOrder(orderBy, orderAsc) { + applyOrderBy(orderBy, orderAsc) { PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, - orderBy, orderAsc, this.state.filterBy); + orderBy, this.state.orderAsc, this.state.filterBy); }, render() { let loadingElement = (); + let AccordionListItemType = this.props.accordionListItemType; return (
    + orderBy={this.state.orderBy} + applyFilterBy={this.applyFilterBy} + applyOrderBy={this.applyOrderBy}> {this.props.customSubmitButton} @@ -151,14 +173,14 @@ let PieceList = React.createClass({ loadingElement={loadingElement}> {this.state.pieceList.map((piece, i) => { return ( - - + ); })} diff --git a/js/components/whitelabel/prize/actions/prize_rating_actions.js b/js/components/whitelabel/prize/actions/prize_rating_actions.js index 536445f8..e36023a0 100644 --- a/js/components/whitelabel/prize/actions/prize_rating_actions.js +++ b/js/components/whitelabel/prize/actions/prize_rating_actions.js @@ -37,7 +37,6 @@ class PrizeRatingActions { resolve(res); }) .catch((err) => { - console.logGlobal(err); reject(err); }); }); @@ -52,7 +51,6 @@ class PrizeRatingActions { resolve(res); }) .catch((err) => { - console.logGlobal(err); reject(err); }); }); diff --git a/js/components/whitelabel/prize/components/ascribe_accordion_list/accordion_list_item_prize.js b/js/components/whitelabel/prize/components/ascribe_accordion_list/accordion_list_item_prize.js new file mode 100644 index 00000000..73c82ca4 --- /dev/null +++ b/js/components/whitelabel/prize/components/ascribe_accordion_list/accordion_list_item_prize.js @@ -0,0 +1,131 @@ +'use strict'; + +import React from 'react'; +import Router from 'react-router'; +import StarRating from 'react-star-rating'; + +import AccordionListItemPiece from '../../../../ascribe_accordion_list/accordion_list_item_piece'; + +import PieceListActions from '../../../../../actions/piece_list_actions'; +import PieceListStore from '../../../../../stores/piece_list_store'; + +import UserStore from '../../../../../stores/user_store'; + +import GlobalNotificationModel from '../../../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../../../actions/global_notification_actions'; + +import AclProxy from '../../../../acl_proxy'; +import SubmitToPrizeButton from './../ascribe_buttons/submit_to_prize_button'; + + +import { getLangText } from '../../../../../utils/lang_utils'; +import { mergeOptions } from '../../../../../utils/general_utils'; + +let Link = Router.Link; + + +let AccordionListItemPrize = React.createClass({ + propTypes: { + className: React.PropTypes.string, + content: React.PropTypes.object, + children: React.PropTypes.oneOfType([ + React.PropTypes.arrayOf(React.PropTypes.element), + React.PropTypes.element + ]) + }, + + getInitialState() { + return mergeOptions( + PieceListStore.getState(), + UserStore.getState() + ); + }, + + componentDidMount() { + PieceListStore.listen(this.onChange); + UserStore.listen(this.onChange); + }, + + componentWillUnmount() { + PieceListStore.unlisten(this.onChange); + UserStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + handleSubmitPrizeSuccess(response) { + 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); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + + getPrizeButtons() { + if (this.state.currentUser && this.state.currentUser.is_jury){ + if (this.props.content.ratings && this.props.content.ratings.rating){ + // jury and rating available + let rating = parseInt(this.props.content.ratings.rating, 10); + return ( +
    + + + +
    ); + } + else { + // jury and no rating yet + return ( +
    + + Submit your rating + +
    + ); + } + } + // participant + return ( +
    + + + +
    + ); + }, + + render() { + let artistName = this.state.currentUser.is_jury ? +
    } + buttons={this.getPrizeButtons()}> + {this.props.children} + + ); + } +}); + +export default AccordionListItemPrize; diff --git a/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js b/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js index b6363868..b41703e7 100644 --- a/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js +++ b/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js @@ -1,15 +1,21 @@ 'use strict'; import React from 'react'; +import Router from 'react-router'; import StarRating from 'react-star-rating'; import PieceActions from '../../../../../actions/piece_actions'; import PieceStore from '../../../../../stores/piece_store'; +import PieceListStore from '../../../../../stores/piece_list_store'; +import PieceListActions from '../../../../../actions/piece_list_actions'; + import PrizeRatingActions from '../../actions/prize_rating_actions'; import PrizeRatingStore from '../../stores/prize_rating_store'; +import UserStore from '../../../../../stores/user_store'; + import Piece from '../../../../../components/ascribe_detail/piece'; import AppConstants from '../../../../../constants/application_constants'; @@ -19,21 +25,32 @@ import Property from '../../../../../components/ascribe_forms/property'; import InputTextAreaToggable from '../../../../../components/ascribe_forms/input_textarea_toggable'; import CollapsibleParagraph from '../../../../../components/ascribe_collapsible/collapsible_paragraph'; +import GlobalNotificationModel from '../../../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../../../actions/global_notification_actions'; + +import DetailProperty from '../../../../ascribe_detail/detail_property'; + +import ApiUrls from '../../../../../constants/api_urls'; +import { mergeOptions } from '../../../../../utils/general_utils'; +import { getLangText } from '../../../../../utils/lang_utils'; + +let Link = Router.Link; + /** * This is the component that implements resource/data specific functionality */ let PieceContainer = React.createClass({ getInitialState() { - return PieceStore.getState(); - }, - - onChange(state) { - this.setState(state); + return mergeOptions( + PieceStore.getState(), + UserStore.getState() + ); }, componentDidMount() { PieceStore.listen(this.onChange); PieceActions.fetchOne(this.props.params.pieceId); + UserStore.listen(this.onChange); }, componentWillUnmount() { @@ -42,10 +59,20 @@ let PieceContainer = React.createClass({ // as it will otherwise display wrong/old data once the user loads // the piece detail a second time PieceActions.updatePiece({}); - PieceStore.unlisten(this.onChange); + UserStore.unlisten(this.onChange); }, + componentWillReceiveProps(nextProps) { + if(this.props.params.pieceId !== nextProps.params.pieceId) { + PieceActions.updatePiece({}); + PieceActions.fetchOne(nextProps.params.pieceId); + } + }, + + onChange(state) { + this.setState(state); + }, loadPiece() { PieceActions.fetchOne(this.props.params.pieceId); @@ -53,10 +80,29 @@ let PieceContainer = React.createClass({ render() { if('title' in this.state.piece) { + let artistName = this.state.currentUser.is_jury ? +
    + Sign up to ascribe + + +

    + or, already an ascribe user? +

    + + Log in + + + ); + }, + + getTitle() { + if (this.state.prize && this.state.prize.active){ + return ( +

    + This is the submission page for Sluice_screens ↄc Prize 2015. +

    + ); + } + return ( +

    + Submissions for Sluice_screens ↄc Prize 2015 are now closed. +

    + ); + }, render() { return (

    Sluice_screens ↄc Prize 2015

    -

    - This is the submission page for Sluice_screens ↄc Prize 2015. -

    - - - Sign up to submit - - -

    - or, already an ascribe user? -

    - - Log in to submit - -
    + {this.getTitle()} + {this.getButtons()}
    diff --git a/js/components/whitelabel/prize/components/prize_piece_list.js b/js/components/whitelabel/prize/components/prize_piece_list.js index 73674e25..2c838b6d 100644 --- a/js/components/whitelabel/prize/components/prize_piece_list.js +++ b/js/components/whitelabel/prize/components/prize_piece_list.js @@ -3,20 +3,60 @@ import React from 'react'; import PieceList from '../../../piece_list'; -import ButtonLink from 'react-router-bootstrap/lib/ButtonLink'; +import UserActions from '../../../../actions/user_actions'; +import UserStore from '../../../../stores/user_store'; +import PrizeActions from '../actions/prize_actions'; +import PrizeStore from '../stores/prize_store'; + +import ButtonLink from 'react-router-bootstrap/lib/ButtonLink'; +import AccordionListItemPrize from './ascribe_accordion_list/accordion_list_item_prize'; + +import { mergeOptions } from '../../../../utils/general_utils'; let PrizePieceList = React.createClass({ + getInitialState() { + return mergeOptions( + PrizeStore.getState(), + UserStore.getState() + ); + }, + + componentDidMount() { + UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + PrizeStore.listen(this.onChange); + PrizeActions.fetchPrize(); + }, + + componentWillUnmount() { + UserStore.unlisten(this.onChange); + PrizeStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + getButtonSubmit() { + if (this.state.prize && this.state.prize.active){ + return ( + + Submit to prize + + ); + } + return null; + }, + render() { return (
    - Submit to prize - - }/> + accordionListItemType={AccordionListItemPrize} + orderParams={this.state.currentUser.is_jury ? ['rating', 'title'] : ['artist_name', 'title']} + filterParams={null} + customSubmitButton={this.getButtonSubmit()}/>
    ); } diff --git a/js/components/whitelabel/prize/constants/prize_api_urls.js b/js/components/whitelabel/prize/constants/prize_api_urls.js index 8ef92daa..8c2e72be 100644 --- a/js/components/whitelabel/prize/constants/prize_api_urls.js +++ b/js/components/whitelabel/prize/constants/prize_api_urls.js @@ -16,7 +16,10 @@ function getPrizeApiUrls(subdomain) { '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}/' + 'rating': AppPrizeConstants.prizeApiEndpoint + subdomain + '/ratings/${piece_id}/', + 'notes': AppPrizeConstants.prizeApiEndpoint + subdomain + '/notes/', + 'note': AppPrizeConstants.prizeApiEndpoint + subdomain + '/notes/${piece_id}/' + }; } diff --git a/js/components/whitelabel/prize/fetchers/prize_rating_fetcher.js b/js/components/whitelabel/prize/fetchers/prize_rating_fetcher.js index 91da0051..b7af8845 100644 --- a/js/components/whitelabel/prize/fetchers/prize_rating_fetcher.js +++ b/js/components/whitelabel/prize/fetchers/prize_rating_fetcher.js @@ -13,7 +13,7 @@ let PrizeRatingFetcher = { }, rate(pieceId, rating) { - return requests.post('ratings', {body: {'piece_id': pieceId, 'rating': rating}}); + return requests.post('ratings', {body: {'piece_id': pieceId, 'value': rating}}); } }; diff --git a/sass/ascribe_piece_list_toolbar.scss b/sass/ascribe_piece_list_toolbar.scss index 902d5be9..8756adf4 100644 --- a/sass/ascribe_piece_list_toolbar.scss +++ b/sass/ascribe_piece_list_toolbar.scss @@ -11,9 +11,39 @@ } } -.ascribe-piece-list-toolbar-filter-widget { - margin-right: 1em; +.ascribe-input-glyph > .form-group > .input-group { + margin-left: 6px; + input { + box-shadow: none; + background-color: transparent; + border: 1px solid #02b6a3; + border-right: 0; + } + > .input-group-addon { + background-color: transparent; + > .filter-glyph { + color: #02b6a3; + } + border: 1px solid #02b6a3; + border-left: 0; + } +} +.ascribe-piece-list-toolbar-filter-widget { + button { + background-color: rgba(0,0,0,0); + color: #02b6a3; + border: 1px solid rgba(0,0,0,0); + padding: 6px 4px 6px 8px; + &:hover, &:active { + background-color: #02b6a3 !important; + color: white; + border: 1px solid #02b6a3 !important; + } + .caret { + display: none; + } + } .filter-widget-item { > a { diff --git a/sass/main.scss b/sass/main.scss index c83379b3..de166b0c 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -80,6 +80,25 @@ hr { font-size: 0.8em; } +.navbar-default .navbar-nav > .active { + a { + background-color: transparent!important; + > span {color: #02b6a3;} + color: #02b6a3; + border-bottom: 1px solid #02b6a3; + + &:hover, &:focus{ + > span {color: #02b6a3;} + color: #02b6a3; + background-color: transparent; + border-bottom: 1px solid #02b6a3; + } + } +} +.navbar-default .navbar-nav > li > a { + border: 1px solid rgba(0,0,0,0); +} + .img-brand { padding: 0; height: 45px; @@ -386,5 +405,26 @@ hr { .rating-container .rating-stars { width: 25px; + color: #02b6a3; +} +#list-rating > a > span > span > .rating-container .rating-stars{ color: #000; +} + +.react-rating-caption { + font-size: 1em; +} + +.disable-select { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.link-ascribe { + color: #666; + &:hover { + color: #000; + } } \ No newline at end of file