From d3cc6a500705784dce0e11ce3aea3c7c746f6b3f Mon Sep 17 00:00:00 2001 From: diminator Date: Fri, 28 Aug 2015 15:24:32 +0200 Subject: [PATCH 1/6] request actions first cut --- js/actions/piece_list_actions.js | 2 +- .../accordion_list_item_wallet.js | 5 +- js/components/piece_list.js | 26 +++++++ .../ascribe_detail/prize_piece_container.js | 1 - js/stores/piece_list_store.js | 21 +++++- sass/ascribe_accordion_list.scss | 2 +- sass/ascribe_global_action.scss | 73 +++++++++++++++++++ sass/main.scss | 1 + 8 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 sass/ascribe_global_action.scss diff --git a/js/actions/piece_list_actions.js b/js/actions/piece_list_actions.js index 1ebe7f42..d1ff363c 100644 --- a/js/actions/piece_list_actions.js +++ b/js/actions/piece_list_actions.js @@ -55,7 +55,7 @@ class PieceListActions { PieceListFetcher .fetchRequestActions() .then((res) => { - this.actions.updatePieceListRequestActions(res.piece_ids); + this.actions.updatePieceListRequestActions(res); }) .catch((err) => console.logGlobal(err)); } diff --git a/js/components/ascribe_accordion_list/accordion_list_item_wallet.js b/js/components/ascribe_accordion_list/accordion_list_item_wallet.js index f7bca334..178a7db4 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_wallet.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_wallet.js @@ -61,12 +61,13 @@ let AccordionListItemWallet = React.createClass({ }, getGlyphicon(){ - if (this.props.content.requestAction && this.props.content.requestAction.length > 0) { + if ((this.props.content.request_action && this.props.content.request_action.length > 0) || + (this.props.content.request_action_editions)){ return ( {getLangText('You have actions pending in one of your editions')}}> + overlay={{getLangText('You have actions pending')}}> ); } diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 79f1471c..185b0f05 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -147,8 +147,34 @@ let PieceList = React.createClass({ render() { let loadingElement = (); let AccordionListItemType = this.props.accordionListItemType; + + let pieceActions = null; + if (this.state.requestActions && this.state.requestActions.pieces){ + pieceActions = this.state.requestActions.pieces.map((item) => { + return ( +
+ test +
); + }); + } + let editionActions = null; + if (this.state.requestActions && this.state.requestActions.editions){ + for (let pieceId in this.state.requestActions.editions) { + editionActions = this.state.requestActions.editions[pieceId].map((item) => { + return ( +
+ test +
); + }); + } + } + return (
+
+ {pieceActions} + {editionActions} +
{ - piece.requestAction = requestActions.indexOf(piece.id) > -1; - }); + onUpdatePieceListRequestActions(res) { + this.requestActions.pieces = res.piece_actions; + this.requestActions.editions = res.edition_actions; + for (let pieceId in res.edition_actions){ + try { + this.onUpdatePropertyForPiece({ + pieceId: parseInt(pieceId, 10), + key: 'request_action_editions', + value: res.edition_actions[pieceId] + }); + } + catch(err) { + console.warn('couldnt match request action with piecelist, maybe on other page'); + } + + } } onUpdatePropertyForPiece({pieceId, key, value}) { diff --git a/sass/ascribe_accordion_list.scss b/sass/ascribe_accordion_list.scss index 1ef4bef9..6f809b27 100644 --- a/sass/ascribe_accordion_list.scss +++ b/sass/ascribe_accordion_list.scss @@ -158,7 +158,7 @@ span.ascribe-accordion-list-table-toggle { right: 0px; color: $ascribe-color-green; font-size: 1.2em; - padding: 0.3em; + padding: 0.8em; } .ascribe-accordion-list-item-edition-widget { diff --git a/sass/ascribe_global_action.scss b/sass/ascribe_global_action.scss new file mode 100644 index 00000000..bcc3f50d --- /dev/null +++ b/sass/ascribe_global_action.scss @@ -0,0 +1,73 @@ +.ascribe-global-action-wrapper { + position: fixed; + width: 100%; + height:3.5em; + left:0; + top:0; + z-index: 2000; + display:table; +} + +.ascribe-global-action { + width: 40%; + margin: 1px auto; + text-align: center; + padding: 1em; + color: black; + border: 1px solid #cccccc; + background-color: white; +} + +.ascribe-global-notification-off { + bottom: -3.5em; +} + +.ascribe-global-notification-on { + bottom: 0; +} + +.ascribe-global-notification > div, .ascribe-global-notification-bubble > div { + display:table-cell; + vertical-align: middle; + font-size: 1.25em; + font-family: 'Source Sans Pro'; + text-align: right; + padding-right: 3em; +} + +.ascribe-global-notification-bubble > div { + padding: .75em 1.5em .75em 1.5em; +} + +.ascribe-global-notification-bubble { + position: fixed; + bottom: 3em; + right: -50em; + + display:table; + + height: 3.5em; + + background-color: #212121; + border-radius: 2px; + + color: white; + + transition: 1s right ease; +} + +.ascribe-global-notification-bubble-off { + right: -100em; +} + +.ascribe-global-notification-bubble-on { + right: 3.5em; +} + +.ascribe-global-notification-danger { + background-color: #d9534f; +} + +.ascribe-global-notification-success { + background-color: rgba(2, 182, 163, 1); +} \ No newline at end of file diff --git a/sass/main.scss b/sass/main.scss index a6ab05b3..34bf42b1 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -22,6 +22,7 @@ $BASE_URL: '<%= BASE_URL %>'; @import 'ascribe_media_player'; @import 'ascribe_uploader'; @import 'ascribe_footer'; +@import 'ascribe_global_action'; @import 'ascribe_global_notification'; @import 'ascribe_piece_register'; @import 'offset_right'; From bfaf4886a49d13e803eaf7ac7da85541f58da9eb Mon Sep 17 00:00:00 2001 From: diminator Date: Tue, 1 Sep 2015 14:45:14 +0200 Subject: [PATCH 2/6] notifications unoptimized --- .../ascribe_detail/edition_container.js | 9 ++ js/components/global_action.js | 43 ++++++++ js/components/header.js | 98 ++++++++++++++++++- js/components/piece_list.js | 27 +---- js/stores/piece_list_store.js | 16 +-- sass/ascribe_global_action.scss | 64 ++---------- sass/ascribe_notification_list.scss | 65 ++++++++++++ sass/main.scss | 1 + 8 files changed, 226 insertions(+), 97 deletions(-) create mode 100644 js/components/global_action.js create mode 100644 sass/ascribe_notification_list.scss diff --git a/js/components/ascribe_detail/edition_container.js b/js/components/ascribe_detail/edition_container.js index 15086434..62efa709 100644 --- a/js/components/ascribe_detail/edition_container.js +++ b/js/components/ascribe_detail/edition_container.js @@ -34,6 +34,15 @@ let EditionContainer = React.createClass({ EditionActions.fetchOne(this.props.params.editionId); }, + // This is done to update the container when the user clicks on the prev or next + // button to update the URL parameter (and therefore to switch pieces) + componentWillReceiveProps(nextProps) { + if(this.props.params.editionId !== nextProps.params.editionId) { + EditionActions.updateEdition({}); + EditionActions.fetchOne(nextProps.params.editionId); + } + }, + componentWillUnmount() { // Every time we're leaving the edition detail page, // just reset the edition that is saved in the edition store diff --git a/js/components/global_action.js b/js/components/global_action.js new file mode 100644 index 00000000..80df0c75 --- /dev/null +++ b/js/components/global_action.js @@ -0,0 +1,43 @@ +'use strict'; + +import React from 'react'; + +let GlobalAction = React.createClass({ + propTypes: { + requestActions: React.PropTypes.object + }, + + render() { + let pieceActions = null; + if (this.props.requestActions && this.props.requestActions.pieces){ + pieceActions = this.props.requestActions.pieces.map((item) => { + return ( +
+ {item} +
); + }); + } + let editionActions = null; + if (this.props.requestActions && this.props.requestActions.editions){ + editionActions = Object.keys(this.props.requestActions.editions).map((pieceId) => { + return this.props.requestActions.editions[pieceId].map((item) => { + return ( +
+ {item} +
); + }); + }); + } + + if (pieceActions || editionActions) { + return ( +
+ {pieceActions} + {editionActions} +
); + } + return null; + } +}); + +export default GlobalAction; \ No newline at end of file diff --git a/js/components/header.js b/js/components/header.js index 0863624f..7cd0e455 100644 --- a/js/components/header.js +++ b/js/components/header.js @@ -3,6 +3,8 @@ import React from 'react'; import Router from 'react-router'; +import Glyphicon from 'react-bootstrap/lib/Glyphicon'; + import UserActions from '../actions/user_actions'; import UserStore from '../stores/user_store'; @@ -10,6 +12,8 @@ import WhitelabelActions from '../actions/whitelabel_actions'; import WhitelabelStore from '../stores/whitelabel_store'; import EventActions from '../actions/event_actions'; +import PieceListStore from '../stores/piece_list_store'; + import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; import CollapsibleNav from 'react-bootstrap/lib/CollapsibleNav'; @@ -25,6 +29,8 @@ import NavRoutesLinks from './nav_routes_links'; import { mergeOptions } from '../utils/general_utils'; import { getLangText } from '../utils/lang_utils'; +let Link = Router.Link; + let Header = React.createClass({ propTypes: { @@ -41,7 +47,11 @@ let Header = React.createClass({ }, getInitialState() { - return mergeOptions(WhitelabelStore.getState(), UserStore.getState()); + return mergeOptions( + WhitelabelStore.getState(), + UserStore.getState(), + PieceListStore.getState() + ); }, componentDidMount() { @@ -49,11 +59,13 @@ let Header = React.createClass({ UserStore.listen(this.onChange); WhitelabelActions.fetchWhitelabel(); WhitelabelStore.listen(this.onChange); + PieceListStore.listen(this.onChange); }, componentWillUnmount() { UserStore.unlisten(this.onChange); WhitelabelStore.unlisten(this.onChange); + PieceListStore.unlisten(this.onChange); }, getLogo(){ @@ -90,6 +102,34 @@ let Header = React.createClass({ } }, + getNotifications() { + if (this.state.requestActions && this.state.requestActions.length > 0) { + return ( + + + + ({this.state.requestActions.length}) + + } + className="notification-menu"> + {this.state.requestActions.map((pieceOrEdition, i) => { + return ( + + + ); + }) + } + + ); + } + return null; + }, + render() { let account; let signup; @@ -100,7 +140,7 @@ let Header = React.createClass({ {getLangText('Account Settings')} {getLangText('Log out')} - + ); navRoutesLinks = ; } @@ -122,6 +162,7 @@ let Header = React.createClass({ {this.getPoweredBy()} + {navRoutesLinks} @@ -175,57 +141,4 @@ let Header = React.createClass({ } }); -let NotificationListItem = React.createClass({ - propTypes: { - pieceOrEdition: React.PropTypes.object - }, - - getLinkData() { - - if(this.props.pieceOrEdition && this.props.pieceOrEdition.bitcoin_id) { - return { - to: 'edition', - params: { - editionId: this.props.pieceOrEdition.bitcoin_id - } - }; - } else { - return { - to: 'piece', - params: { - pieceId: this.props.pieceOrEdition.id - } - }; - } - - }, - - render() { - if (this.props.pieceOrEdition) { - return ( - -
-
-
- -
-
-
-

{this.props.pieceOrEdition.title}

-
by {this.props.pieceOrEdition.artist_name}
-
- { - this.props.pieceOrEdition.request_action.map((requestAction) => { - return 'Pending ' + requestAction.action + ' request'; - }) - } -
-
-
- ); - } - return null; - } -}); - export default Header; diff --git a/js/components/header_notification.js b/js/components/header_notification.js new file mode 100644 index 00000000..c838e346 --- /dev/null +++ b/js/components/header_notification.js @@ -0,0 +1,122 @@ +'use strict'; + +import React from 'react'; +import Router from 'react-router'; +import DropdownButton from 'react-bootstrap/lib/DropdownButton'; +import Glyphicon from 'react-bootstrap/lib/Glyphicon'; +import MenuItem from 'react-bootstrap/lib/MenuItem'; + +import Nav from 'react-bootstrap/lib/Nav'; + +import PieceListStore from '../stores/piece_list_store'; + +import { mergeOptions } from '../utils/general_utils'; +import { getLangText } from '../utils/lang_utils'; + +let Link = Router.Link; + + +let HeaderNotifications = React.createClass({ + + getInitialState() { + return mergeOptions( + PieceListStore.getState() + ); + }, + + componentDidMount() { + PieceListStore.listen(this.onChange); + }, + + componentWillUnmount() { + PieceListStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + render() { + if (this.state.requestActions && this.state.requestActions.length > 0) { + return ( + + ); + } + return null; + } +}); + +let NotificationListItem = React.createClass({ + propTypes: { + pieceOrEdition: React.PropTypes.object + }, + + getLinkData() { + + if(this.props.pieceOrEdition && this.props.pieceOrEdition.parent) { + return { + to: 'edition', + params: { + editionId: this.props.pieceOrEdition.bitcoin_id + } + }; + } else { + return { + to: 'piece', + params: { + pieceId: this.props.pieceOrEdition.id + } + }; + } + + }, + + render() { + if (this.props.pieceOrEdition) { + return ( + +
+
+
+ +
+
+
+

{this.props.pieceOrEdition.title}

+
by {this.props.pieceOrEdition.artist_name}
+
+ { + this.props.pieceOrEdition.request_action.map((requestAction) => { + return 'Pending ' + requestAction.action + ' request'; + }) + } +
+
+
+ ); + } + return null; + } +}); + +export default HeaderNotifications; From 71c438ee599fa96b1c74542ab432b753c3d3dd98 Mon Sep 17 00:00:00 2001 From: diminator Date: Tue, 1 Sep 2015 18:38:24 +0200 Subject: [PATCH 4/6] close notifications dropdown after click --- js/components/header.js | 4 +++- js/components/header_notification.js | 24 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/js/components/header.js b/js/components/header.js index 6876885c..dcf3c475 100644 --- a/js/components/header.js +++ b/js/components/header.js @@ -102,7 +102,9 @@ let Header = React.createClass({ let navRoutesLinks; if (this.state.currentUser.username){ account = ( - + {getLangText('Account Settings')} {getLangText('Log out')} diff --git a/js/components/header_notification.js b/js/components/header_notification.js index c838e346..9222c0c4 100644 --- a/js/components/header_notification.js +++ b/js/components/header_notification.js @@ -36,6 +36,27 @@ let HeaderNotifications = React.createClass({ this.setState(state); }, + onSelected(event) { + /* + This is a hack to make the dropdown close after clicking on an item + The function just need to be defined + + from https://github.com/react-bootstrap/react-bootstrap/issues/368: + + @jvillasante - Have you tried to use onSelect with the DropdownButton? + I don't have a working example that is exactly like yours, + but I just noticed that the Dropdown closes when I've attached an event handler to OnSelect: + + + + onSelected: function(e) { + // doesn't need to have functionality (necessarily) ... just wired up + } + Internally, a call to DropdownButton.setDropDownState(false) is made which will hide the dropdown menu. + So, you should be able to call that directly on the DropdownButton instance as well if needed. + */ + }, + render() { if (this.state.requestActions && this.state.requestActions.length > 0) { return ( @@ -48,7 +69,8 @@ let HeaderNotifications = React.createClass({ ({this.state.requestActions.length}) } - className="notification-menu"> + className="notification-menu" + onSelect={this.onSelected}> {this.state.requestActions.map((pieceOrEdition, i) => { return ( From bf5d1de635fea9d55ee6cc5ec49a17374dec9dfc Mon Sep 17 00:00:00 2001 From: diminator Date: Tue, 1 Sep 2015 19:14:48 +0200 Subject: [PATCH 5/6] update notification on request action --- .../ascribe_forms/form_request_action.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/js/components/ascribe_forms/form_request_action.js b/js/components/ascribe_forms/form_request_action.js index 5ca7db6c..2448720a 100644 --- a/js/components/ascribe_forms/form_request_action.js +++ b/js/components/ascribe_forms/form_request_action.js @@ -6,6 +6,8 @@ import AclButton from './../ascribe_buttons/acl_button'; import ActionPanel from '../ascribe_panel/action_panel'; import Form from './form'; +import PieceListActions from '../../actions/piece_list_actions'; + import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationActions from '../../actions/global_notification_actions'; @@ -71,12 +73,18 @@ let RequestActionForm = React.createClass({ let notification = new GlobalNotificationModel(message, 'success'); GlobalNotificationActions.appendGlobalNotification(notification); - if(this.props.handleSuccess) { - this.props.handleSuccess(); - } + this.handleSuccess(); + }; }, + handleSuccess() { + PieceListActions.fetchPieceRequestActions(); + if(this.props.handleSuccess) { + this.props.handleSuccess(); + } + }, + getContent() { let pieceOrEditionStr = this.isPiece() ? getLangText('this work%s', '.') : getLangText('this edition%s', '.'); let message = this.props.requestUser + ' ' + getLangText('requests you') + ' ' + this.props.requestAction + ' ' + pieceOrEditionStr; @@ -99,7 +107,7 @@ let RequestActionForm = React.createClass({ buttonAcceptClassName='inline pull-right btn-sm ascribe-margin-1px' pieceOrEditions={this.props.pieceOrEditions} currentUser={this.props.currentUser} - handleSuccess={this.props.handleSuccess} /> + handleSuccess={this.handleSuccess} /> ); } else if(this.props.requestAction === 'loan_request') { return ( @@ -110,7 +118,7 @@ let RequestActionForm = React.createClass({ buttonAcceptClassName='inline pull-right btn-sm ascribe-margin-1px' pieceOrEditions={this.props.pieceOrEditions} currentUser={this.props.currentUser} - handleSuccess={this.props.handleSuccess} /> + handleSuccess={this.handleSuccess} /> ); } else { return ( From 07ce180ff7a69a4d826e6326336ee897bc56ab5a Mon Sep 17 00:00:00 2001 From: diminator Date: Wed, 2 Sep 2015 13:57:41 +0200 Subject: [PATCH 6/6] contract form POST --- js/components/ascribe_forms/form_create_contract.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/js/components/ascribe_forms/form_create_contract.js b/js/components/ascribe_forms/form_create_contract.js index 1b3fcfd6..416ae0e5 100644 --- a/js/components/ascribe_forms/form_create_contract.js +++ b/js/components/ascribe_forms/form_create_contract.js @@ -18,14 +18,20 @@ let CreateContractForm = React.createClass({ getInitialState() { return { - digitalWorkKey: null, + contractKey: null, isUploadReady: false }; }, + getFormData(){ + return { + blob: this.state.contractKey + }; + }, + submitKey(key) { this.setState({ - digitalWorkKey: key + contractKey: key }); }, @@ -39,6 +45,7 @@ let CreateContractForm = React.createClass({ return (