From 23b7ebd7761bbe0400ff4893d204b4b0f2231619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Mon, 19 Oct 2015 15:29:57 +0200 Subject: [PATCH 001/115] add 404 routine to EditionContainer --- js/actions/edition_actions.js | 4 +- .../ascribe_detail/edition_container.js | 50 ++++++++++++------- js/stores/edition_store.js | 6 +++ js/utils/requests.js | 9 ++++ 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/js/actions/edition_actions.js b/js/actions/edition_actions.js index 4bdf093a..3f659524 100644 --- a/js/actions/edition_actions.js +++ b/js/actions/edition_actions.js @@ -7,7 +7,8 @@ import EditionFetcher from '../fetchers/edition_fetcher'; class EditionActions { constructor() { this.generateActions( - 'updateEdition' + 'updateEdition', + 'editionFailed' ); } @@ -18,6 +19,7 @@ class EditionActions { }) .catch((err) => { console.logGlobal(err); + this.actions.editionFailed(err.json); }); } } diff --git a/js/components/ascribe_detail/edition_container.js b/js/components/ascribe_detail/edition_container.js index fa24bcbf..030a7150 100644 --- a/js/components/ascribe_detail/edition_container.js +++ b/js/components/ascribe_detail/edition_container.js @@ -1,6 +1,7 @@ 'use strict'; import React from 'react'; +import { History } from 'react-router'; import EditionActions from '../../actions/edition_actions'; import EditionStore from '../../stores/edition_store'; @@ -15,26 +16,23 @@ import AppConstants from '../../constants/application_constants'; */ let EditionContainer = React.createClass({ propTypes: { - location: React.PropTypes.object + location: React.PropTypes.object, + params: React.PropTypes.object }, + mixins: [History], + getInitialState() { return EditionStore.getState(); }, - onChange(state) { - this.setState(state); - if (!state.edition.digital_work) { - return; - } - let isEncoding = state.edition.digital_work.isEncoding; - if (state.edition.digital_work.mime === 'video' && typeof isEncoding === 'number' && isEncoding !== 100 && !this.state.timerId) { - let timerId = window.setInterval(() => EditionActions.fetchOne(this.props.params.editionId), 10000); - this.setState({timerId: timerId}); - } - }, - componentDidMount() { + // Every time we're entering the edition detail page, + // just reset the edition that is saved in the edition store + // as it will otherwise display wrong/old data once the user loads + // the edition detail a second time + EditionActions.updateEdition({}); + EditionStore.listen(this.onChange); EditionActions.fetchOne(this.props.params.editionId); }, @@ -48,16 +46,32 @@ let EditionContainer = React.createClass({ } }, + componentDidUpdate() { + const { editionError } = this.state; + + if(editionError && editionError.status === 404) { + // Even though the /404 path doesn't really exist, + // we can still redirect there and the page will show up + this.history.pushState(null, '/404'); + } + }, + componentWillUnmount() { - // Every time we're leaving the edition detail page, - // just reset the edition that is saved in the edition store - // as it will otherwise display wrong/old data once the user loads - // the edition detail a second time - EditionActions.updateEdition({}); window.clearInterval(this.state.timerId); EditionStore.unlisten(this.onChange); }, + onChange(state) { + this.setState(state); + if (!state.edition.digital_work) { + return; + } + let isEncoding = state.edition.digital_work.isEncoding; + if (state.edition.digital_work.mime === 'video' && typeof isEncoding === 'number' && isEncoding !== 100 && !this.state.timerId) { + let timerId = window.setInterval(() => EditionActions.fetchOne(this.props.params.editionId), 10000); + this.setState({timerId: timerId}); + } + }, loadEdition() { EditionActions.fetchOne(this.props.params.editionId); diff --git a/js/stores/edition_store.js b/js/stores/edition_store.js index 14ee4fee..22e78d23 100644 --- a/js/stores/edition_store.js +++ b/js/stores/edition_store.js @@ -7,11 +7,17 @@ import EditionActions from '../actions/edition_actions'; class EditionStore { constructor() { this.edition = {}; + this.editionError = null; this.bindActions(EditionActions); } onUpdateEdition(edition) { this.edition = edition; + this.editionError = null; + } + + onEditionFailed(error) { + this.editionError = error; } } diff --git a/js/utils/requests.js b/js/utils/requests.js index 7e9c9a58..eeaa3513 100644 --- a/js/utils/requests.js +++ b/js/utils/requests.js @@ -40,6 +40,15 @@ class Requests { reject(error); } else if(body && body.detail) { reject(new Error(body.detail)); + } else if(!body.success) { + let error = new Error('Client Request Error'); + error.json = { + status: response.status, + statusText: response.status, + type: response.status, + url: response.url + }; + reject(error); } else { resolve(body); } From 757ee40e4eb874a300f9e442cb50b47f1bf6b515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Mon, 19 Oct 2015 15:58:05 +0200 Subject: [PATCH 002/115] add 404 routine to PieceContainer --- js/actions/piece_actions.js | 4 ++- .../ascribe_detail/piece_container.js | 33 +++++++++++++------ js/stores/piece_store.js | 6 ++++ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/js/actions/piece_actions.js b/js/actions/piece_actions.js index 7aed13fc..64dcb2e2 100644 --- a/js/actions/piece_actions.js +++ b/js/actions/piece_actions.js @@ -8,7 +8,8 @@ class PieceActions { constructor() { this.generateActions( 'updatePiece', - 'updateProperty' + 'updateProperty', + 'pieceFailed' ); } @@ -18,6 +19,7 @@ class PieceActions { this.actions.updatePiece(res.piece); }) .catch((err) => { + this.actions.pieceFailed(err.json); console.logGlobal(err); }); } diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index e2ebaa43..175008b4 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -44,7 +44,8 @@ import { getLangText } from '../../utils/lang_utils'; */ let PieceContainer = React.createClass({ propTypes: { - location: React.PropTypes.object + location: React.PropTypes.object, + params: React.PropTypes.object }, mixins: [History], @@ -61,19 +62,31 @@ let PieceContainer = React.createClass({ }, componentDidMount() { - UserStore.listen(this.onChange); - PieceListStore.listen(this.onChange); - UserActions.fetchCurrentUser(); - PieceStore.listen(this.onChange); - PieceActions.fetchOne(this.props.params.pieceId); - }, - - componentWillUnmount() { - // Every time we're leaving the piece detail page, + // Every time we're entering the piece detail page, // just reset the piece that is saved in the piece store // as it will otherwise display wrong/old data once the user loads // the piece detail a second time PieceActions.updatePiece({}); + + UserStore.listen(this.onChange); + PieceListStore.listen(this.onChange); + PieceStore.listen(this.onChange); + + UserActions.fetchCurrentUser(); + PieceActions.fetchOne(this.props.params.pieceId); + }, + + componentDidUpdate() { + const { pieceError } = this.state; + + if(pieceError && pieceError.status === 404) { + // Even though this path doesn't exist we can redirect + // to it as it catches all unknown paths + this.history.pushState(null, '/404'); + } + }, + + componentWillUnmount() { PieceStore.unlisten(this.onChange); UserStore.unlisten(this.onChange); PieceListStore.unlisten(this.onChange); diff --git a/js/stores/piece_store.js b/js/stores/piece_store.js index ccef50b1..3b04736b 100644 --- a/js/stores/piece_store.js +++ b/js/stores/piece_store.js @@ -7,11 +7,13 @@ import PieceActions from '../actions/piece_actions'; class PieceStore { constructor() { this.piece = {}; + this.pieceError = null; this.bindActions(PieceActions); } onUpdatePiece(piece) { this.piece = piece; + this.pieceError = null; } onUpdateProperty({key, value}) { @@ -21,6 +23,10 @@ class PieceStore { throw new Error('There is no piece defined in PieceStore or the piece object does not have the property you\'re looking for.'); } } + + onPieceFailed(err) { + this.pieceError = err; + } } export default alt.createStore(PieceStore, 'PieceStore'); From f82278070b2d19c9f154d0a701116cd35282941f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Mon, 19 Oct 2015 16:01:05 +0200 Subject: [PATCH 003/115] Log before dispatch in PieceActions --- js/actions/piece_actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/actions/piece_actions.js b/js/actions/piece_actions.js index 64dcb2e2..9002e8c5 100644 --- a/js/actions/piece_actions.js +++ b/js/actions/piece_actions.js @@ -19,8 +19,8 @@ class PieceActions { this.actions.updatePiece(res.piece); }) .catch((err) => { - this.actions.pieceFailed(err.json); console.logGlobal(err); + this.actions.pieceFailed(err.json); }); } } From 74e07d5ce597567dfd341eb73db6cb4b383af062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Tue, 20 Oct 2015 11:33:04 +0200 Subject: [PATCH 004/115] Fix stupid mistake --- js/utils/requests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/utils/requests.js b/js/utils/requests.js index eeaa3513..04bde25c 100644 --- a/js/utils/requests.js +++ b/js/utils/requests.js @@ -44,8 +44,8 @@ class Requests { let error = new Error('Client Request Error'); error.json = { status: response.status, - statusText: response.status, - type: response.status, + statusText: response.statusText, + type: response.type, url: response.url }; reject(error); From 7116133337a987ccb2faf696052b97a4aac11514 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 21 Oct 2015 14:10:21 +0200 Subject: [PATCH 005/115] AD-1194 Create front-end host configs Default route components to be replaced with Lumenus specific ones. --- .../wallet/constants/wallet_api_urls.js | 9 ++++- .../whitelabel/wallet/wallet_routes.js | 35 +++++++++++++++++++ js/constants/application_constants.js | 7 ++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index 2cdc0054..8f4543c7 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -19,7 +19,14 @@ function getWalletApiUrls(subdomain) { 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' }; } + else if (subdomain === 'lumenus'){ + return { + 'editions': walletConstants.walletApiEndpoint + subdomain + 'editions/', + 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', + 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' + }; + } return {}; } -export default getWalletApiUrls; \ No newline at end of file +export default getWalletApiUrls; diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 8e4d5197..bcd0a60b 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -15,6 +15,7 @@ import EditionContainer from '../../../components/ascribe_detail/edition_contain import SettingsContainer from '../../../components/ascribe_settings/settings_container'; import ContractSettings from '../../../components/ascribe_settings/contract_settings'; import ErrorNotFoundPage from '../../../components/error_not_found_page'; +import RegisterPiece from '../../../components/register_piece'; //TODO: Remove once finished with LumenusRegisterPiece import CylandLanding from './components/cyland/cyland_landing'; import CylandPieceContainer from './components/cyland/cyland_detail/cyland_piece_container'; @@ -148,6 +149,40 @@ let ROUTES = { + ), + 'lumenus': ( + + + + + + + + + + + + + + ) }; diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index 0fe5e210..a961f453 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -46,6 +46,13 @@ let constants = { 'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/ikonotv/ikono-logo-black.png', 'permissions': ['register', 'edit', 'share', 'del_from_collection'], 'type': 'wallet' + }, + { + 'subdomain': 'lumenus', + 'name': 'Lumenus', + 'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/lumenus/lumenus-logo.png', + 'permissions': ['register', 'edit', 'share', 'del_from_collection'], + 'type': 'wallet' } ], 'defaultDomain': { From 0c51eb374e76f27bc46a89c4e7417bf8fe385695 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 21 Oct 2015 17:37:29 +0200 Subject: [PATCH 006/115] Add Lumenus specific piece list --- .../piece_list_bulk_modal.js | 13 ++-- js/components/piece_list.js | 7 ++- .../components/lumenus/lumenus_piece_list.js | 61 +++++++++++++++++++ .../whitelabel/wallet/wallet_routes.js | 4 +- sass/ascribe_piece_list_toolbar.scss | 4 ++ 5 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js diff --git a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js index 3e5b6495..f5d97842 100644 --- a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js +++ b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js @@ -22,9 +22,16 @@ import { getLangText } from '../../utils/lang_utils.js'; let PieceListBulkModal = React.createClass({ propTypes: { + aclFilterBy: React.PropTypes.func, className: React.PropTypes.string }, + getDefaultProps() { + return { + aclFilterBy: (aclName) => aclName !== 'acl_view' + }; + }, + getInitialState() { return mergeOptions( EditionListStore.getState(), @@ -33,8 +40,6 @@ let PieceListBulkModal = React.createClass({ ); }, - - componentDidMount() { EditionListStore.listen(this.onChange); UserStore.listen(this.onChange); @@ -95,7 +100,7 @@ let PieceListBulkModal = React.createClass({ render() { let selectedEditions = this.fetchSelectedEditionList(); - let availableAcls = getAvailableAcls(selectedEditions, (aclName) => aclName !== 'acl_view'); + let availableAcls = getAvailableAcls(selectedEditions, this.props.aclFilterBy); if(Object.keys(availableAcls).length > 0) { return ( @@ -136,4 +141,4 @@ let PieceListBulkModal = React.createClass({ } }); -export default PieceListBulkModal; \ No newline at end of file +export default PieceListBulkModal; diff --git a/js/components/piece_list.js b/js/components/piece_list.js index de40be22..a5b9ff55 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -30,6 +30,7 @@ import { setDocumentTitle } from '../utils/dom_utils'; let PieceList = React.createClass({ propTypes: { accordionListItemType: React.PropTypes.func, + aclFilterBy: React.PropTypes.func, redirectTo: React.PropTypes.string, customSubmitButton: React.PropTypes.element, filterParams: React.PropTypes.array, @@ -63,7 +64,7 @@ let PieceList = React.createClass({ componentDidMount() { let page = this.props.location.query.page || 1; - + PieceListStore.listen(this.onChange); EditionListStore.listen(this.onChange); @@ -170,7 +171,9 @@ let PieceList = React.createClass({ applyOrderBy={this.applyOrderBy}> {this.props.customSubmitButton} - + diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js new file mode 100644 index 00000000..0a6b3b27 --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js @@ -0,0 +1,61 @@ +'use strict'; + +import React from 'react'; +import PieceList from '../../../../piece_list'; + +import UserActions from '../../../../../actions/user_actions'; +import UserStore from '../../../../../stores/user_store'; + +import { getLangText } from '../../../../../utils/lang_utils'; +import { setDocumentTitle } from '../../../../../utils/dom_utils'; + + +let LumenusPieceList = React.createClass({ + propTypes: { + location: React.PropTypes.object + }, + + getInitialState() { + return UserStore.getState(); + }, + + componentDidMount() { + UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + }, + + componentWillUnmount() { + UserStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + showOnlyConsignAcl(aclName) { + return aclName === 'acl_consign' || + aclName === 'acl_unconsign'; + }, + + render() { + setDocumentTitle(getLangText('Collection')); + + return ( +
+ +
+ ); + } +}); + +export default LumenusPieceList; diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index bcd0a60b..e258dfa7 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -29,6 +29,8 @@ import IkonotvRegisterPiece from './components/ikonotv/ikonotv_register_piece'; import IkonotvPieceContainer from './components/ikonotv/ikonotv_detail/ikonotv_piece_container'; import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications'; +import LumenusPieceList from './components/lumenus/lumenus_piece_list'; + import CCRegisterPiece from './components/cc/cc_register_piece'; import AuthProxyHandler from '../../../components/ascribe_routes/proxy_routes/auth_proxy_handler'; @@ -176,7 +178,7 @@ let ROUTES = { headerTitle='+ NEW WORK'/> diff --git a/sass/ascribe_piece_list_toolbar.scss b/sass/ascribe_piece_list_toolbar.scss index dde7f2f0..166ba382 100644 --- a/sass/ascribe_piece_list_toolbar.scss +++ b/sass/ascribe_piece_list_toolbar.scss @@ -86,4 +86,8 @@ top: 2px; } } + + .dropdown-menu { + min-width: 170px; + } } From de3c5bca14863b79c9cb5d66ad24f28b56629412 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 22 Oct 2015 11:12:41 +0200 Subject: [PATCH 007/115] Correct misleading comments in utils --- js/utils/general_utils.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js index 2cba98b9..cc1d22dd 100644 --- a/js/utils/general_utils.js +++ b/js/utils/general_utils.js @@ -131,7 +131,8 @@ export function mergeOptions(...l) { } /** - * Merges a number of objects even if there're having duplicates. + * Merges a number of objects even if there're having duplicates, + * taking the last given value for the key. * * DOES NOT RETURN AN ERROR! * @@ -141,8 +142,6 @@ export function mergeOptions(...l) { * @return {[type]} [description] */ export function mergeOptionsWithDuplicates(...l) { - // If the objects submitted in the list have duplicates,in their key names, - // abort the merge and tell the function's user to check his objects. let newObj = {}; for(let i = 1; i < l.length; i++) { @@ -174,7 +173,7 @@ export function update(a, ...l) { */ function _mergeOptions(obj1, obj2) { let obj3 = {}; - + for (let attrname in obj1) { obj3[attrname] = obj1[attrname]; } @@ -232,4 +231,4 @@ export function getSubdomain() { let { host } = window.location; let tokens = host.split('.'); return tokens.length > 2 ? tokens[0] : 'www'; -} \ No newline at end of file +} From ba777781b7a81a5f7a67132118a476ab6485b507 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 22 Oct 2015 11:13:44 +0200 Subject: [PATCH 008/115] Add sliding piece registration with dummy additional form --- .../lumenus_additional_data_form.js | 129 ++++++++++++ .../lumenus/lumenus_register_piece.js | 195 ++++++++++++++++++ .../whitelabel/wallet/wallet_routes.js | 3 +- 3 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js new file mode 100644 index 00000000..c0493ddd --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js @@ -0,0 +1,129 @@ +'use strict'; + +import React from 'react'; + +import Form from '../../../../../ascribe_forms/form'; +import Property from '../../../../../ascribe_forms/property'; + +import GlobalNotificationModel from '../../../../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; + +import ApiUrls from '../../../../../../constants/api_urls'; +import AppConstants from '../../../../../../constants/application_constants'; + +import requests from '../../../../../../utils/requests'; + +import { getLangText } from '../../../../../../utils/lang_utils'; + + +let LumenusAdditionalDataForm = React.createClass({ + propTypes: { + handleSuccess: React.PropTypes.func, + piece: React.PropTypes.object.isRequired, + isInline: React.PropTypes.bool, + location: React.PropTypes.object + }, + + getDefaultProps() { + return { + isInline: false + }; + }, + + getInitialState() { + return { + isUploadReady: true + }; + }, + + handleSuccess() { + let notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + + getFormData() { + let extradata = {}; + let formRefs = this.refs.form.refs; + + // Put additional fields in extra data object + Object + .keys(formRefs) + .forEach((fieldName) => { + extradata[fieldName] = formRefs[fieldName].state.value; + }); + + return { + extradata: extradata, + piece_id: this.props.piece.id + }; + + }, + + render() { + let { piece, isInline, handleSuccess } = this.props; + let buttons, spinner, heading; + + if(!isInline) { + buttons = ( + + ); + + spinner = ( +
+ +
+ ); + + heading = ( +
+

+ {getLangText('Provide additional details')} +

+
+ ); + } + + if(piece && piece.id) { + return ( +
+ {heading} + + + + + + +
+ ); + } else { + return ( +
+ +
+ ); + } + } +}); + +export default LumenusAdditionalDataForm; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js new file mode 100644 index 00000000..9d671ee0 --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js @@ -0,0 +1,195 @@ +'use strict'; + +import React from 'react'; +import { History } from 'react-router'; + +import Col from 'react-bootstrap/lib/Col'; +import Row from 'react-bootstrap/lib/Row'; + +import Property from '../../../../ascribe_forms/property'; +import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece'; + +import WhitelabelActions from '../../../../../actions/whitelabel_actions'; +import WhitelabelStore from '../../../../../stores/whitelabel_store'; + +import PieceListStore from '../../../../../stores/piece_list_store'; +import PieceListActions from '../../../../../actions/piece_list_actions'; + +import UserStore from '../../../../../stores/user_store'; +import UserActions from '../../../../../actions/user_actions'; + +import PieceStore from '../../../../../stores/piece_store'; +import PieceActions from '../../../../../actions/piece_actions'; + +import LumenusAdditionalDataForm from './lumenus_forms/lumenus_additional_data_form'; + +import SlidesContainer from '../../../../ascribe_slides_container/slides_container'; + +import { getLangText } from '../../../../../utils/lang_utils'; +import { setDocumentTitle } from '../../../../../utils/dom_utils'; +import { mergeOptions } from '../../../../../utils/general_utils'; + + +let LumenusRegisterPiece = React.createClass({ + propTypes: { + location: React.PropTypes.object + }, + + mixins: [History], + + getInitialState(){ + return mergeOptions( + UserStore.getState(), + PieceListStore.getState(), + PieceStore.getState(), + WhitelabelStore.getState(), + { + selectedLicense: 0, + isFineUploaderActive: false, + step: 0 + }); + }, + + componentDidMount() { + PieceListStore.listen(this.onChange); + UserStore.listen(this.onChange); + PieceStore.listen(this.onChange); + WhitelabelStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + WhitelabelActions.fetchWhitelabel(); + + let queryParams = this.props.location.query; + + // Since every step of this register process is atomic, + // we may need to enter the process at step 1 or 2. + // If this is the case, we'll need the piece number to complete submission. + // It is encoded in the URL as a queryParam and we're checking for it here. + // + // We're using 'in' here as we want to know if 'piece_id' is present in the url, + // we don't care about the value. + if(queryParams && 'piece_id' in queryParams) { + PieceActions.fetchOne(queryParams.piece_id); + } + }, + + componentWillUnmount() { + PieceListStore.unlisten(this.onChange); + UserStore.unlisten(this.onChange); + PieceStore.unlisten(this.onChange); + WhitelabelStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + + if(this.state.currentUser && this.state.currentUser.email) { + // we should also make the fineuploader component editable again + this.setState({ + isFineUploaderActive: true + }); + } + }, + + handleRegisterSuccess(response){ + this.refreshPieceList(); + + // also start loading the piece for the next step + if(response && response.piece) { + PieceActions.updatePiece({}); + PieceActions.updatePiece(response.piece); + } + + this.incrementStep(); + + this.refs.slidesContainer.nextSlide({ piece_id: response.piece.id }); + }, + + handleAdditionalDataSuccess() { + // We need to refetch the piece again after submitting the additional data + // since we want it's otherData to be displayed when the user choses to click + // on the browsers back button. + PieceActions.fetchOne(this.state.piece.id); + + this.refreshPieceList(); + + this.history.pushState(null, `/collection`); + }, + + // We need to increase the step to lock the forms that are already filled out + incrementStep() { + // also increase step + let newStep = this.state.step + 1; + this.setState({ + step: newStep + }); + }, + + refreshPieceList() { + PieceListActions.fetchPieceList( + this.state.page, + this.state.pageSize, + this.state.searchTerm, + this.state.orderBy, + this.state.orderAsc, + this.state.filterBy + ); + }, + + // basically redirects to the second slide (index: 1), when the user is not logged in + onLoggedOut() { + this.history.pushState(null, '/login'); + }, + + render() { + setDocumentTitle(getLangText('Register a new piece')); + + return ( + +
+ + + 0} + enableLocalHashing={false} + headerMessage={getLangText('Consign to Lumenus')} + submitMessage={getLangText('Proceed to additional details')} + isFineUploaderActive={this.state.isFineUploaderActive} + handleSuccess={this.handleRegisterSuccess} + onLoggedOut={this.onLoggedOut} + location={this.props.location}> + + + + + + +
+
+ + + + + +
+
+ ); + } +}); + +export default LumenusRegisterPiece; diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index e258dfa7..e170d671 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -30,6 +30,7 @@ import IkonotvPieceContainer from './components/ikonotv/ikonotv_detail/ikonotv_p import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications'; import LumenusPieceList from './components/lumenus/lumenus_piece_list'; +import LumenusRegisterPiece from './components/lumenus/lumenus_register_piece'; import CCRegisterPiece from './components/cc/cc_register_piece'; @@ -174,7 +175,7 @@ let ROUTES = { component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(ContractSettings)}/> Date: Thu, 22 Oct 2015 14:14:06 +0200 Subject: [PATCH 009/115] Fix typo in edition api url --- .../ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js | 1 - js/components/whitelabel/wallet/constants/wallet_api_urls.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js index f5d97842..2a4d4301 100644 --- a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js +++ b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js @@ -137,7 +137,6 @@ let PieceListBulkModal = React.createClass({ } else { return null; } - } }); diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index 8f4543c7..87ee6b14 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -21,7 +21,7 @@ function getWalletApiUrls(subdomain) { } else if (subdomain === 'lumenus'){ return { - 'editions': walletConstants.walletApiEndpoint + subdomain + 'editions/', + 'editions': walletConstants.walletApiEndpoint + subdomain + '/editions/', 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' }; From 3dedc93d2e979fc33c3d821343256691a03122b8 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 23 Oct 2015 14:19:44 +0200 Subject: [PATCH 010/115] Force consignee to be submissions@lumenus.co Special white label form settings only defined for consign form for now, but could be added to others as needed. --- js/components/ascribe_buttons/acl_button.js | 70 ++++++++----------- js/components/ascribe_forms/form_consign.js | 10 ++- .../cyland/cyland_register_piece.js | 7 +- js/constants/application_constants.js | 8 ++- js/utils/form_utils.js | 57 ++++++++++++--- 5 files changed, 97 insertions(+), 55 deletions(-) diff --git a/js/components/ascribe_buttons/acl_button.js b/js/components/ascribe_buttons/acl_button.js index e3c7fa1c..ee337149 100644 --- a/js/components/ascribe_buttons/acl_button.js +++ b/js/components/ascribe_buttons/acl_button.js @@ -34,15 +34,19 @@ let AclButton = React.createClass({ className: React.PropTypes.string }, - isPiece(){ + isPiece() { return this.props.pieceOrEditions.constructor !== Array; }, - actionProperties(){ + actionProperties() { + let message = getAclFormMessage({ + aclName: this.props.action, + entities: this.props.pieceOrEditions, + isPiece: this.isPiece(), + senderName: this.props.currentUser.username + }); - let message = getAclFormMessage(this.props.action, this.getTitlesString(), this.props.currentUser.username); - - if (this.props.action === 'acl_consign'){ + if (this.props.action === 'acl_consign') { return { title: getLangText('Consign artwork'), tooltip: getLangText('Have someone else sell the artwork'), @@ -51,11 +55,10 @@ let AclButton = React.createClass({ message={message} id={this.getFormDataId()} url={ApiUrls.ownership_consigns}/> - ), + ), handleSuccess: this.showNotification }; - } - if (this.props.action === 'acl_unconsign'){ + } else if (this.props.action === 'acl_unconsign') { return { title: getLangText('Unconsign artwork'), tooltip: getLangText('Have the owner manage his sales again'), @@ -64,10 +67,10 @@ let AclButton = React.createClass({ message={message} id={this.getFormDataId()} url={ApiUrls.ownership_unconsigns}/> - ), + ), handleSuccess: this.showNotification }; - }else if (this.props.action === 'acl_transfer') { + } else if (this.props.action === 'acl_transfer') { return { title: getLangText('Transfer artwork'), tooltip: getLangText('Transfer the ownership of the artwork'), @@ -79,32 +82,32 @@ let AclButton = React.createClass({ ), handleSuccess: this.showNotification }; - } - else if (this.props.action === 'acl_loan'){ + } else if (this.props.action === 'acl_loan') { return { title: getLangText('Loan artwork'), tooltip: getLangText('Loan your artwork for a limited period of time'), - form: ( + url={this.isPiece() ? ApiUrls.ownership_loans_pieces + : ApiUrls.ownership_loans_editions}/> ), handleSuccess: this.showNotification }; - } - else if (this.props.action === 'acl_loan_request'){ + } else if (this.props.action === 'acl_loan_request') { return { title: getLangText('Loan artwork'), tooltip: getLangText('Someone requested you to loan your artwork for a limited period of time'), - form: ( ), handleSuccess: this.showNotification }; - } - else if (this.props.action === 'acl_share'){ + } else if (this.props.action === 'acl_share') { return { title: getLangText('Share artwork'), tooltip: getLangText('Share the artwork'), @@ -112,8 +115,9 @@ let AclButton = React.createClass({ - ), + url={this.isPiece() ? ApiUrls.ownership_shares_pieces + : ApiUrls.ownership_shares_editions}/> + ), handleSuccess: this.showNotification }; } else { @@ -121,32 +125,18 @@ let AclButton = React.createClass({ } }, - showNotification(response){ + showNotification(response) { this.props.handleSuccess(); - if(response.notification) { + if (response.notification) { let notification = new GlobalNotificationModel(response.notification, 'success'); GlobalNotificationActions.appendGlobalNotification(notification); } }, - // plz move to share form - getTitlesString(){ - if (this.isPiece()){ - return '\"' + this.props.pieceOrEditions.title + '\"'; - } - else { - return this.props.pieceOrEditions.map(function(edition) { - return '- \"' + edition.title + ', ' + getLangText('edition') + ' ' + edition.edition_number + '\"\n'; - }).join(''); - } - - }, - getFormDataId(){ if (this.isPiece()) { return {piece_id: this.props.pieceOrEditions.id}; - } - else { + } else { return {bitcoin_id: this.props.pieceOrEditions.map(function(edition){ return edition.bitcoin_id; }).join()}; @@ -162,7 +152,7 @@ let AclButton = React.createClass({ }, render() { - if (this.props.availableAcls){ + if (this.props.availableAcls) { let shouldDisplay = this.props.availableAcls[this.props.action]; let aclProps = this.actionProperties(); let buttonClassName = this.props.buttonAcceptClassName ? this.props.buttonAcceptClassName : ''; @@ -183,4 +173,4 @@ let AclButton = React.createClass({ } }); -export default AclButton; \ No newline at end of file +export default AclButton; diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index f57e0045..7e3233e8 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -11,6 +11,7 @@ import InputTextAreaToggable from './input_textarea_toggable'; import AscribeSpinner from '../ascribe_spinner'; import { getLangText } from '../../utils/lang_utils.js'; +import { getSubdomainFormSettings } from '../../utils/form_utils'; let ConsignForm = React.createClass({ propTypes: { @@ -25,6 +26,8 @@ let ConsignForm = React.createClass({ }, render() { + let envSettings = getSubdomainFormSettings('consign'); + return (
}> + label={getLangText('Email')} + editable={!envSettings.consigneeDisabled} + overrideForm={true}> Date: Mon, 26 Oct 2015 11:09:04 +0100 Subject: [PATCH 011/115] Revert changes to allow piece list to filter ACLs We can do this with the backend instead. --- .../piece_list_bulk_modal.js | 9 +-------- js/components/piece_list.js | 4 +--- .../wallet/components/lumenus/lumenus_piece_list.js | 6 ------ 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js index 2a4d4301..3f2217e6 100644 --- a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js +++ b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js @@ -22,16 +22,9 @@ import { getLangText } from '../../utils/lang_utils.js'; let PieceListBulkModal = React.createClass({ propTypes: { - aclFilterBy: React.PropTypes.func, className: React.PropTypes.string }, - getDefaultProps() { - return { - aclFilterBy: (aclName) => aclName !== 'acl_view' - }; - }, - getInitialState() { return mergeOptions( EditionListStore.getState(), @@ -100,7 +93,7 @@ let PieceListBulkModal = React.createClass({ render() { let selectedEditions = this.fetchSelectedEditionList(); - let availableAcls = getAvailableAcls(selectedEditions, this.props.aclFilterBy); + let availableAcls = getAvailableAcls(selectedEditions, (aclName) => aclName !== 'acl_view'); if(Object.keys(availableAcls).length > 0) { return ( diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 07a9d47e..c427a911 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -32,7 +32,6 @@ import { setDocumentTitle } from '../utils/dom_utils'; let PieceList = React.createClass({ propTypes: { accordionListItemType: React.PropTypes.func, - aclFilterBy: React.PropTypes.func, redirectTo: React.PropTypes.string, customSubmitButton: React.PropTypes.element, filterParams: React.PropTypes.array, @@ -179,8 +178,7 @@ let PieceList = React.createClass({ } + className="ascribe-piece-list-bulk-modal"/> diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js index 0a6b3b27..c3f01374 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js @@ -32,11 +32,6 @@ let LumenusPieceList = React.createClass({ this.setState(state); }, - showOnlyConsignAcl(aclName) { - return aclName === 'acl_consign' || - aclName === 'acl_unconsign'; - }, - render() { setDocumentTitle(getLangText('Collection')); @@ -44,7 +39,6 @@ let LumenusPieceList = React.createClass({
Date: Mon, 26 Oct 2015 19:17:06 +0100 Subject: [PATCH 012/115] Set the consignee email by using the server's white label settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consignee email is available through the white label settings we get from the server, so we don’t need to hardcode them into the constants. Separated out AclButtonList from PieceListbulkModal to make the modal more flexible and separated in concerns. --- js/components/ascribe_buttons/acl_button.js | 10 +- js/components/ascribe_forms/form_consign.js | 11 +-- .../piece_list_bulk_modal.js | 68 +++----------- js/components/piece_list.js | 61 ++++++++++++- .../lumenus_acl_button_list.js | 39 ++++++++ .../lumenus_buttons/lumenus_submit_button.js | 91 +++++++++++++++++++ .../components/lumenus/lumenus_piece_list.js | 4 + .../lumenus/lumenus_register_piece.js | 7 -- js/constants/application_constants.js | 6 -- js/utils/acl_utils.js | 13 ++- js/utils/form_utils.js | 21 +++-- 11 files changed, 230 insertions(+), 101 deletions(-) create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js diff --git a/js/components/ascribe_buttons/acl_button.js b/js/components/ascribe_buttons/acl_button.js index ee337149..7eec36f1 100644 --- a/js/components/ascribe_buttons/acl_button.js +++ b/js/components/ascribe_buttons/acl_button.js @@ -16,7 +16,7 @@ import GlobalNotificationActions from '../../actions/global_notification_actions import ApiUrls from '../../constants/api_urls'; -import { getAclFormMessage } from '../../utils/form_utils'; +import { getAclFormMessage, getAclFormDataId } from '../../utils/form_utils'; import { getLangText } from '../../utils/lang_utils'; let AclButton = React.createClass({ @@ -134,13 +134,7 @@ let AclButton = React.createClass({ }, getFormDataId(){ - if (this.isPiece()) { - return {piece_id: this.props.pieceOrEditions.id}; - } else { - return {bitcoin_id: this.props.pieceOrEditions.map(function(edition){ - return edition.bitcoin_id; - }).join()}; - } + return getAclFormDataId(this.isPiece(), this.props.pieceOrEditions); }, // Removes the acl_ prefix and converts to upper case diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index 7e3233e8..9617acd4 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -11,12 +11,11 @@ import InputTextAreaToggable from './input_textarea_toggable'; import AscribeSpinner from '../ascribe_spinner'; import { getLangText } from '../../utils/lang_utils.js'; -import { getSubdomainFormSettings } from '../../utils/form_utils'; - let ConsignForm = React.createClass({ propTypes: { url: React.PropTypes.string, id: React.PropTypes.object, + email: React.PropTypes.string, message: React.PropTypes.string, handleSuccess: React.PropTypes.func }, @@ -26,8 +25,6 @@ let ConsignForm = React.createClass({ }, render() { - let envSettings = getSubdomainFormSettings('consign'); - return ( + editable={!this.props.email} + overrideForm={!!this.props.email}> { - return this.state.editionList[pieceId] - .filter((edition) => edition.selected).length > 0; - }); - return filteredPieceIdList; - }, - - fetchSelectedEditionList() { - let selectedEditionList = []; - - Object - .keys(this.state.editionList) - .forEach((pieceId) => { - let filteredEditionsForPiece = this.state.editionList[pieceId].filter((edition) => edition.selected); - selectedEditionList = selectedEditionList.concat(filteredEditionsForPiece); - }); - - return selectedEditionList; - }, - clearAllSelections() { EditionListActions.clearAllEditionSelections(); EditionListActions.closeAllEditionLists(); }, - handleSuccess() { - PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, - this.state.orderBy, this.state.orderAsc, this.state.filterBy); - - this.fetchSelectedPieceEditionList() - .forEach((pieceId) => { - EditionListActions.refreshEditionList({pieceId, filterBy: {}}); - }); - EditionListActions.clearAllEditionSelections(); - }, - render() { - let selectedEditions = this.fetchSelectedEditionList(); - let availableAcls = getAvailableAcls(selectedEditions, (aclName) => aclName !== 'acl_view'); - - if(Object.keys(availableAcls).length > 0) { + if (Object.keys(this.props.availableAcls).length > 0) { return (
@@ -104,7 +70,7 @@ let PieceListBulkModal = React.createClass({
+ numberOfSelectedEditions={this.props.selectedEditions.length} />          

- - - + {this.props.children}
diff --git a/js/components/piece_list.js b/js/components/piece_list.js index c427a911..528ee409 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -13,6 +13,9 @@ import AccordionList from './ascribe_accordion_list/accordion_list'; import AccordionListItemWallet from './ascribe_accordion_list/accordion_list_item_wallet'; import AccordionListItemTableEditions from './ascribe_accordion_list/accordion_list_item_table_editions'; +import AclButtonList from './ascribe_buttons/acl_button_list.js'; +import DeleteButton from './ascribe_buttons/delete_button'; + import Pagination from './ascribe_pagination/pagination'; import PieceListFilterDisplay from './piece_list_filter_display'; @@ -24,6 +27,7 @@ import AscribeSpinner from './ascribe_spinner'; import AppConstants from '../constants/application_constants'; +import { getAvailableAcls } from '../utils/acl_utils'; import { mergeOptions } from '../utils/general_utils'; import { getLangText } from '../utils/lang_utils'; import { setDocumentTitle } from '../utils/dom_utils'; @@ -32,6 +36,7 @@ import { setDocumentTitle } from '../utils/dom_utils'; let PieceList = React.createClass({ propTypes: { accordionListItemType: React.PropTypes.func, + bulkModalButtonListType: React.PropTypes.func, redirectTo: React.PropTypes.string, customSubmitButton: React.PropTypes.element, filterParams: React.PropTypes.array, @@ -45,6 +50,7 @@ let PieceList = React.createClass({ getDefaultProps() { return { accordionListItemType: AccordionListItemWallet, + bulkModalButtonListType: AclButtonList, orderParams: ['artist_name', 'title'], filterParams: [{ label: getLangText('Show works I can'), @@ -152,9 +158,46 @@ let PieceList = React.createClass({ orderBy, this.state.orderAsc, this.state.filterBy); }, + fetchSelectedPieceEditionList() { + let filteredPieceIdList = Object.keys(this.state.editionList) + .filter((pieceId) => { + return this.state.editionList[pieceId] + .filter((edition) => edition.selected).length > 0; + }); + return filteredPieceIdList; + }, + + fetchSelectedEditionList() { + let selectedEditionList = []; + + Object + .keys(this.state.editionList) + .forEach((pieceId) => { + let filteredEditionsForPiece = this.state.editionList[pieceId].filter((edition) => edition.selected); + selectedEditionList = selectedEditionList.concat(filteredEditionsForPiece); + }); + + return selectedEditionList; + }, + + handleAclSuccess() { + PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, + this.state.orderBy, this.state.orderAsc, this.state.filterBy); + + this.fetchSelectedPieceEditionList() + .forEach((pieceId) => { + EditionListActions.refreshEditionList({pieceId, filterBy: {}}); + }); + EditionListActions.clearAllEditionSelections(); + }, + render() { - let loadingElement = ; - let AccordionListItemType = this.props.accordionListItemType; + const loadingElement = ; + const AccordionListItemType = this.props.accordionListItemType; + const BulkModalButtonListType = this.props.bulkModalButtonListType; + + const selectedEditions = this.fetchSelectedEditionList(); + const availableAcls = getAvailableAcls(selectedEditions, (aclName) => aclName !== 'acl_view'); setDocumentTitle(getLangText('Collection')); @@ -178,7 +221,19 @@ let PieceList = React.createClass({ } + availableAcls={availableAcls} + selectedEditions={selectedEditions} + className="ascribe-piece-list-bulk-modal"> + + + + diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js new file mode 100644 index 00000000..6c1aa0ae --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js @@ -0,0 +1,39 @@ +'use strict'; + +import React from 'react'; + +import LumenusSubmitButton from './lumenus_submit_button'; + +import AclProxy from '../../../../../acl_proxy'; + +import DeleteButton from '../../../../../ascribe_buttons/delete_button'; + +let LumenusAclButtonList = React.createClass({ + propTypes: { + availableAcls: React.PropTypes.object.isRequired, + className: React.PropTypes.string, + editions: React.PropTypes.array, + handleSuccess: React.PropTypes.func, + children: React.PropTypes.oneOfType([ + React.PropTypes.arrayOf(React.PropTypes.element), + React.PropTypes.element + ]) + }, + + render() { + return ( +
+ + + + {this.props.children} +
+ ); + } +}); + +export default LumenusAclButtonList; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js new file mode 100644 index 00000000..13483502 --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js @@ -0,0 +1,91 @@ +'use strict'; + +import React from 'react'; +import classNames from 'classnames'; + + +import GlobalNotificationModel from '../../../../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; +import UserActions from '../../../../../../actions/user_actions'; +import UserStore from '../../../../../../stores/user_store'; +import WhitelabelActions from '../../../../../../actions/whitelabel_actions'; +import WhitelabelStore from '../../../../../../stores/whitelabel_store'; + +import ConsignForm from '../../../../../ascribe_forms/form_consign'; + +import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper'; + +import ApiUrls from '../../../../../../constants/api_urls'; + +import { getAclFormMessage, getAclFormDataId } from '../../../../../../utils/form_utils'; +import { mergeOptions } from '../../../../../../utils/general_utils'; +import { getLangText } from '../../../../../../utils/lang_utils'; + +let LumenusSubmitButton = React.createClass({ + propTypes: { + className: React.PropTypes.string, + editions: React.PropTypes.array, + handleSuccess: React.PropTypes.func, + }, + + getInitialState() { + return mergeOptions( + UserStore.getState(), + WhitelabelStore.getState() + ); + }, + + componentDidMount() { + UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + WhitelabelStore.listen(this.onChange); + WhitelabelActions.fetchWhitelabel(); + }, + + componentWillUnmount() { + UserStore.unlisten(this.onChange); + WhitelabelStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + showNotification(response) { + this.props.handleSuccess(); + if (response.notification) { + let notification = new GlobalNotificationModel(response.notification, 'success'); + GlobalNotificationActions.appendGlobalNotification(notification); + } + }, + + render() { + const { className, editions, handleSuccess } = this.props; + const title = getLangText('Consign to Lumenus'); + const message = getAclFormMessage({ + aclName: 'acl_consign', + entities: editions, + isPiece: false, + senderName: this.state.currentUser.username + }); + + return ( + + {title} + + } + handleSuccess={this.showNotification} + title={title}> + + + ); + } +}); + +export default LumenusSubmitButton; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js index c3f01374..403e226b 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js @@ -1,6 +1,9 @@ 'use strict'; import React from 'react'; + +import LumenusAclButtonList from './lumenus_buttons/lumenus_acl_button_list'; + import PieceList from '../../../../piece_list'; import UserActions from '../../../../../actions/user_actions'; @@ -39,6 +42,7 @@ let LumenusPieceList = React.createClass({
= 1) { + if (editionsCopy.length >= 1) { availableAcls = editionsCopy[0].acl; - } - if(editionsCopy.length >= 2) { - for(let i = 1; i < editionsCopy.length; i++) { + } else if (editionsCopy.length >= 2) { + for (let i = 1; i < editionsCopy.length; i++) { availableAcls = intersectAcls(availableAcls, editionsCopy[i].acl); } } // convert acls back to key-value object let availableAclsObj = {}; - for(let i = 0; i < availableAcls.length; i++) { + for (let i = 0; i < availableAcls.length; i++) { availableAclsObj[availableAcls[i]] = true; } return availableAclsObj; -} \ No newline at end of file +} diff --git a/js/utils/form_utils.js b/js/utils/form_utils.js index a98d956f..d2d2cd29 100644 --- a/js/utils/form_utils.js +++ b/js/utils/form_utils.js @@ -5,15 +5,19 @@ import { getLangText } from './lang_utils'; import AppConstants from '../constants/application_constants'; /** - * Gets a dictionary of settings for a form based on the environment - * (ie. if on a whitelabel) - * @param {string} formName Name of the form - * @return {object} Settings key-val dictionary + * Get the data ids of the given piece or editions. + * @param {boolean} isPiece Is the given entities parameter a piece? (False: array of editions) + * @param {(object|object[])} pieceOrEditions Piece or array of editions + * @return {(object|object[])} Data IDs of the pieceOrEditions for the form */ -export function getSubdomainFormSettings(formName) { - let subdomainFormSettings = AppConstants.whitelabel.formSettings || {}; - - return subdomainFormSettings[formName] || {}; +export function getAclFormDataId(isPiece, pieceOrEditions) { + if (isPiece) { + return {piece_id: pieceOrEditions.id}; + } else { + return {bitcoin_id: pieceOrEditions.map(function(edition){ + return edition.bitcoin_id; + }).join()}; + } } /** @@ -21,6 +25,7 @@ export function getSubdomainFormSettings(formName) { * @param {object} options Options object for creating the message: * @param {string} options.aclName Enum name of an acl * @param {(object|object[])} options.entities Piece or array of Editions + * @param {boolean} options.isPiece Is the given entities parameter a piece? (False: array of editions) * @param {string} [options.senderName] Name of the sender * @return {string} Completed message */ From 633939b7fa926404bd0f24b0e93386c22cdb8cf4 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 27 Oct 2015 17:01:50 +0100 Subject: [PATCH 013/115] Use AclButton in LumenusSubmitButton Instead of copying functionality from AclButton to add a default email, LumenusSubmitButton just returns one with preset settings. --- js/components/ascribe_buttons/acl_button.js | 3 + .../lumenus_acl_button_list.js | 44 ++++++++++--- .../lumenus_buttons/lumenus_submit_button.js | 65 +++++-------------- 3 files changed, 52 insertions(+), 60 deletions(-) diff --git a/js/components/ascribe_buttons/acl_button.js b/js/components/ascribe_buttons/acl_button.js index 7eec36f1..e3a1b8c4 100644 --- a/js/components/ascribe_buttons/acl_button.js +++ b/js/components/ascribe_buttons/acl_button.js @@ -30,6 +30,7 @@ let AclButton = React.createClass({ currentUser: React.PropTypes.object, buttonAcceptName: React.PropTypes.string, buttonAcceptClassName: React.PropTypes.string, + email: React.PropTypes.string, handleSuccess: React.PropTypes.func.isRequired, className: React.PropTypes.string }, @@ -52,6 +53,7 @@ let AclButton = React.createClass({ tooltip: getLangText('Have someone else sell the artwork'), form: ( @@ -88,6 +90,7 @@ let AclButton = React.createClass({ tooltip: getLangText('Loan your artwork for a limited period of time'), form: ( - - - +
+ + {this.props.children}
); diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js index 13483502..365a5a34 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js @@ -3,47 +3,32 @@ import React from 'react'; import classNames from 'classnames'; +import AclButton from '../../../../../ascribe_buttons/acl_button'; -import GlobalNotificationModel from '../../../../../../models/global_notification_model'; -import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; -import UserActions from '../../../../../../actions/user_actions'; -import UserStore from '../../../../../../stores/user_store'; import WhitelabelActions from '../../../../../../actions/whitelabel_actions'; import WhitelabelStore from '../../../../../../stores/whitelabel_store'; -import ConsignForm from '../../../../../ascribe_forms/form_consign'; - -import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper'; - -import ApiUrls from '../../../../../../constants/api_urls'; - -import { getAclFormMessage, getAclFormDataId } from '../../../../../../utils/form_utils'; -import { mergeOptions } from '../../../../../../utils/general_utils'; import { getLangText } from '../../../../../../utils/lang_utils'; let LumenusSubmitButton = React.createClass({ propTypes: { - className: React.PropTypes.string, + availableAcls: React.PropTypes.object.isRequired, + currentUser: React.PropTypes.object, editions: React.PropTypes.array, handleSuccess: React.PropTypes.func, + className: React.PropTypes.string, }, getInitialState() { - return mergeOptions( - UserStore.getState(), - WhitelabelStore.getState() - ); + return WhitelabelStore.getState(); }, componentDidMount() { - UserStore.listen(this.onChange); - UserActions.fetchCurrentUser(); WhitelabelStore.listen(this.onChange); WhitelabelActions.fetchWhitelabel(); }, componentWillUnmount() { - UserStore.unlisten(this.onChange); WhitelabelStore.unlisten(this.onChange); }, @@ -51,39 +36,19 @@ let LumenusSubmitButton = React.createClass({ this.setState(state); }, - showNotification(response) { - this.props.handleSuccess(); - if (response.notification) { - let notification = new GlobalNotificationModel(response.notification, 'success'); - GlobalNotificationActions.appendGlobalNotification(notification); - } - }, - render() { - const { className, editions, handleSuccess } = this.props; - const title = getLangText('Consign to Lumenus'); - const message = getAclFormMessage({ - aclName: 'acl_consign', - entities: editions, - isPiece: false, - senderName: this.state.currentUser.username - }); + const { availableAcls, currentUser, className, editions, handleSuccess } = this.props; return ( - - {title} - - } - handleSuccess={this.showNotification} - title={title}> - - + ); } }); From 82b49fc653b5b90b698364e717f3c7284f1943ba Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 27 Oct 2015 17:54:35 +0100 Subject: [PATCH 014/115] Add additional required properties for Lumenus Currently uses the FurtherDetailsFileuploader, just relabeled, to handle the marketplace image. For now, this allows us to avoid creating another property in the backend to handle the image, but could be replaced or modified in the future if we do want to create another property. --- .../further_details_fileuploader.js | 6 +- .../cyland_additional_data_form.js | 4 +- .../lumenus_additional_data_form.js | 73 +++++++++++++++---- 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/js/components/ascribe_detail/further_details_fileuploader.js b/js/components/ascribe_detail/further_details_fileuploader.js index c5ef8a1c..6f9abca4 100644 --- a/js/components/ascribe_detail/further_details_fileuploader.js +++ b/js/components/ascribe_detail/further_details_fileuploader.js @@ -13,6 +13,7 @@ import { getCookie } from '../../utils/fetch_api_utils'; let FurtherDetailsFileuploader = React.createClass({ propTypes: { + label: React.PropTypes.string, uploadStarted: React.PropTypes.func, pieceId: React.PropTypes.number, otherData: React.PropTypes.arrayOf(React.PropTypes.object), @@ -26,6 +27,7 @@ let FurtherDetailsFileuploader = React.createClass({ getDefaultProps() { return { + label: "Additional files", multiple: false }; }, @@ -44,7 +46,7 @@ let FurtherDetailsFileuploader = React.createClass({ return ( + label={this.props.label}> + className="btn btn-default btn-wide" + disabled={!this.state.isUploadReady}> {getLangText('Register work')} ); @@ -98,20 +114,49 @@ let LumenusAdditionalDataForm = React.createClass({ buttons={buttons} spinner={spinner}> {heading} + - + - + + + + + + + From c72a5aa308ab4f0b163f1e19d60456a3c8d4b422 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 28 Oct 2015 11:26:54 +0100 Subject: [PATCH 015/115] Add Lumenus specific data forms to edition and piece details --- js/components/ascribe_detail/edition.js | 19 ++++++-- .../ascribe_detail/edition_action_panel.js | 26 +++++++---- .../ascribe_detail/edition_container.js | 4 ++ .../ascribe_detail/piece_container.js | 12 ++++- .../lumenus_edition_container.js | 30 ++++++++++++ .../lumenus_detail/lumenus_further_details.js | 46 +++++++++++++++++++ .../lumenus_detail/lumenus_piece_container.js | 27 +++++++++++ .../lumenus_additional_data_form.js | 18 +++++--- .../components/lumenus/lumenus_piece_list.js | 21 --------- .../lumenus/lumenus_register_piece.js | 11 ++--- .../whitelabel/wallet/wallet_routes.js | 6 ++- 11 files changed, 170 insertions(+), 50 deletions(-) create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index bcd2902c..bcd6743c 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -40,6 +40,8 @@ import { getLangText } from '../../utils/lang_utils'; */ let Edition = React.createClass({ propTypes: { + actionPanelButtonListType: React.PropTypes.func, + furtherDetailsType: React.PropTypes.func, edition: React.PropTypes.object, loadEdition: React.PropTypes.func, location: React.PropTypes.object @@ -47,6 +49,12 @@ let Edition = React.createClass({ mixins: [History], + getDefaultProps() { + return { + furtherDetailsType: FurtherDetails + }; + }, + getInitialState() { return UserStore.getState(); }, @@ -74,6 +82,8 @@ let Edition = React.createClass({ }, render() { + let FurtherDetailsType = this.props.furtherDetailsType; + return ( @@ -89,6 +99,7 @@ let Edition = React.createClass({
@@ -151,13 +162,13 @@ let Edition = React.createClass({ show={this.props.edition.acl.acl_edit || Object.keys(this.props.edition.extra_data).length > 0 || this.props.edition.other_data.length > 0}> - + location={this.props.location} /> {this.getStatus()} diff --git a/js/components/ascribe_detail/edition_action_panel.js b/js/components/ascribe_detail/edition_action_panel.js index bd423e4c..3d6d3043 100644 --- a/js/components/ascribe_detail/edition_action_panel.js +++ b/js/components/ascribe_detail/edition_action_panel.js @@ -34,6 +34,7 @@ import { getLangText } from '../../utils/lang_utils'; */ let EditionActionPanel = React.createClass({ propTypes: { + actionPanelButtonListType: React.PropTypes.func, edition: React.PropTypes.object, currentUser: React.PropTypes.object, handleSuccess: React.PropTypes.func @@ -41,6 +42,12 @@ let EditionActionPanel = React.createClass({ mixins: [History], + getDefaultProps() { + return { + actionPanelButtonListType: AclButtonList + }; + }, + getInitialState() { return PieceListStore.getState(); }, @@ -85,7 +92,8 @@ let EditionActionPanel = React.createClass({ }, render(){ - let {edition, currentUser} = this.props; + let { edition, currentUser } = this.props; + let ActionPanelButtonListType = this.props.actionPanelButtonListType; if (edition && edition.notifications && @@ -102,7 +110,7 @@ let EditionActionPanel = React.createClass({ return ( -
+ ); + } +}); + +export default LumenusEditionContainer; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js new file mode 100644 index 00000000..17dbad20 --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js @@ -0,0 +1,46 @@ +'use strict'; + +import React from 'react'; + +import LumenusAdditionalDataForm from '../lumenus_forms/lumenus_additional_data_form' + +import GlobalNotificationModel from '../../../../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; + +let FurtherDetails = React.createClass({ + propTypes: { + pieceId: React.PropTypes.number, + extraData: React.PropTypes.object, + otherData: React.PropTypes.arrayOf(React.PropTypes.object), + handleSuccess: React.PropTypes.func, + location: React.PropTypes.object + }, + + showNotification() { + this.props.handleSuccess(); + let notification = new GlobalNotificationModel('Further details updated', 'success'); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + + render() { + const { pieceId, extraData, otherData, handleSuccess, location } = this.props; + + // Instead of grabbing the entire piece from the PieceStore and making this component + // stateful, we can put together a piece for the additional form solely based on the props + const piece = { + id: pieceId, + extra_data: extraData, + other_data: otherData + }; + + return ( + + ); + } +}); + +export default FurtherDetails; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js new file mode 100644 index 00000000..03fd9d9b --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js @@ -0,0 +1,27 @@ +'use strict'; + +import React from 'react'; + +import LumenusFurtherDetails from './lumenus_further_details'; + +import PieceContainer from '../../../../../ascribe_detail/piece_container'; + +let LumenusPieceContainer = React.createClass({ + propTypes: { + params: React.PropTypes.object, + location: React.PropTypes.object + }, + + render() { + return ( +
+ +
+ ); + } +}); + +export default LumenusPieceContainer; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js index d75bb946..3df03fd8 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js @@ -23,7 +23,11 @@ import { getLangText } from '../../../../../../utils/lang_utils'; let LumenusAdditionalDataForm = React.createClass({ propTypes: { handleSuccess: React.PropTypes.func, - piece: React.PropTypes.object.isRequired, + piece: React.PropTypes.shape({ + id: React.PropTypes.number, + extra_data: React.PropTypes.object, + other_data: React.PropTypes.arrayOf(React.PropTypes.object) + }).isRequired, isInline: React.PropTypes.bool, location: React.PropTypes.object }, @@ -78,7 +82,7 @@ let LumenusAdditionalDataForm = React.createClass({ let { piece, isInline, handleSuccess } = this.props; let buttons, spinner, heading; - if(!isInline) { + if (!isInline) { buttons = (
} - badge={this.getGlyphicon()}> + badge={this.getGlyphicon()}> {this.getCreateEditionsDialog()} {/* this.props.children is AccordionListItemTableEditions */} {this.props.children} diff --git a/js/components/ascribe_buttons/acl_button.js b/js/components/ascribe_buttons/acl_button.js index e3a1b8c4..5197a744 100644 --- a/js/components/ascribe_buttons/acl_button.js +++ b/js/components/ascribe_buttons/acl_button.js @@ -156,7 +156,8 @@ let AclButton = React.createClass({ return ( + } diff --git a/js/components/ascribe_buttons/acl_button_list.js b/js/components/ascribe_buttons/acl_button_list.js index b9139979..e87a6407 100644 --- a/js/components/ascribe_buttons/acl_button_list.js +++ b/js/components/ascribe_buttons/acl_button_list.js @@ -1,12 +1,15 @@ 'use strict'; -import React from 'react'; +import React from 'react/addons'; import UserActions from '../../actions/user_actions'; import UserStore from '../../stores/user_store'; import AclButton from '../ascribe_buttons/acl_button'; +import { mergeOptions } from '../../utils/general_utils'; + + let AclButtonList = React.createClass({ propTypes: { className: React.PropTypes.string, @@ -15,6 +18,7 @@ let AclButtonList = React.createClass({ React.PropTypes.array ]), availableAcls: React.PropTypes.object, + buttonsStyle: React.PropTypes.object, handleSuccess: React.PropTypes.func, children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), @@ -23,56 +27,97 @@ let AclButtonList = React.createClass({ }, getInitialState() { - return UserStore.getState(); + return mergeOptions( + UserStore.getState(), + { + buttonListSize: 0 + } + ); }, componentDidMount() { UserStore.listen(this.onChange); UserActions.fetchCurrentUser(); + + window.addEventListener('resize', this.handleResize); + window.dispatchEvent(new Event('resize')); + }, + + componentDidUpdate(prevProps) { + if(prevProps.availableAcls && prevProps.availableAcls !== this.props.availableAcls) { + window.dispatchEvent(new Event('resize')); + } }, componentWillUnmount() { UserStore.unlisten(this.onChange); + + window.removeEventListener('resize', this.handleResize); + }, + + handleResize() { + this.setState({ + buttonListSize: this.refs.buttonList.getDOMNode().offsetWidth + }); }, onChange(state) { this.setState(state); }, + renderChildren() { + const { children } = this.props; + const { buttonListSize } = this.state; + + return React.Children.map(children, (child) => { + return React.addons.cloneWithProps(child, { buttonListSize }); + }); + }, + render() { + const { className, + buttonsStyle, + availableAcls, + editions, + handleSuccess } = this.props; + + const { currentUser } = this.state; + return ( -
- - - - - - {this.props.children} +
+ + + + + + + {this.renderChildren()} +
); } diff --git a/js/components/ascribe_buttons/acl_information.js b/js/components/ascribe_buttons/acl_information.js new file mode 100644 index 00000000..8d412e02 --- /dev/null +++ b/js/components/ascribe_buttons/acl_information.js @@ -0,0 +1,133 @@ +'use strict'; + +import React from 'react'; +import classnames from 'classnames'; + +import { InformationTexts } from '../../constants/information_text'; +import { replaceSubstringAtIndex, sanitize, intersectLists } from '../../utils/general_utils'; +import { getLangText } from '../../utils/lang_utils'; + + +let AclInformation = React.createClass({ + propTypes: { + verbs: React.PropTypes.arrayOf(React.PropTypes.string), + aim: React.PropTypes.string.isRequired, + aclObject: React.PropTypes.object, + + // Must be inserted from the outside + buttonListSize: React.PropTypes.number.isRequired + }, + + getDefaultProps() { + return { + buttonListSize: 400 + }; + }, + + getInitialState() { + return { isVisible: false }; + }, + + onOff() { + if(!this.state.isVisible) { + this.setState({ isVisible: true }); + } + else { + this.setState({ isVisible: false }); + } + }, + + getInfoText(title, info, example){ + let aim = this.props.aim; + + if(aim) { + if(aim === 'form') { + return ( +

+ + {replaceSubstringAtIndex(info.slice(2), 's ', ' ')} + + + {' ' + example} + +

+ ); + } + else if(aim === 'button') { + return ( +

+ + {title} + + + {info + ' '} + + + {example} + +

+ ); + } + } + else { + console.log('Aim is required when you want to place information text'); + } + }, + + produceInformationBlock() { + const { titles, informationSentences, exampleSentences } = InformationTexts; + const { verbs, aim } = this.props; + + const availableInformations = intersectLists(verbs, Object.keys(titles)); + + // sorting is not needed, as `this.props.verbs` takes care of sorting already + // So we assume a user of `AclInformationButton` puts an ordered version of + // `verbs` into `propTypes` + let verbsToDisplay = []; + + + if(aim === 'form' && availableInformations.length > 0) { + verbsToDisplay = verbsToDisplay.concat(verbs); + } else if(aim === 'button' && this.props.aclObject) { + const { aclObject } = this.props; + const sanitizedAclObject = sanitize(aclObject, (val) => !val); + verbsToDisplay = verbsToDisplay.concat(intersectLists(verbs, Object.keys(sanitizedAclObject))); + } + + return verbsToDisplay.map((verb) => { + return this.getInfoText(getLangText(titles[verb]), getLangText(informationSentences[verb]), getLangText(exampleSentences[verb])); + }); + }, + + getButton() { + return this.props.aim === 'button' ? + ; + btnDelete = ; } else if(availableAcls.acl_unshare){ @@ -57,7 +57,7 @@ let DeleteButton = React.createClass({ title = getLangText('Remove Piece from Collection'); } - btnDelete = ; + btnDelete = ; } else { return null; diff --git a/js/components/ascribe_detail/detail_property.js b/js/components/ascribe_detail/detail_property.js index 12054996..8b0f50b5 100644 --- a/js/components/ascribe_detail/detail_property.js +++ b/js/components/ascribe_detail/detail_property.js @@ -2,6 +2,7 @@ import React from 'react'; + let DetailProperty = React.createClass({ propTypes: { label: React.PropTypes.string, @@ -23,14 +24,19 @@ let DetailProperty = React.createClass({ getDefaultProps() { return { separator: '', - labelClassName: 'col-xs-3 col-sm-3 col-md-2 col-lg-2 col-xs-height col-bottom ascribe-detail-property-label', + labelClassName: 'col-xs-3 col-sm-3 col-md-2 col-lg-2 col-xs-height 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' }; }, render() { - let value = this.props.value; let styles = {}; + const { labelClassName, + label, + separator, + valueClassName, + children, + value } = this.props; if(this.props.ellipsis) { styles = { @@ -40,30 +46,16 @@ let DetailProperty = React.createClass({ }; } - - if (this.props.children){ - value = ( -
-
- { this.props.value } -
-
- { this.props.children } -
-
); - } return (
-
- { this.props.label } { this.props.separator} +
+ {label} {separator}
- {value} + {children || value}
diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index bcd6743c..80bf81d5 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -25,11 +25,11 @@ import LicenseDetail from './license_detail'; import FurtherDetails from './further_details'; import EditionActionPanel from './edition_action_panel'; +import AclProxy from '../acl_proxy'; import Note from './note'; import ApiUrls from '../../constants/api_urls'; -import AppConstants from '../../constants/application_constants'; import AscribeSpinner from '../ascribe_spinner'; import { getLangText } from '../../utils/lang_utils'; @@ -95,7 +95,7 @@ let Edition = React.createClass({

{this.props.edition.title}

- +
- - {this.getStatus()} - + + + + +
); diff --git a/js/components/ascribe_detail/edition_action_panel.js b/js/components/ascribe_detail/edition_action_panel.js index 3d6d3043..d0f16103 100644 --- a/js/components/ascribe_detail/edition_action_panel.js +++ b/js/components/ascribe_detail/edition_action_panel.js @@ -22,6 +22,8 @@ import DeleteButton from '../ascribe_buttons/delete_button'; import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationActions from '../../actions/global_notification_actions'; +import AclInformation from '../ascribe_buttons/acl_information'; + import AclProxy from '../acl_proxy'; import ApiUrls from '../../constants/api_urls'; @@ -73,7 +75,7 @@ let EditionActionPanel = React.createClass({ let notification = new GlobalNotificationModel(response.notification, 'success'); GlobalNotificationActions.appendGlobalNotification(notification); - this.history.pushState('/collection'); + this.history.pushState(null, '/collection'); }, refreshCollection() { @@ -111,7 +113,7 @@ let EditionActionPanel = React.createClass({ @@ -131,7 +133,7 @@ let EditionActionPanel = React.createClass({ value={edition.bitcoin_id} readOnly />
- @@ -168,6 +170,10 @@ let EditionActionPanel = React.createClass({ + diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index 879f7b32..554903c3 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -27,6 +27,9 @@ import CreateEditionsForm from '../ascribe_forms/create_editions_form'; import CreateEditionsButton from '../ascribe_buttons/create_editions_button'; import DeleteButton from '../ascribe_buttons/delete_button'; +import AclInformation from '../ascribe_buttons/acl_information'; +import AclProxy from '../acl_proxy'; + import ListRequestActions from '../ascribe_forms/list_form_request_actions'; import GlobalNotificationModel from '../../models/global_notification_model'; @@ -187,33 +190,41 @@ let PieceContainer = React.createClass({ }, getActions() { - if (this.state.piece && - this.state.piece.notifications && - this.state.piece.notifications.length > 0) { + const { piece, currentUser } = this.state; + + if (piece && piece.notifications && piece.notifications.length > 0) { return ( ); - } - else { + notifications={piece.notifications}/>); + } else { return ( - - - - + + + + + + + + + ); } }, @@ -232,7 +243,7 @@ let PieceContainer = React.createClass({

{this.state.piece.title}

- + {this.state.piece.num_editions > 0 ? : null}
diff --git a/js/components/ascribe_forms/form.js b/js/components/ascribe_forms/form.js index c5f60b76..fe15f537 100644 --- a/js/components/ascribe_forms/form.js +++ b/js/components/ascribe_forms/form.js @@ -288,10 +288,8 @@ let Form = React.createClass({ {this.renderChildren()} {this.getButtons()} - ); } }); - export default Form; diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index 9617acd4..db18ba4f 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -9,6 +9,9 @@ import Property from './property'; import InputTextAreaToggable from './input_textarea_toggable'; import AscribeSpinner from '../ascribe_spinner'; + +import AclInformation from '../ascribe_buttons/acl_information'; + import { getLangText } from '../../utils/lang_utils.js'; let ConsignForm = React.createClass({ @@ -47,6 +50,7 @@ let ConsignForm = React.createClass({

}> + }> +

{getLangText('Are you sure you would like to permanently delete this edition')}?

{getLangText('This is an irrevocable action%s', '.')}

diff --git a/js/components/ascribe_forms/form_delete_piece.js b/js/components/ascribe_forms/form_delete_piece.js index 4b0c9e39..ee066d3f 100644 --- a/js/components/ascribe_forms/form_delete_piece.js +++ b/js/components/ascribe_forms/form_delete_piece.js @@ -4,6 +4,8 @@ import React from 'react'; import Form from '../ascribe_forms/form'; +import AclInformation from '../ascribe_buttons/acl_information'; + import ApiUrls from '../../constants/api_urls'; import AscribeSpinner from '../ascribe_spinner'; @@ -51,6 +53,7 @@ let PieceDeleteForm = React.createClass({

}> +

{getLangText('Are you sure you would like to permanently delete this piece')}?

{getLangText('This is an irrevocable action%s', '.')}

diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js index 919b6118..d6102f14 100644 --- a/js/components/ascribe_forms/form_loan.js +++ b/js/components/ascribe_forms/form_loan.js @@ -19,7 +19,7 @@ import AscribeSpinner from '../ascribe_spinner'; import { mergeOptions } from '../../utils/general_utils'; import { getLangText } from '../../utils/lang_utils'; - +import AclInformation from '../ascribe_buttons/acl_information'; let LoanForm = React.createClass({ propTypes: { @@ -232,6 +232,7 @@ let LoanForm = React.createClass({

{this.props.loanHeading}

+

}> + diff --git a/js/components/ascribe_forms/form_transfer.js b/js/components/ascribe_forms/form_transfer.js index 010c4829..3fb95ff6 100644 --- a/js/components/ascribe_forms/form_transfer.js +++ b/js/components/ascribe_forms/form_transfer.js @@ -9,6 +9,8 @@ import Form from './form'; import Property from './property'; import InputTextAreaToggable from './input_textarea_toggable'; +import AclInformation from '../ascribe_buttons/acl_information'; + import AscribeSpinner from '../ascribe_spinner'; import { getLangText } from '../../utils/lang_utils.js'; @@ -52,6 +54,7 @@ let TransferForm = React.createClass({

}> + diff --git a/js/components/ascribe_modal/modal_wrapper.js b/js/components/ascribe_modal/modal_wrapper.js index f00eee9e..5c3ce742 100644 --- a/js/components/ascribe_modal/modal_wrapper.js +++ b/js/components/ascribe_modal/modal_wrapper.js @@ -65,7 +65,7 @@ let ModalWrapper = React.createClass({ {this.props.title} -
+
{this.renderChildren()}
diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js index 16886def..38de2af6 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js @@ -7,7 +7,7 @@ import DropdownButton from 'react-bootstrap/lib/DropdownButton'; import { getLangText } from '../../utils/lang_utils.js'; -let PieceListToolbarFilterWidgetFilter = React.createClass({ +let PieceListToolbarFilterWidget = React.createClass({ propTypes: { filterParams: React.PropTypes.arrayOf( React.PropTypes.shape({ @@ -83,6 +83,7 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({ return ( {/* We iterate over filterParams, to receive the label and then for each @@ -139,4 +140,4 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({ } }); -export default PieceListToolbarFilterWidgetFilter; \ No newline at end of file +export default PieceListToolbarFilterWidget; \ No newline at end of file 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 index a3615aec..c38144b0 100644 --- 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 @@ -54,6 +54,7 @@ let PieceListToolbarOrderWidget = React.createClass({ return (
  • @@ -72,7 +73,7 @@ let PieceListToolbarOrderWidget = React.createClass({ -1} />
  • diff --git a/js/components/ascribe_spinner.js b/js/components/ascribe_spinner.js index ecdf641b..e1daf5b2 100644 --- a/js/components/ascribe_spinner.js +++ b/js/components/ascribe_spinner.js @@ -20,12 +20,13 @@ let AscribeSpinner = React.createClass({ render() { return ( -
    -
    -
    A
    +
    +
    +
    A
    ); } diff --git a/js/components/header.js b/js/components/header.js index 042d2b1c..51f91318 100644 --- a/js/components/header.js +++ b/js/components/header.js @@ -2,6 +2,8 @@ import React from 'react'; +import { Link } from 'react-router'; + import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; import CollapsibleNav from 'react-bootstrap/lib/CollapsibleNav'; @@ -29,7 +31,7 @@ import NavRoutesLinks from './nav_routes_links'; import { mergeOptions } from '../utils/general_utils'; import { getLangText } from '../utils/lang_utils'; -import {constructHead} from '../utils/head_setter'; +import { constructHead } from '../utils/dom_utils'; let Header = React.createClass({ @@ -71,12 +73,16 @@ let Header = React.createClass({ } if (whitelabel.subdomain && whitelabel.subdomain !== 'www' && whitelabel.logo){ - return (); + return ( + + Whitelabel brand + + ); } return ( - + ); }, @@ -201,7 +207,7 @@ let Header = React.createClass({ {this.getPoweredBy()} diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 528ee409..425247da 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -25,8 +25,6 @@ import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar'; import AscribeSpinner from './ascribe_spinner'; -import AppConstants from '../constants/application_constants'; - import { getAvailableAcls } from '../utils/acl_utils'; import { mergeOptions } from '../utils/general_utils'; import { getLangText } from '../utils/lang_utils'; 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 index abda8a1b..caef504b 100644 --- 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 @@ -182,7 +182,7 @@ let AccordionListItemPrize = React.createClass({ artistName={artistName} subsubheading={
    - {this.props.content.date_created.split('-')[0]} + {new Date(this.props.content.date_created).getFullYear()}
    } buttons={this.getPrizeButtons()} badge={this.getPrizeBadge()}> 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 a5f9838f..07e84b0e 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 @@ -141,7 +141,7 @@ let PieceContainer = React.createClass({

    {this.state.piece.title}

    - + {artistEmail} {this.getActions()}
    diff --git a/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js b/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js index 8a38f02e..5644b5b0 100644 --- a/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js +++ b/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js @@ -39,7 +39,7 @@ let WalletPieceContainer = React.createClass({

    {this.props.piece.title}

    - +
    } diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js b/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js index ae96db98..755e550b 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js @@ -100,7 +100,7 @@ let CylandAccordionListItem = React.createClass({ piece={this.props.content} subsubheading={
    - {this.props.content.date_created.split('-')[0]} + {new Date(this.props.content.date_created).getFullYear()}
    } buttons={this.getSubmitButtons()}> {this.props.children} diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js index 8a6c38b5..63085fc9 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js @@ -3,7 +3,6 @@ import React from 'react'; import { History } from 'react-router'; - import WhitelabelActions from '../../../../../actions/whitelabel_actions'; import WhitelabelStore from '../../../../../stores/whitelabel_store'; @@ -14,10 +13,13 @@ import LinkContainer from 'react-router-bootstrap/lib/LinkContainer'; import UserStore from '../../../../../stores/user_store'; import UserActions from '../../../../../actions/user_actions'; +import AscribeSpinner from '../../../../ascribe_spinner'; + import { mergeOptions } from '../../../../../utils/general_utils'; import { getLangText } from '../../../../../utils/lang_utils'; import { setDocumentTitle } from '../../../../../utils/dom_utils'; + let CylandLanding = React.createClass({ mixins: [History], @@ -61,10 +63,9 @@ let CylandLanding = React.createClass({
    - {getLangText('Submissions to Cyland Archive are powered by')} + {getLangText('Submissions to Cyland Archive are powered by') + ' '} - ascribe - +
    diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js index 00e7f318..7445eb36 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js @@ -106,7 +106,7 @@ let IkonotvAccordionListItem = React.createClass({ piece={this.props.content} subsubheading={
    - {this.props.content.date_created.split('-')[0]} + {new Date(this.props.content.date_created).getFullYear()}
    } buttons={this.getSubmitButtons()}> {this.props.children} diff --git a/js/constants/information_text.js b/js/constants/information_text.js new file mode 100644 index 00000000..442e481e --- /dev/null +++ b/js/constants/information_text.js @@ -0,0 +1,33 @@ +'use strict'; + +export const InformationTexts = { + 'titles': { + 'acl_consign': 'CONSIGN', + 'acl_loan': 'LOAN', + 'acl_share': 'SHARE', + 'acl_delete': 'DELETE', + 'acl_create_editions': 'CREATE EDITIONS', + 'acl_unconsign': 'UNCONSIGN', + 'acl_request_unconsign': 'REQUEST UNCONSIGN' + }, + 'informationSentences': { + 'acl_consign': ' - Lets someone represent you in dealing with the work, under the terms you agree to.', + 'acl_loan': ' - Lets someone use or put the Work on display for a limited amount of time.', + 'acl_share': ' - Lets someone view the Work or Edition, but does not give rights to publish or display it.', + 'acl_delete': ' - Removes the Work from your Wallet. Note that the previous registration and transfer ' + + 'history will still exist on the blockchain and cannot be deleted.', + 'acl_create_editions': ' Lets the artist set a fixed number of editions of a work which can then be transferred, guaranteeing each edition is authentic and from the artist.', + 'acl_unconsign': 'Ends the consignment agreement between the owner and a consignee.', + 'acl_request_unconsign': 'Lets the owner ask the consignee to confirm that they will no longer manage the work.' + }, + 'exampleSentences': { + 'acl_consign': '(e.g. an artist Consigns 10 Editions of her new Work to a gallery ' + + 'so the gallery can sell them on her behalf, under the terms the artist and the gallery have agreed to)', + 'acl_loan': '(e.g. a collector Loans a Work to a gallery for one month for display in the gallery\'s show)', + 'acl_share': '(e.g. a photographer Shares proofs of a graduation photo with the graduate\'s grandparents)', + 'acl_delete': '(e.g. an artist uploaded the wrong file and doesn\'t want it cluttering his Wallet, so he Deletes it)', + 'acl_create_editions': '(e.g. A company commissions a visual artists to create three limited edition prints for a giveaway)', + 'acl_unconsign': '(e.g. An artist regains full control over their work and releases the consignee of any rights or responsibilities)', + 'acl_request_unconsign': '(e.g. An artist submits an unconsign request to a gallery after his exhibition ends, as per their agreement)' + } +}; \ No newline at end of file diff --git a/js/utils/acl_utils.js b/js/utils/acl_utils.js index 170d7aec..dd39a380 100644 --- a/js/utils/acl_utils.js +++ b/js/utils/acl_utils.js @@ -1,10 +1,6 @@ 'use strict'; -import { sanitize } from './general_utils'; - -function intersectAcls(a, b) { - return a.filter((val) => b.indexOf(val) > -1); -} +import { sanitize, intersectLists } from './general_utils'; export function getAvailableAcls(editions, filterFn) { let availableAcls = []; @@ -37,13 +33,15 @@ export function getAvailableAcls(editions, filterFn) { }); // If no edition has been selected, availableActions is empty - // If only one edition has been selected, their actions are available - // If more than one editions have been selected, their acl properties are intersected + // If only one edition has been selected, its actions are available + // If more than one editions have been selected, intersect all their acl properties if (editionsCopy.length >= 1) { availableAcls = editionsCopy[0].acl; - } else if (editionsCopy.length >= 2) { - for (let i = 1; i < editionsCopy.length; i++) { - availableAcls = intersectAcls(availableAcls, editionsCopy[i].acl); + + if (editionsCopy.length >= 2) { + for (let i = 1; i < editionsCopy.length; i++) { + availableAcls = intersectLists(availableAcls, editionsCopy[i].acl); + } } } @@ -53,6 +51,5 @@ export function getAvailableAcls(editions, filterFn) { availableAclsObj[availableAcls[i]] = true; } - return availableAclsObj; } diff --git a/js/utils/dom_utils.js b/js/utils/dom_utils.js index c20e1009..d009f90f 100644 --- a/js/utils/dom_utils.js +++ b/js/utils/dom_utils.js @@ -1,9 +1,45 @@ 'use strict'; - /** * Set the title in the browser window. */ export function setDocumentTitle(title) { document.title = title; } + +/** + * @param {string} elementType: string, is the type of the element, such as link, meta, etc. + * @param {string} elementId id of the element + * @param {object} elementAttributes: hash table containing the attributes of the relevant element + */ +function constructHeadElement(elementType, elementId, elementAttributes) { + let head = (document.head || document.getElementsByTagName('head')[0]); + let element = document.createElement(elementType); + let oldElement = document.getElementById(elementId); + element.setAttribute('id', elementId); + for (let k in elementAttributes){ + try { + element.setAttribute(k, elementAttributes[k]); + } + catch(e){ + console.warn(e.message); + } + } + if (oldElement) { + head.removeChild(oldElement); + } + head.appendChild(element); +} + +/** + * Accepts a dictionary of dictionaries which comprises a part or all of html head part + * @param {object} headObject {link : {id1: {rel: ... }}} + */ +export function constructHead(headObject){ + for (let k in headObject){ + let favicons = headObject[k]; + for (let f in favicons){ + constructHeadElement(k, f, favicons[f]); + } + } +} \ No newline at end of file diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js index cc1d22dd..1aea3cd9 100644 --- a/js/utils/general_utils.js +++ b/js/utils/general_utils.js @@ -222,6 +222,16 @@ export function truncateTextAtCharIndex(text, charIndex, replacement = '...') { return truncatedText; } +/** + * @param index, int, the starting index of the substring to be replaced + * @param character, substring to be replaced + * @returns {string} + */ +export function replaceSubstringAtIndex(baseString, substrToReplace, stringToBePut) { + let index = baseString.indexOf(substrToReplace); + return baseString.substr(0, index) + stringToBePut + baseString.substr(index + substrToReplace.length); +} + /** * Extracts the user's subdomain from the browser's window. * If no subdomain is found (for example on a naked domain), the default "www" is just assumed. @@ -232,3 +242,13 @@ export function getSubdomain() { let tokens = host.split('.'); return tokens.length > 2 ? tokens[0] : 'www'; } + +/** + * Takes two lists and returns their intersection as a list + * @param {Array} a + * @param {Array} b + * @return {[Array]} Intersected list of a and b + */ +export function intersectLists(a, b) { + return a.filter((val) => b.indexOf(val) > -1); +} diff --git a/js/utils/head_setter.js b/js/utils/head_setter.js deleted file mode 100644 index 6ca2c9b0..00000000 --- a/js/utils/head_setter.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -// elementType: string, is the type of the element, such as link, meta, etc. -// elementId id of the element -// elementAttributes: hash table containing the attributes of the relevant element - -function constructHeadElement(elementType, elementId, elementAttributes) { - let head = (document.head || document.getElementsByTagName('head')[0]); - let element = document.createElement(elementType); - let oldElement = document.getElementById(elementId); - element.setAttribute('id', elementId); - for (let k in elementAttributes){ - try { - element.setAttribute(k, elementAttributes[k]); - } - catch(e){ - console.warn(e.message); - continue; - } - } - if (oldElement) { - head.removeChild(oldElement); - } - head.appendChild(element); -} - -// Accepts a dictionary of dictionaries which comprises a part or all of html head part -// {link : {id1: {rel: ... }}} -// traverses a tree of depth 3 (no backtracking) -export function constructHead(headObject){ - for (let k in headObject){ - let favicons = headObject[k]; - for (let f in favicons){ - constructHeadElement(k, f, favicons[f]); - } - } -} diff --git a/sass/ascribe_acl_information.scss b/sass/ascribe_acl_information.scss new file mode 100644 index 00000000..063c8ae6 --- /dev/null +++ b/sass/ascribe_acl_information.scss @@ -0,0 +1,25 @@ +.acl-information-dropdown-list { + text-align: justify; + padding: .5em .5em .5em 0; + + p { + margin: 0 .5em 1em 0; + line-height: 1.2; + } + + span { + font-size: 13px; + } + + .title { + color: $ascribe-dark-blue; + } + + .info { + color: #212121; + } + + .example { + color: #616161; + } +} \ No newline at end of file diff --git a/sass/ascribe_custom_style.scss b/sass/ascribe_custom_style.scss index 51281d91..05d39027 100644 --- a/sass/ascribe_custom_style.scss +++ b/sass/ascribe_custom_style.scss @@ -91,12 +91,27 @@ hr { } } - .navbar-brand, - .navbar-brand:hover { + .navbar-brand { font-size: 23px; padding: 12px 15px; - color: $ascribe--nav-fg-prim-color; + + .icon-ascribe-logo { + color: $ascribe--nav-fg-prim-color; + + &:hover { + color: $ascribe--nav-fg-sec-color; + text-decoration: none; + } + &:focus { + text-decoration: none; + } + } + + .img-brand { + height: 100%; + } } + .img-brand .navbar-brand { width: 0; height: 0; @@ -327,6 +342,29 @@ fieldset[disabled] .btn-secondary.active { } } +.btn-tertiary { + background-color: transparent; + border-color: transparent; + color: $ascribe-dark-blue; + + &:focus, + &:active:focus, + &:active.focus { + background-color: transparent; + border-color: transparent; + color: $ascribe-dark-blue; + } + + &:hover, + &:active, + &:active:hover, + &.active:hover{ + background-color: $ascribe-pink; + border-color: $ascribe-pink; + color: $ascribe-white; + } +} + .ascribe-piece-list-toolbar-filter-widget button { background-color: transparent; border: 1px solid transparent; diff --git a/sass/ascribe_edition.scss b/sass/ascribe_edition.scss index 9fa30387..195e79e0 100644 --- a/sass/ascribe_edition.scss +++ b/sass/ascribe_edition.scss @@ -17,8 +17,4 @@ border: 1px solid #CCC; display: table-cell; vertical-align: middle; -} - -.ascribe-button-list { - margin-top: 1em; -} +} \ No newline at end of file diff --git a/sass/lib/buttons.scss b/sass/lib/buttons.scss index e69de29b..68a124f9 100644 --- a/sass/lib/buttons.scss +++ b/sass/lib/buttons.scss @@ -0,0 +1,9 @@ +.btn-transparent { + color: black; + background-color: transparent; + + &:hover, &:active, &:focus { + color:#424242; + outline: none; + } +} \ No newline at end of file diff --git a/sass/lib/modals.scss b/sass/lib/modals.scss new file mode 100644 index 00000000..20a720c1 --- /dev/null +++ b/sass/lib/modals.scss @@ -0,0 +1,8 @@ +.modal-body { + padding-top:0; +} + +.modal-header { + padding: 15px 15px 0 15px; + border-bottom: none; +} \ No newline at end of file diff --git a/sass/main.scss b/sass/main.scss index 4b9e0f71..337dd32e 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -35,6 +35,9 @@ $BASE_URL: '<%= BASE_URL %>'; @import 'ascribe_form'; @import 'ascribe_panel'; @import 'ascribe_collapsible'; +@import 'ascribe_acl_information'; +@import 'lib/buttons'; +@import 'lib/modals'; @import 'ascribe_custom_style'; @import 'ascribe_spinner'; @@ -155,6 +158,7 @@ hr { } .ascribe-detail-property-label { + vertical-align: top; font-size: .8em; } diff --git a/sass/variables.scss b/sass/variables.scss index ccd48864..ac93a2fb 100644 --- a/sass/variables.scss +++ b/sass/variables.scss @@ -613,7 +613,7 @@ $modal-header-border-color: #e5e5e5 !default; $modal-footer-border-color: $modal-header-border-color !default; $modal-lg: 900px !default; -$modal-md: 600px !default; +$modal-md: 500px !default; $modal-sm: 300px !default; diff --git a/sass/whitelabel/prize/sluice/sluice_custom_style.scss b/sass/whitelabel/prize/sluice/sluice_custom_style.scss index e2ceeeb3..4cfb7c82 100644 --- a/sass/whitelabel/prize/sluice/sluice_custom_style.scss +++ b/sass/whitelabel/prize/sluice/sluice_custom_style.scss @@ -251,3 +251,7 @@ $sluice--button-color: $sluice--nav-fg-prim-color; .client--sluice .ascribe-progress-bar > .progress-bar { background-color: $sluice--button-color; } + +.client--sluice .acl-information-dropdown-list .title { + color: $sluice--button-color; +} \ No newline at end of file diff --git a/sass/whitelabel/wallet/cc/cc_custom_style.scss b/sass/whitelabel/wallet/cc/cc_custom_style.scss index 238937e7..44cb0dd1 100644 --- a/sass/whitelabel/wallet/cc/cc_custom_style.scss +++ b/sass/whitelabel/wallet/cc/cc_custom_style.scss @@ -204,3 +204,7 @@ $cc--button-color: $cc--nav-fg-prim-color; .client--cc .ascribe-progress-bar > .progress-bar { background-color: $cc--button-color; } + +.client--cc .acl-information-dropdown-list .title { + color: $cc--button-color; +} \ No newline at end of file diff --git a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss index c33e247b..eaf45621 100644 --- a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss +++ b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss @@ -179,3 +179,7 @@ $cyland--button-color: $cyland--nav-fg-prim-color; .client--cyland .ascribe-progress-bar > .progress-bar { background-color: $cyland--button-color; } + +.client--cyland .acl-information-dropdown-list .title { + color: $cyland--button-color; +} \ No newline at end of file diff --git a/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss b/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss index 5fc9220b..52affdaf 100644 --- a/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss +++ b/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss @@ -521,3 +521,7 @@ $ikono--font: 'Helvetica Neue', 'Helvetica', sans-serif !important; .client--ikonotv .ascribe-progress-bar > .progress-bar { background-color: $ikono--button-color; } + +.client--ikonotv .acl-information-dropdown-list .title { + color: $ikono--button-color; +} \ No newline at end of file From 3a8f91e29ef16e390b6f326357ef05a345a7b74a Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 3 Nov 2015 10:49:06 +0100 Subject: [PATCH 018/115] Change required prop for AD1251 changes --- .../lumenus/lumenus_forms/lumenus_additional_data_form.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js index 3df03fd8..e08a8bc7 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js @@ -134,7 +134,7 @@ let LumenusAdditionalDataForm = React.createClass({ rows={1} defaultValue={piece.extra_data.artist_bio} placeholder={getLangText('Enter a biography of the artist...')} - required="required"/> + required />
    + required /> + required /> + required /> ); From f7259a8ab3784cb74ac2d71e710cd6ef90ee917c Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 3 Nov 2015 11:13:32 +0100 Subject: [PATCH 019/115] Merge with AD-1255 --- js/components/ascribe_buttons/acl_button.js | 174 ------------------ .../ascribe_buttons/acl_button_list.js | 45 +++-- .../ascribe_buttons/acls/acl_button.js | 83 +++++++++ .../ascribe_buttons/acls/consign_button.js | 27 +++ .../ascribe_buttons/acls/loan_button.js | 27 +++ .../acls/loan_request_button.js | 24 +++ .../ascribe_buttons/acls/share_button.js | 24 +++ .../ascribe_buttons/acls/transfer_button.js | 24 +++ .../ascribe_buttons/acls/unconsign_button.js | 24 +++ .../ascribe_detail/edition_action_panel.js | 2 +- .../ascribe_detail/piece_container.js | 2 +- .../ascribe_forms/acl_form_factory.js | 128 +++++++++++++ .../ascribe_forms/form_request_action.js | 70 ++++--- .../ascribe_detail/wallet_action_panel.js | 4 +- js/utils/form_utils.js | 16 ++ js/utils/general_utils.js | 39 +++- js/utils/lang_utils.js | 6 +- js/utils/requests.js | 8 +- 18 files changed, 474 insertions(+), 253 deletions(-) delete mode 100644 js/components/ascribe_buttons/acl_button.js create mode 100644 js/components/ascribe_buttons/acls/acl_button.js create mode 100644 js/components/ascribe_buttons/acls/consign_button.js create mode 100644 js/components/ascribe_buttons/acls/loan_button.js create mode 100644 js/components/ascribe_buttons/acls/loan_request_button.js create mode 100644 js/components/ascribe_buttons/acls/share_button.js create mode 100644 js/components/ascribe_buttons/acls/transfer_button.js create mode 100644 js/components/ascribe_buttons/acls/unconsign_button.js create mode 100644 js/components/ascribe_forms/acl_form_factory.js diff --git a/js/components/ascribe_buttons/acl_button.js b/js/components/ascribe_buttons/acl_button.js deleted file mode 100644 index 5197a744..00000000 --- a/js/components/ascribe_buttons/acl_button.js +++ /dev/null @@ -1,174 +0,0 @@ -'use strict'; - -import React from 'react'; - -import ConsignForm from '../ascribe_forms/form_consign'; -import UnConsignForm from '../ascribe_forms/form_unconsign'; -import TransferForm from '../ascribe_forms/form_transfer'; -import LoanForm from '../ascribe_forms/form_loan'; -import LoanRequestAnswerForm from '../ascribe_forms/form_loan_request_answer'; -import ShareForm from '../ascribe_forms/form_share_email'; -import ModalWrapper from '../ascribe_modal/modal_wrapper'; -import AppConstants from '../../constants/application_constants'; - -import GlobalNotificationModel from '../../models/global_notification_model'; -import GlobalNotificationActions from '../../actions/global_notification_actions'; - -import ApiUrls from '../../constants/api_urls'; - -import { getAclFormMessage, getAclFormDataId } from '../../utils/form_utils'; -import { getLangText } from '../../utils/lang_utils'; - -let AclButton = React.createClass({ - propTypes: { - action: React.PropTypes.oneOf(AppConstants.aclList).isRequired, - availableAcls: React.PropTypes.object.isRequired, - pieceOrEditions: React.PropTypes.oneOfType([ - React.PropTypes.object, - React.PropTypes.array - ]).isRequired, - currentUser: React.PropTypes.object, - buttonAcceptName: React.PropTypes.string, - buttonAcceptClassName: React.PropTypes.string, - email: React.PropTypes.string, - handleSuccess: React.PropTypes.func.isRequired, - className: React.PropTypes.string - }, - - isPiece() { - return this.props.pieceOrEditions.constructor !== Array; - }, - - actionProperties() { - let message = getAclFormMessage({ - aclName: this.props.action, - entities: this.props.pieceOrEditions, - isPiece: this.isPiece(), - senderName: this.props.currentUser.username - }); - - if (this.props.action === 'acl_consign') { - return { - title: getLangText('Consign artwork'), - tooltip: getLangText('Have someone else sell the artwork'), - form: ( - - ), - handleSuccess: this.showNotification - }; - } else if (this.props.action === 'acl_unconsign') { - return { - title: getLangText('Unconsign artwork'), - tooltip: getLangText('Have the owner manage his sales again'), - form: ( - - ), - handleSuccess: this.showNotification - }; - } else if (this.props.action === 'acl_transfer') { - return { - title: getLangText('Transfer artwork'), - tooltip: getLangText('Transfer the ownership of the artwork'), - form: ( - - ), - handleSuccess: this.showNotification - }; - } else if (this.props.action === 'acl_loan') { - return { - title: getLangText('Loan artwork'), - tooltip: getLangText('Loan your artwork for a limited period of time'), - form: ( - - ), - handleSuccess: this.showNotification - }; - } else if (this.props.action === 'acl_loan_request') { - return { - title: getLangText('Loan artwork'), - tooltip: getLangText('Someone requested you to loan your artwork for a limited period of time'), - form: ( - - ), - handleSuccess: this.showNotification - }; - } else if (this.props.action === 'acl_share') { - return { - title: getLangText('Share artwork'), - tooltip: getLangText('Share the artwork'), - form: ( - - ), - handleSuccess: this.showNotification - }; - } else { - throw new Error('Your specified action did not match a form.'); - } - }, - - showNotification(response) { - this.props.handleSuccess(); - if (response.notification) { - let notification = new GlobalNotificationModel(response.notification, 'success'); - GlobalNotificationActions.appendGlobalNotification(notification); - } - }, - - getFormDataId(){ - return getAclFormDataId(this.isPiece(), this.props.pieceOrEditions); - }, - - // Removes the acl_ prefix and converts to upper case - sanitizeAction() { - if (this.props.buttonAcceptName) { - return this.props.buttonAcceptName; - } - return this.props.action.split('acl_')[1].toUpperCase(); - }, - - render() { - if (this.props.availableAcls) { - let shouldDisplay = this.props.availableAcls[this.props.action]; - let aclProps = this.actionProperties(); - let buttonClassName = this.props.buttonAcceptClassName ? this.props.buttonAcceptClassName : ''; - return ( - - {this.sanitizeAction()} - - } - handleSuccess={aclProps.handleSuccess} - title={aclProps.title}> - {aclProps.form} - - ); - } - return null; - } -}); - -export default AclButton; diff --git a/js/components/ascribe_buttons/acl_button_list.js b/js/components/ascribe_buttons/acl_button_list.js index e87a6407..83495363 100644 --- a/js/components/ascribe_buttons/acl_button_list.js +++ b/js/components/ascribe_buttons/acl_button_list.js @@ -5,21 +5,25 @@ import React from 'react/addons'; import UserActions from '../../actions/user_actions'; import UserStore from '../../stores/user_store'; -import AclButton from '../ascribe_buttons/acl_button'; +import ConsignButton from './acls/consign_button'; +import LoanButton from './acls/loan_button'; +import LoanRequestButton from './acls/loan_request_button'; +import ShareButton from './acls/share_button'; +import TransferButton from './acls/transfer_button'; +import UnconsignButton from './acls/unconsign_button'; import { mergeOptions } from '../../utils/general_utils'; - let AclButtonList = React.createClass({ propTypes: { className: React.PropTypes.string, - editions: React.PropTypes.oneOfType([ + pieceOrEditions: React.PropTypes.oneOfType([ React.PropTypes.object, React.PropTypes.array - ]), - availableAcls: React.PropTypes.object, + ]).isRequired, + availableAcls: React.PropTypes.object.isRequired, buttonsStyle: React.PropTypes.object, - handleSuccess: React.PropTypes.func, + handleSuccess: React.PropTypes.func.isRequired, children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element @@ -78,7 +82,7 @@ let AclButtonList = React.createClass({ const { className, buttonsStyle, availableAcls, - editions, + pieceOrEditions, handleSuccess } = this.props; const { currentUser } = this.state; @@ -86,34 +90,29 @@ let AclButtonList = React.createClass({ return (
    - - - - - {this.renderChildren()} @@ -123,4 +122,4 @@ let AclButtonList = React.createClass({ } }); -export default AclButtonList; \ No newline at end of file +export default AclButtonList; diff --git a/js/components/ascribe_buttons/acls/acl_button.js b/js/components/ascribe_buttons/acls/acl_button.js new file mode 100644 index 00000000..d81a19f4 --- /dev/null +++ b/js/components/ascribe_buttons/acls/acl_button.js @@ -0,0 +1,83 @@ +'use strict'; + +import React from 'react'; +import classNames from 'classnames'; + +import AclProxy from '../../acl_proxy'; + +import AclFormFactory from '../../ascribe_forms/acl_form_factory'; + +import ModalWrapper from '../../ascribe_modal/modal_wrapper'; + +import AppConstants from '../../../constants/application_constants'; + +import GlobalNotificationModel from '../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../actions/global_notification_actions'; + +import ApiUrls from '../../../constants/api_urls'; + +import { getAclFormMessage, getAclFormDataId } from '../../../utils/form_utils'; +import { getLangText } from '../../../utils/lang_utils'; + +let AclButton = React.createClass({ + propTypes: { + action: React.PropTypes.oneOf(AppConstants.aclList).isRequired, + availableAcls: React.PropTypes.object.isRequired, + buttonAcceptName: React.PropTypes.string, + buttonAcceptClassName: React.PropTypes.string, + currentUser: React.PropTypes.object.isRequired, + email: React.PropTypes.string, + pieceOrEditions: React.PropTypes.oneOfType([ + React.PropTypes.object, + React.PropTypes.array + ]).isRequired, + title: React.PropTypes.string, + handleSuccess: React.PropTypes.func.isRequired, + className: React.PropTypes.string + }, + + // Removes the acl_ prefix and converts to upper case + sanitizeAction() { + if (this.props.buttonAcceptName) { + return this.props.buttonAcceptName; + } + return this.props.action.split('acl_')[1].toUpperCase(); + }, + + render() { + const { + action, + availableAcls, + buttonAcceptClassName, + currentUser, + email, + pieceOrEditions, + handleSuccess, + title } = this.props; + + return ( + + + {this.sanitizeAction()} + + } + handleSuccess={handleSuccess} + title={title}> + + + + ); + } +}); + +export default AclButton; diff --git a/js/components/ascribe_buttons/acls/consign_button.js b/js/components/ascribe_buttons/acls/consign_button.js new file mode 100644 index 00000000..6a3759db --- /dev/null +++ b/js/components/ascribe_buttons/acls/consign_button.js @@ -0,0 +1,27 @@ +'use strict'; + +import React from 'react'; + +import AclButton from './acl_button'; + +import { omitFromObject } from '../../../utils/general_utils'; +import { getLangText } from '../../../utils/lang_utils'; + +let ConsignButton = React.createClass({ + propTypes: { + ...omitFromObject(AclButton.propTypes, ['action']), + email: React.PropTypes.string + }, + + render() { + return ( + + ); + } +}); + +export default ConsignButton; diff --git a/js/components/ascribe_buttons/acls/loan_button.js b/js/components/ascribe_buttons/acls/loan_button.js new file mode 100644 index 00000000..283b43eb --- /dev/null +++ b/js/components/ascribe_buttons/acls/loan_button.js @@ -0,0 +1,27 @@ +'use strict'; + +import React from 'react'; + +import AclButton from './acl_button'; + +import { omitFromObject } from '../../../utils/general_utils'; +import { getLangText } from '../../../utils/lang_utils'; + +let LoanButton = React.createClass({ + propTypes: { + ...omitFromObject(AclButton.propTypes, ['action']), + email: React.PropTypes.string + }, + + render() { + return ( + + ); + } +}); + +export default LoanButton; diff --git a/js/components/ascribe_buttons/acls/loan_request_button.js b/js/components/ascribe_buttons/acls/loan_request_button.js new file mode 100644 index 00000000..f4ffe9a4 --- /dev/null +++ b/js/components/ascribe_buttons/acls/loan_request_button.js @@ -0,0 +1,24 @@ +'use strict'; + +import React from 'react'; + +import AclButton from './acl_button'; + +import { omitFromObject } from '../../../utils/general_utils'; +import { getLangText } from '../../../utils/lang_utils'; + +let LoanButton = React.createClass({ + propTypes: omitFromObject(AclButton.propTypes, ['action']), + + render() { + return ( + + ); + } +}); + +export default LoanButton; diff --git a/js/components/ascribe_buttons/acls/share_button.js b/js/components/ascribe_buttons/acls/share_button.js new file mode 100644 index 00000000..4fb914a0 --- /dev/null +++ b/js/components/ascribe_buttons/acls/share_button.js @@ -0,0 +1,24 @@ +'use strict'; + +import React from 'react'; + +import AclButton from './acl_button'; + +import { omitFromObject } from '../../../utils/general_utils'; +import { getLangText } from '../../../utils/lang_utils'; + +let ShareButton = React.createClass({ + propTypes: omitFromObject(AclButton.propTypes, ['action']), + + render() { + return ( + + ); + } +}); + +export default ShareButton; diff --git a/js/components/ascribe_buttons/acls/transfer_button.js b/js/components/ascribe_buttons/acls/transfer_button.js new file mode 100644 index 00000000..e85a81d1 --- /dev/null +++ b/js/components/ascribe_buttons/acls/transfer_button.js @@ -0,0 +1,24 @@ +'use strict'; + +import React from 'react'; + +import AclButton from './acl_button'; + +import { omitFromObject } from '../../../utils/general_utils'; +import { getLangText } from '../../../utils/lang_utils'; + +let TransferButton = React.createClass({ + propTypes: omitFromObject(AclButton.propTypes, ['action']), + + render() { + return ( + + ); + } +}); + +export default TransferButton; diff --git a/js/components/ascribe_buttons/acls/unconsign_button.js b/js/components/ascribe_buttons/acls/unconsign_button.js new file mode 100644 index 00000000..39029c18 --- /dev/null +++ b/js/components/ascribe_buttons/acls/unconsign_button.js @@ -0,0 +1,24 @@ +'use strict'; + +import React from 'react'; + +import AclButton from './acl_button'; + +import { omitFromObject } from '../../../utils/general_utils'; +import { getLangText } from '../../../utils/lang_utils'; + +let UnconsignButton = React.createClass({ + propTypes: omitFromObject(AclButton.propTypes, ['action']), + + render() { + return ( + + ); + } +}); + +export default UnconsignButton; diff --git a/js/components/ascribe_detail/edition_action_panel.js b/js/components/ascribe_detail/edition_action_panel.js index d0f16103..6bd8108d 100644 --- a/js/components/ascribe_detail/edition_action_panel.js +++ b/js/components/ascribe_detail/edition_action_panel.js @@ -115,7 +115,7 @@ let EditionActionPanel = React.createClass({ + ); + } else if (action === 'acl_unconsign') { + return ( + + ); + } else if (action === 'acl_transfer') { + return ( + + ); + } else if (action === 'acl_loan') { + return ( + + ); + } else if (action === 'acl_loan_request') { + return ( + + ); + } else if (action === 'acl_share') { + return ( + + ); + } else { + throw new Error('Your specified action did not match a form.'); + } + } +}); + +export default AclFormFactory; diff --git a/js/components/ascribe_forms/form_request_action.js b/js/components/ascribe_forms/form_request_action.js index b0f3b6c6..d3d1bc71 100644 --- a/js/components/ascribe_forms/form_request_action.js +++ b/js/components/ascribe_forms/form_request_action.js @@ -2,10 +2,13 @@ import React from 'react'; -import AclButton from './../ascribe_buttons/acl_button'; -import ActionPanel from '../ascribe_panel/action_panel'; import Form from './form'; +import LoanRequestButton from '../ascribe_buttons/acls/loan_request_button'; +import UnconsignButton from '../ascribe_buttons/acls/unconsign_button'; + +import ActionPanel from '../ascribe_panel/action_panel'; + import NotificationActions from '../../actions/notification_actions'; import GlobalNotificationModel from '../../models/global_notification_model'; @@ -13,9 +16,9 @@ import GlobalNotificationActions from '../../actions/global_notification_actions import ApiUrls from '../../constants/api_urls'; +import { getAclFormDataId } from '../../utils/form_utils'; import { getLangText } from '../../utils/lang_utils.js'; - let RequestActionForm = React.createClass({ propTypes: { pieceOrEditions: React.PropTypes.oneOfType([ @@ -27,26 +30,26 @@ let RequestActionForm = React.createClass({ handleSuccess: React.PropTypes.func }, - isPiece(){ + isPiece() { return this.props.pieceOrEditions.constructor !== Array; }, getUrls() { let urls = {}; - if (this.props.notifications.action === 'consign'){ + if (this.props.notifications.action === 'consign') { urls.accept = ApiUrls.ownership_consigns_confirm; urls.deny = ApiUrls.ownership_consigns_deny; - } else if (this.props.notifications.action === 'unconsign'){ + } else if (this.props.notifications.action === 'unconsign') { urls.accept = ApiUrls.ownership_unconsigns; urls.deny = ApiUrls.ownership_unconsigns_deny; - } else if (this.props.notifications.action === 'loan' && !this.isPiece()){ + } else if (this.props.notifications.action === 'loan' && !this.isPiece()) { urls.accept = ApiUrls.ownership_loans_confirm; urls.deny = ApiUrls.ownership_loans_deny; - } else if (this.props.notifications.action === 'loan' && this.isPiece()){ + } else if (this.props.notifications.action === 'loan' && this.isPiece()) { urls.accept = ApiUrls.ownership_loans_pieces_confirm; urls.deny = ApiUrls.ownership_loans_pieces_deny; - } else if (this.props.notifications.action === 'loan_request' && this.isPiece()){ + } else if (this.props.notifications.action === 'loan_request' && this.isPiece()) { urls.accept = ApiUrls.ownership_loans_pieces_request_confirm; urls.deny = ApiUrls.ownership_loans_pieces_request_deny; } @@ -54,37 +57,28 @@ let RequestActionForm = React.createClass({ return urls; }, - getFormData(){ - if (this.isPiece()) { - return {piece_id: this.props.pieceOrEditions.id}; - } - else { - return {bitcoin_id: this.props.pieceOrEditions.map(function(edition){ - return edition.bitcoin_id; - }).join()}; - } + getFormData() { + return getAclFormDataId(this.isPiece(), this.props.pieceOrEditions); }, showNotification(option, action, owner) { return () => { - let message = getLangText('You have successfully') + ' ' + option + ' the ' + action + ' request ' + getLangText('from') + ' ' + owner; - - let notifications = new GlobalNotificationModel(message, 'success'); + const message = getLangText('You have successfully %s the %s request from %s', getLangText(option), getLangText(action), owner); + const notifications = new GlobalNotificationModel(message, 'success'); GlobalNotificationActions.appendGlobalNotification(notifications); this.handleSuccess(); - }; }, handleSuccess() { - if (this.isPiece()){ + if (this.isPiece()) { NotificationActions.fetchPieceListNotifications(); - } - else { + } else { NotificationActions.fetchEditionListNotifications(); } - if(this.props.handleSuccess) { + + if (typeof this.props.handleSuccess === 'function') { this.props.handleSuccess(); } }, @@ -98,21 +92,19 @@ let RequestActionForm = React.createClass({ }, getAcceptButtonForm(urls) { - if(this.props.notifications.action === 'unconsign') { + if (this.props.notifications.action === 'unconsign') { return ( - ); - } else if(this.props.notifications.action === 'loan_request') { + } else if (this.props.notifications.action === 'loan_request') { return ( - @@ -140,8 +132,8 @@ let RequestActionForm = React.createClass({ }, getButtonForm() { - let urls = this.getUrls(); - let acceptButtonForm = this.getAcceptButtonForm(urls); + const urls = this.getUrls(); + const acceptButtonForm = this.getAcceptButtonForm(urls); return (
    @@ -150,13 +142,13 @@ let RequestActionForm = React.createClass({ isInline={true} getFormData={this.getFormData} handleSuccess={ - this.showNotification(getLangText('denied'), this.props.notifications.action, this.props.notifications.by) + this.showNotification('denied', this.props.notifications.action, this.props.notifications.by) } className='inline pull-right'> {acceptButtonForm} @@ -168,10 +160,10 @@ let RequestActionForm = React.createClass({ return ( + buttons={this.getButtonForm()} /> ); } }); -export default RequestActionForm; \ No newline at end of file +export default RequestActionForm; diff --git a/js/components/whitelabel/wallet/components/ascribe_detail/wallet_action_panel.js b/js/components/whitelabel/wallet/components/ascribe_detail/wallet_action_panel.js index 178be7da..bd240126 100644 --- a/js/components/whitelabel/wallet/components/ascribe_detail/wallet_action_panel.js +++ b/js/components/whitelabel/wallet/components/ascribe_detail/wallet_action_panel.js @@ -47,7 +47,7 @@ let WalletActionPanel = React.createClass({ { + return filter.indexOf(key) >= 0; + }); + } else if (filter && typeof filter === 'function') { + return filterObjOnFn(obj, filter); + } else { + throw new Error('The given filter is not an array or function. Exclude aborted'); + } } /** diff --git a/js/utils/lang_utils.js b/js/utils/lang_utils.js index f2e2fa14..ee2d292c 100644 --- a/js/utils/lang_utils.js +++ b/js/utils/lang_utils.js @@ -22,15 +22,15 @@ export function getLangText(s, ...args) { let lang = getLang(); try { if(lang in languages) { - return formatText(languages[lang][s], args); + return formatText(languages[lang][s], ...args); } else { // just use the english language - return formatText(languages['en-US'][s], args); + return formatText(languages['en-US'][s], ...args); } } catch(err) { //if(!(s in languages[lang])) { //console.warn('Language-string is not in constants file. Add: "' + s + '" to the "' + lang + '" language file. Defaulting to keyname'); - return formatText(s, args); + return formatText(s, ...args); //} else { // console.error(err); //} diff --git a/js/utils/requests.js b/js/utils/requests.js index 7e9c9a58..43c09458 100644 --- a/js/utils/requests.js +++ b/js/utils/requests.js @@ -6,7 +6,7 @@ import { argsToQueryParams, getCookie } from '../utils/fetch_api_utils'; import AppConstants from '../constants/application_constants'; -import {excludePropFromObject} from '../utils/general_utils'; +import { omitFromObject } from '../utils/general_utils'; class Requests { _merge(defaults, options) { @@ -138,9 +138,9 @@ class Requests { return this.request('delete', newUrl); } - _putOrPost(url, paramsAndBody, method){ - let paramsCopy = this._merge(paramsAndBody); - let params = excludePropFromObject(paramsAndBody, ['body']); + _putOrPost(url, paramsAndBody, method) { + let paramsCopy = Object.assign({}, paramsAndBody); + let params = omitFromObject(paramsAndBody, ['body']); let newUrl = this.prepareUrl(url, params); let body = null; if (paramsCopy && paramsCopy.body) { From 54212627bbab35b1b7ab3b2bda841f8b77c095d3 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 3 Nov 2015 12:10:21 +0100 Subject: [PATCH 020/115] Change lumens buttons for AD-1264 changes --- js/components/piece_list.js | 2 +- .../lumenus_buttons/lumenus_acl_button_list.js | 13 ++++++------- .../lumenus_buttons/lumenus_submit_button.js | 11 +++++------ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 425247da..3099d31a 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -224,7 +224,7 @@ let PieceList = React.createClass({ className="ascribe-piece-list-bulk-modal"> - {this.props.children}
    diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js index 365a5a34..941b3ec0 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js @@ -3,7 +3,7 @@ import React from 'react'; import classNames from 'classnames'; -import AclButton from '../../../../../ascribe_buttons/acl_button'; +import ConsignButton from '../../../../../ascribe_buttons/acls/consign_button'; import WhitelabelActions from '../../../../../../actions/whitelabel_actions'; import WhitelabelStore from '../../../../../../stores/whitelabel_store'; @@ -14,7 +14,7 @@ let LumenusSubmitButton = React.createClass({ propTypes: { availableAcls: React.PropTypes.object.isRequired, currentUser: React.PropTypes.object, - editions: React.PropTypes.array, + pieceOrEditions: React.PropTypes.array, handleSuccess: React.PropTypes.func, className: React.PropTypes.string, }, @@ -37,17 +37,16 @@ let LumenusSubmitButton = React.createClass({ }, render() { - const { availableAcls, currentUser, className, editions, handleSuccess } = this.props; + const { availableAcls, currentUser, className, pieceOrEditions, handleSuccess } = this.props; return ( - ); } From 3929712e9ddbe9fc2efa019739bddd3a6096df2a Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 3 Nov 2015 12:10:43 +0100 Subject: [PATCH 021/115] Prune unnecessary divs from some components --- .../lumenus_edition_container.js | 12 ++++------ .../lumenus_detail/lumenus_piece_container.js | 10 ++++---- .../components/lumenus/lumenus_piece_list.js | 24 +++++++++---------- 3 files changed, 20 insertions(+), 26 deletions(-) diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js index 756735eb..77cca99c 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js @@ -16,13 +16,11 @@ let LumenusEditionContainer = React.createClass({ render() { return ( -
    - -
    + ); } }); diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js index 03fd9d9b..5b7cf8ee 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js @@ -14,12 +14,10 @@ let LumenusPieceContainer = React.createClass({ render() { return ( -
    - -
    + ); } }); diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js index 4bc2485f..14dac214 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js @@ -18,19 +18,17 @@ let LumenusPieceList = React.createClass({ setDocumentTitle(getLangText('Collection')); return ( -
    - -
    + ); } }); From 85eb45b5cd8cb443441e1da40bde06dc1caf604c Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 4 Nov 2015 00:41:12 +0100 Subject: [PATCH 022/115] Add small changes for previous merges that were missed --- js/components/piece_list.js | 2 +- .../lumenus/lumenus_buttons/lumenus_acl_button_list.js | 8 +++++++- .../lumenus_forms/lumenus_additional_data_form.js | 6 ++---- .../wallet/components/lumenus/lumenus_piece_list.js | 6 +++--- js/stores/edition_list_store.js | 2 +- js/utils/requests.js | 10 +++------- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 3099d31a..69f5b23f 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -184,7 +184,7 @@ let PieceList = React.createClass({ this.fetchSelectedPieceEditionList() .forEach((pieceId) => { - EditionListActions.refreshEditionList({pieceId, filterBy: {}}); + EditionListActions.refreshEditionList({pieceId}); }); EditionListActions.clearAllEditionSelections(); }, diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js index b902b93b..76608032 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js @@ -6,6 +6,7 @@ import LumenusSubmitButton from './lumenus_submit_button'; import DeleteButton from '../../../../../ascribe_buttons/delete_button'; import ShareButton from '../../../../../ascribe_buttons/acls/share_button'; +import TransferButton from '../../../../../ascribe_buttons/acls/transfer_button'; import UserActions from '../../../../../../actions/user_actions'; import UserStore from '../../../../../../stores/user_store'; @@ -46,13 +47,18 @@ let LumenusAclButtonList = React.createClass({ + {this.props.children}
    ); diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js index e08a8bc7..3e67c21a 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js @@ -28,8 +28,7 @@ let LumenusAdditionalDataForm = React.createClass({ extra_data: React.PropTypes.object, other_data: React.PropTypes.arrayOf(React.PropTypes.object) }).isRequired, - isInline: React.PropTypes.bool, - location: React.PropTypes.object + isInline: React.PropTypes.bool }, getDefaultProps() { @@ -125,8 +124,7 @@ let LumenusAdditionalDataForm = React.createClass({ setIsUploadReady={this.setIsUploadReady} isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile} pieceId={piece.id} - otherData={piece.other_data} - location={this.props.location}/> + otherData={piece.other_data} /> diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js index 14dac214..ccfb7e1c 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js @@ -22,10 +22,10 @@ let LumenusPieceList = React.createClass({ redirectTo="/register_piece?slide_num=0" bulkModalButtonListType={LumenusAclButtonList} filterParams={[{ - label: getLangText('Show works I have'), + label: getLangText('Show works I can'), items: [{ - key: 'acl_consigned', - label: getLangText('consigned to Lumenus') + key: 'acl_consign', + label: getLangText('consign to Lumenus') }] }]} location={this.props.location}/> diff --git a/js/stores/edition_list_store.js b/js/stores/edition_list_store.js index 4ccada4e..107f9af4 100644 --- a/js/stores/edition_list_store.js +++ b/js/stores/edition_list_store.js @@ -60,7 +60,7 @@ class EditionListStore { * We often just have to refresh the edition list for a certain pieceId, * this method provides exactly that functionality without any side effects */ - onRefreshEditionList({pieceId, filterBy}) { + onRefreshEditionList({pieceId, filterBy = {}}) { // It may happen that the user enters the site logged in already // through /editions // If he then tries to delete a piece/edition and this method is called, diff --git a/js/utils/requests.js b/js/utils/requests.js index 8f015a11..112588e0 100644 --- a/js/utils/requests.js +++ b/js/utils/requests.js @@ -99,8 +99,7 @@ class Requests { return newUrl; } - request(verb, url, options) { - options = options || {}; + request(verb, url, options = {}) { let merged = Object.assign({}, this.httpOptions, options); let csrftoken = getCookie(AppConstants.csrftoken); if (csrftoken) { @@ -128,13 +127,10 @@ class Requests { } _putOrPost(url, paramsAndBody, method) { - let paramsCopy = Object.assign({}, paramsAndBody); let params = omitFromObject(paramsAndBody, ['body']); let newUrl = this.prepareUrl(url, params); - let body = null; - if (paramsCopy && paramsCopy.body) { - body = JSON.stringify(paramsCopy.body); - } + let body = paramsAndBody && paramsAndBody.body ? JSON.stringify(paramsAndBody.body) + : null; return this.request(method, newUrl, { body }); } From adf0d411d67a06331f1b002f663fb50cf4f9426e Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 5 Nov 2015 11:56:17 +0100 Subject: [PATCH 023/115] Fetch piece data in AdditionalDetailForm Although this moves state further down the hierarchy, it allows the AdditionalDataForm to be used more easily. Parent components would otherwise have to have the piece prop carried down through multiple levels or create a makeshift object. --- js/components/ascribe_forms/property.js | 5 +- .../lumenus_edition_container.js | 10 +- .../lumenus_detail/lumenus_further_details.js | 31 +----- .../lumenus_detail/lumenus_piece_container.js | 10 +- .../lumenus_additional_data_form.js | 99 ++++++++++++++----- .../lumenus/lumenus_register_piece.js | 58 +++++------ 6 files changed, 111 insertions(+), 102 deletions(-) diff --git a/js/components/ascribe_forms/property.js b/js/components/ascribe_forms/property.js index 793be538..b4baf9df 100644 --- a/js/components/ascribe_forms/property.js +++ b/js/components/ascribe_forms/property.js @@ -31,7 +31,10 @@ let Property = React.createClass({ footer: React.PropTypes.element, handleChange: React.PropTypes.func, ignoreFocus: React.PropTypes.bool, - name: React.PropTypes.string.isRequired, + name: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number + ]).isRequired, className: React.PropTypes.string, onClick: React.PropTypes.func, diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js index 77cca99c..c81fb0bb 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js @@ -9,18 +9,14 @@ import LumenusAclButtonList from '../lumenus_buttons/lumenus_acl_button_list'; import EditionContainer from '../../../../../ascribe_detail/edition_container'; let LumenusEditionContainer = React.createClass({ - propTypes: { - params: React.PropTypes.object, - location: React.PropTypes.object - }, + propTypes: EditionContainer.propTypes, render() { return ( + furtherDetailsType={LumenusFurtherDetails} /> ); } }); diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js index 17dbad20..79199b68 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js @@ -4,43 +4,20 @@ import React from 'react'; import LumenusAdditionalDataForm from '../lumenus_forms/lumenus_additional_data_form' -import GlobalNotificationModel from '../../../../../../models/global_notification_model'; -import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; - -let FurtherDetails = React.createClass({ +let LumenusFurtherDetails = React.createClass({ propTypes: { pieceId: React.PropTypes.number, - extraData: React.PropTypes.object, - otherData: React.PropTypes.arrayOf(React.PropTypes.object), handleSuccess: React.PropTypes.func, - location: React.PropTypes.object - }, - - showNotification() { - this.props.handleSuccess(); - let notification = new GlobalNotificationModel('Further details updated', 'success'); - GlobalNotificationActions.appendGlobalNotification(notification); }, render() { - const { pieceId, extraData, otherData, handleSuccess, location } = this.props; - - // Instead of grabbing the entire piece from the PieceStore and making this component - // stateful, we can put together a piece for the additional form solely based on the props - const piece = { - id: pieceId, - extra_data: extraData, - other_data: otherData - }; - return ( + showNotification /> ); } }); -export default FurtherDetails; +export default LumenusFurtherDetails; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js index 5b7cf8ee..391a7cb5 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js @@ -7,17 +7,13 @@ import LumenusFurtherDetails from './lumenus_further_details'; import PieceContainer from '../../../../../ascribe_detail/piece_container'; let LumenusPieceContainer = React.createClass({ - propTypes: { - params: React.PropTypes.object, - location: React.PropTypes.object - }, + propTypes: PieceContainer.propTypes, render() { return ( + {...this.props} + furtherDetailsType={LumenusFurtherDetails} /> ); } }); diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js index 3e67c21a..3b69ce81 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js @@ -14,38 +14,70 @@ import GlobalNotificationActions from '../../../../../../actions/global_notifica import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_s3_fine_uploader_utils'; +import PieceActions from '../../../../../../actions/piece_actions'; +import PieceStore from '../../../../../../stores/piece_store'; + import ApiUrls from '../../../../../../constants/api_urls'; import AppConstants from '../../../../../../constants/application_constants'; import requests from '../../../../../../utils/requests'; +import { mergeOptions } from '../../../../../../utils/general_utils'; import { getLangText } from '../../../../../../utils/lang_utils'; let LumenusAdditionalDataForm = React.createClass({ propTypes: { - handleSuccess: React.PropTypes.func, - piece: React.PropTypes.shape({ - id: React.PropTypes.number, - extra_data: React.PropTypes.object, - other_data: React.PropTypes.arrayOf(React.PropTypes.object) - }).isRequired, - isInline: React.PropTypes.bool - }, - - getDefaultProps() { - return { - isInline: false - }; + pieceId: React.PropTypes.oneOfType([ + React.PropTypes.number, + React.PropTypes.string + ]), + isInline: React.PropTypes.bool, + showHeading: React.PropTypes.bool, + showNotification: React.PropTypes.bool, + handleSuccess: React.PropTypes.func }, getInitialState() { - return { - isUploadReady: false - }; + const pieceStore = PieceStore.getState(); + + return mergeOptions( + pieceStore, + { + // Allow the form to be submitted if there's already an additional image uploaded + isUploadReady: this.isUploadReadyOnChange(pieceStore.piece), + forceUpdateKey: 0, + }); }, - handleSuccess() { - let notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000); - GlobalNotificationActions.appendGlobalNotification(notification); + componentDidMount() { + PieceStore.listen(this.onChange); + + // If the Piece store doesn't already have the piece we want loaded, load it + const { pieceId } = this.props; + if (pieceId && this.state.piece.id !== pieceId) { + PieceActions.fetchOne(pieceId); + } + }, + + componentWillUnmount() { + PieceStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + + this.setState({ + // Allow the form to be submitted if the updated piece already has an additional image uploaded + isUploadReady: this.isUploadReadyOnChange(state.piece), + + /** + * Increment the forceUpdateKey to force the form to rerender on each change + * + * THIS IS A HACK TO MAKE SURE THE FORM ALWAYS DISPLAYS THE MOST RECENT STATE + * BECAUSE SOME OF OUR FORM ELEMENTS DON'T UPDATE FROM PROP CHANGES (ie. + * InputTextAreaToggable). + */ + forceUpdateKey: this.state.forceUpdateKey + 1 + }); }, getFormData() { @@ -61,10 +93,23 @@ let LumenusAdditionalDataForm = React.createClass({ return { extradata: extradata, - piece_id: this.props.piece.id + piece_id: this.state.piece.id }; }, + isUploadReadyOnChange(piece) { + return piece && piece.other_data && piece.other_data.length > 0 ? true : false; + }, + + handleSuccessWithNotification() { + if (typeof this.props.handleSuccess === 'function') { + this.props.handleSuccess(); + } + + let notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + uploadStarted() { this.setState({ isUploadReady: false @@ -78,7 +123,8 @@ let LumenusAdditionalDataForm = React.createClass({ }, render() { - let { piece, isInline, handleSuccess } = this.props; + const { isInline, handleSuccess, showHeading, showNotification } = this.props; + const { piece } = this.state; let buttons, spinner, heading; if (!isInline) { @@ -97,13 +143,13 @@ let LumenusAdditionalDataForm = React.createClass({ ); - heading = ( + heading = showHeading ? (

    {getLangText('Provide additional details')}

    - ); + ) : null; } if (piece && piece.id) { @@ -111,8 +157,9 @@ let LumenusAdditionalDataForm = React.createClass({
    @@ -144,11 +191,11 @@ let LumenusAdditionalDataForm = React.createClass({ required /> diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js index 342c4a32..3d43418d 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_register_piece.js @@ -13,7 +13,6 @@ import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece'; import UserStore from '../../../../../stores/user_store'; import UserActions from '../../../../../actions/user_actions'; -import PieceStore from '../../../../../stores/piece_store'; import PieceActions from '../../../../../actions/piece_actions'; import PieceListStore from '../../../../../stores/piece_list_store'; import PieceListActions from '../../../../../actions/piece_list_actions'; @@ -35,7 +34,6 @@ let LumenusRegisterPiece = React.createClass({ return mergeOptions( UserStore.getState(), PieceListStore.getState(), - PieceStore.getState(), { selectedLicense: 0, isFineUploaderActive: false, @@ -46,33 +44,22 @@ let LumenusRegisterPiece = React.createClass({ componentDidMount() { PieceListStore.listen(this.onChange); UserStore.listen(this.onChange); - PieceStore.listen(this.onChange); UserActions.fetchCurrentUser(); - let queryParams = this.props.location.query; - - // Since every step of this register process is atomic, - // we may need to enter the process at step 1 or 2. - // If this is the case, we'll need the piece number to complete submission. - // It is encoded in the URL as a queryParam and we're checking for it here. - // - // We're using 'in' here as we want to know if 'piece_id' is present in the url, - // we don't care about the value. - if(queryParams && 'piece_id' in queryParams) { - PieceActions.fetchOne(queryParams.piece_id); - } + // Reset the piece store to make sure that we don't display old data + // if the user repeatedly registers works + PieceActions.updatePiece({}); }, componentWillUnmount() { PieceListStore.unlisten(this.onChange); UserStore.unlisten(this.onChange); - PieceStore.unlisten(this.onChange); }, onChange(state) { this.setState(state); - if(this.state.currentUser && this.state.currentUser.email) { + if (this.state.currentUser && this.state.currentUser.email) { // we should also make the fineuploader component editable again this.setState({ isFineUploaderActive: true @@ -80,26 +67,21 @@ let LumenusRegisterPiece = React.createClass({ } }, - handleRegisterSuccess(response){ + handleRegisterSuccess(response) { this.refreshPieceList(); - // also start loading the piece for the next step - if(response && response.piece) { - PieceActions.updatePiece({}); + // Use the response's piece for the next step if available + let pieceId = null; + if (response && response.piece) { + pieceId = response.piece.id; PieceActions.updatePiece(response.piece); } this.incrementStep(); - - this.refs.slidesContainer.nextSlide({ piece_id: response.piece.id }); + this.refs.slidesContainer.nextSlide({ piece_id: pieceId }); }, handleAdditionalDataSuccess() { - // We need to refetch the piece again after submitting the additional data - // since we want it's otherData to be displayed when the user choses to click - // on the browsers back button. - PieceActions.fetchOne(this.state.piece.id); - this.refreshPieceList(); this.history.pushState(null, `/collection`); @@ -107,13 +89,21 @@ let LumenusRegisterPiece = React.createClass({ // We need to increase the step to lock the forms that are already filled out incrementStep() { - // also increase step - let newStep = this.state.step + 1; this.setState({ - step: newStep + step: this.state.step + 1 }); }, + getPieceFromQueryParam() { + const queryParams = this.props.location.query; + + // Since every step of this register process is atomic, + // we may need to enter the process at step 1 or 2. + // If this is the case, we'll need the piece number to complete submission. + // It is encoded in the URL as a queryParam and we're checking for it here. + return queryParams && queryParams.piece_id; + }, + refreshPieceList() { PieceListActions.fetchPieceList( this.state.page, @@ -161,7 +151,7 @@ let LumenusRegisterPiece = React.createClass({ type="number" placeholder="(e.g. 32)" min={0} - required/> + required /> @@ -172,8 +162,8 @@ let LumenusRegisterPiece = React.createClass({ + pieceId={this.getPieceFromQueryParam()} + showHeading /> From 4ca8ca8feb8a992e89fba0c93380536181497d1c Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 5 Nov 2015 11:58:32 +0100 Subject: [PATCH 024/115] Show AdditionalDetailsModal from SubmitButton when details need to be filled in --- .../lumenus_buttons/lumenus_submit_button.js | 102 +++++++++++++++--- 1 file changed, 87 insertions(+), 15 deletions(-) diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js index 941b3ec0..6722beb7 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js @@ -3,19 +3,30 @@ import React from 'react'; import classNames from 'classnames'; +import LumenusAdditionalDataForm from '../lumenus_forms/lumenus_additional_data_form'; + import ConsignButton from '../../../../../ascribe_buttons/acls/consign_button'; +import AclFormFactory from '../../../../../ascribe_forms/acl_form_factory'; +import ConsignForm from '../../../../../ascribe_forms/form_consign'; + +import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper'; + +import PieceActions from '../../../../../../actions/piece_actions'; import WhitelabelActions from '../../../../../../actions/whitelabel_actions'; import WhitelabelStore from '../../../../../../stores/whitelabel_store'; +import ApiUrls from '../../../../../../constants/api_urls'; + +import { getAclFormDataId } from '../../../../../../utils/form_utils'; import { getLangText } from '../../../../../../utils/lang_utils'; let LumenusSubmitButton = React.createClass({ propTypes: { availableAcls: React.PropTypes.object.isRequired, - currentUser: React.PropTypes.object, - pieceOrEditions: React.PropTypes.array, - handleSuccess: React.PropTypes.func, + currentUser: React.PropTypes.object.isRequired, + editions: React.PropTypes.array.isRequired, + handleSuccess: React.PropTypes.func.isRequired, className: React.PropTypes.string, }, @@ -25,6 +36,7 @@ let LumenusSubmitButton = React.createClass({ componentDidMount() { WhitelabelStore.listen(this.onChange); + WhitelabelActions.fetchWhitelabel(); }, @@ -36,19 +48,79 @@ let LumenusSubmitButton = React.createClass({ this.setState(state); }, - render() { - const { availableAcls, currentUser, className, pieceOrEditions, handleSuccess } = this.props; + getFormDataId() { + return getAclFormDataId(false, this.props.editions); + }, - return ( - - ); + getAggregateEditionDetails() { + const { editions } = this.props; + + // Currently, we only care if all the given editions are from the same parent piece + // and if they can be submitted + return editions.reduce((details, curEdition) => { + return { + solePieceId: details.solePieceId === curEdition.parent ? details.solePieceId : null, + canSubmit: details.canSubmit && curEdition.acl.acl_wallet_submit + }; + }, { + solePieceId: editions.length > 0 ? editions[0].parent : null, + canSubmit: editions.length > 0 ? editions[0].acl.acl_wallet_submit : false + }); + }, + + handleAdditionalDataSuccess(pieceId) { + // Fetch newly updated piece to update the views + PieceActions.fetchOne(pieceId); + + this.refs.consignModal.show(); + }, + + render() { + const { availableAcls, currentUser, className, editions, handleSuccess } = this.props; + const buttonTitle = getLangText('CONSIGN TO LUMENUS'); + + const { solePieceId, canSubmit } = this.getAggregateEditionDetails(); + + if (solePieceId && !canSubmit) { + return ( + + + {buttonTitle} + + } + handleSuccess={this.handleAdditionalDataSuccess.bind(this, solePieceId)} + title={getLangText('Add additional information')}> + + + + + + + + ); + } else { + return ( + + ); + } } }); From 6223248ea03e58c2af3a460455c112a44ee3ab3d Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 5 Nov 2015 13:51:40 +0100 Subject: [PATCH 025/115] Fix lumenus whitelabel api endpoints --- .../whitelabel/wallet/constants/wallet_api_urls.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index 87ee6b14..11e29a39 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -4,25 +4,26 @@ import walletConstants from './wallet_application_constants'; // gets subdomain as a parameter function getWalletApiUrls(subdomain) { - if (subdomain === 'cyland'){ + if (subdomain === 'cyland') { return { 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/', 'piece_extradata': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/extradata/', 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' }; - } - else if (subdomain === 'ikonotv'){ + } else if (subdomain === 'ikonotv') { return { 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/', 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' }; - } - else if (subdomain === 'lumenus'){ + } else if (subdomain === 'lumenus') { return { - 'editions': walletConstants.walletApiEndpoint + subdomain + '/editions/', + 'editions_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/editions/', + 'edition': walletConstants.walletApiEndpoint + subdomain + '/editions/${edition_id}/', 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', + 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/', + 'piece_extradata': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/extradata/', 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' }; } From 04e453b28cf6041b4c4e92c2f662259ed00f77d8 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 5 Nov 2015 16:05:20 +0100 Subject: [PATCH 026/115] Fix lumenus edition endpoint --- js/components/whitelabel/wallet/constants/wallet_api_urls.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index 11e29a39..27be363d 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -20,7 +20,7 @@ function getWalletApiUrls(subdomain) { } else if (subdomain === 'lumenus') { return { 'editions_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/editions/', - 'edition': walletConstants.walletApiEndpoint + subdomain + '/editions/${edition_id}/', + 'edition': walletConstants.walletApiEndpoint + subdomain + '/editions/${bitcoin_id}/', 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/', 'piece_extradata': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/extradata/', From fb62d2d2e0461b2511eb38d55f333e11d9332916 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 6 Nov 2015 14:00:58 +0100 Subject: [PATCH 027/115] Check for completion of additional data on front end In the end, it made more sense to check if all the additional details have been filled in on the front end than receiving an acl or flag from the backend. --- .../lumenus_buttons/lumenus_submit_button.js | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js index 6722beb7..7046c069 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_submit_button.js @@ -12,6 +12,8 @@ import ConsignForm from '../../../../../ascribe_forms/form_consign'; import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper'; +import AclProxy from '../../../../../acl_proxy'; + import PieceActions from '../../../../../../actions/piece_actions'; import WhitelabelActions from '../../../../../../actions/whitelabel_actions'; import WhitelabelStore from '../../../../../../stores/whitelabel_store'; @@ -48,6 +50,20 @@ let LumenusSubmitButton = React.createClass({ this.setState(state); }, + canEditionBeSubmitted(edition) { + if (edition && edition.extra_data && edition.other_data) { + const { extra_data, other_data } = edition; + + if (extra_data.artist_bio && extra_data.work_description && + extra_data.technology_details && extra_data.display_instructions && + other_data.length > 0) { + return true; + } + } + + return false; + }, + getFormDataId() { return getAclFormDataId(false, this.props.editions); }, @@ -60,11 +76,11 @@ let LumenusSubmitButton = React.createClass({ return editions.reduce((details, curEdition) => { return { solePieceId: details.solePieceId === curEdition.parent ? details.solePieceId : null, - canSubmit: details.canSubmit && curEdition.acl.acl_wallet_submit + canSubmit: details.canSubmit && this.canEditionBeSubmitted(curEdition) }; }, { solePieceId: editions.length > 0 ? editions[0].parent : null, - canSubmit: editions.length > 0 ? editions[0].acl.acl_wallet_submit : false + canSubmit: this.canEditionBeSubmitted(editions[0]) }); }, @@ -83,7 +99,9 @@ let LumenusSubmitButton = React.createClass({ if (solePieceId && !canSubmit) { return ( - + @@ -107,12 +125,12 @@ let LumenusSubmitButton = React.createClass({ pieceOrEditions={editions} showNotification /> - + ); } else { return ( Date: Fri, 6 Nov 2015 14:01:26 +0100 Subject: [PATCH 028/115] Show unconsign button --- .../lumenus/lumenus_buttons/lumenus_acl_button_list.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js index 76608032..77657aca 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js @@ -7,6 +7,7 @@ import LumenusSubmitButton from './lumenus_submit_button'; import DeleteButton from '../../../../../ascribe_buttons/delete_button'; import ShareButton from '../../../../../ascribe_buttons/acls/share_button'; import TransferButton from '../../../../../ascribe_buttons/acls/transfer_button'; +import UnconsignButton from '../../../../../ascribe_buttons/acls/transfer_button'; import UserActions from '../../../../../../actions/user_actions'; import UserStore from '../../../../../../stores/user_store'; @@ -59,6 +60,11 @@ let LumenusAclButtonList = React.createClass({ currentUser={this.state.currentUser} pieceOrEditions={pieceOrEditions} handleSuccess={handleSuccess} /> + {this.props.children} ); From 318a0bf4b2c0f2e8b383342de5a54d4ad117b158 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 6 Nov 2015 14:10:54 +0100 Subject: [PATCH 029/115] Filter the collection to only show the consignable items by default --- .../piece_list_bulk_modal.js | 31 ------------------- .../piece_list_toolbar_filter_widget.js | 10 +++--- js/components/piece_list.js | 23 +++++++++++++- .../components/lumenus/lumenus_piece_list.js | 3 +- 4 files changed, 29 insertions(+), 38 deletions(-) diff --git a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js index 1380f21d..bb8d4ccc 100644 --- a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js +++ b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js @@ -6,12 +6,6 @@ import { mergeOptions } from '../../utils/general_utils'; import EditionListActions from '../../actions/edition_list_actions'; -import UserStore from '../../stores/user_store'; -import UserActions from '../../actions/user_actions'; - -import PieceListStore from '../../stores/piece_list_store'; -import PieceListActions from '../../actions/piece_list_actions'; - import PieceListBulkModalSelectedEditionsWidget from './piece_list_bulk_modal_selected_editions_widget'; import { getLangText } from '../../utils/lang_utils.js'; @@ -30,31 +24,6 @@ let PieceListBulkModal = React.createClass({ ]) }, - getInitialState() { - return mergeOptions( - UserStore.getState(), - PieceListStore.getState() - ); - }, - - componentDidMount() { - UserStore.listen(this.onChange); - PieceListStore.listen(this.onChange); - - UserActions.fetchCurrentUser(); - PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, - this.state.orderBy, this.state.orderAsc, this.state.filterBy); - }, - - componentWillUnmount() { - PieceListStore.unlisten(this.onChange); - UserStore.unlisten(this.onChange); - }, - - onChange(state) { - this.setState(state); - }, - clearAllSelections() { EditionListActions.clearAllEditionSelections(); EditionListActions.closeAllEditionLists(); diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js index 38de2af6..cea41e3b 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js @@ -28,7 +28,7 @@ let PieceListToolbarFilterWidget = React.createClass({ }, generateFilterByStatement(param) { - let filterBy = this.props.filterBy; + const { filterBy } = this.props; if(filterBy) { // we need hasOwnProperty since the values are all booleans @@ -56,13 +56,13 @@ let PieceListToolbarFilterWidget = React.createClass({ */ filterBy(param) { return () => { - let filterBy = this.generateFilterByStatement(param); + const filterBy = this.generateFilterByStatement(param); this.props.applyFilterBy(filterBy); }; }, isFilterActive() { - let trueValuesOnly = Object.keys(this.props.filterBy).filter((acl) => acl); + const 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 @@ -74,7 +74,7 @@ let PieceListToolbarFilterWidget = React.createClass({ }, render() { - let filterIcon = ( + const filterIcon = ( * @@ -140,4 +140,4 @@ let PieceListToolbarFilterWidget = React.createClass({ } }); -export default PieceListToolbarFilterWidget; \ No newline at end of file +export default PieceListToolbarFilterWidget; diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 69f5b23f..1ea64ff6 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -60,11 +60,17 @@ let PieceList = React.createClass({ }] }; }, + getInitialState() { - return mergeOptions( + const stores = mergeOptions( PieceListStore.getState(), EditionListStore.getState() ); + + // Use the default filters but use the stores' settings if they're available + stores.filterBy = Object.assign(this.getDefaultFilterBy(), stores.filterBy); + + return stores; }, componentDidMount() { @@ -96,6 +102,21 @@ let PieceList = React.createClass({ this.setState(state); }, + getDefaultFilterBy() { + const { filterParams } = this.props; + const defaultFilterBy = {}; + + filterParams.forEach(({ label, items }) => { + items.forEach((item) => { + if (typeof item === 'object' && item.defaultValue) { + defaultFilterBy[item.key] = true; + } + }); + }); + + return defaultFilterBy; + }, + paginationGoToPage(page) { return () => { // if the users clicks a pager of the pagination, diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js index ccfb7e1c..58ad7813 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js @@ -25,7 +25,8 @@ let LumenusPieceList = React.createClass({ label: getLangText('Show works I can'), items: [{ key: 'acl_consign', - label: getLangText('consign to Lumenus') + label: getLangText('consign to Lumenus'), + defaultValue: true }] }]} location={this.props.location}/> From b100fdd80a35ae138afd2a33c9c9391e50d6f0bc Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 6 Nov 2015 16:13:47 +0100 Subject: [PATCH 030/115] Change default collection filter for lumens admin --- .../lumenus_acl_button_list.js | 2 +- .../components/lumenus/lumenus_piece_list.js | 39 +++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js index 77657aca..8398dbda 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js @@ -7,7 +7,7 @@ import LumenusSubmitButton from './lumenus_submit_button'; import DeleteButton from '../../../../../ascribe_buttons/delete_button'; import ShareButton from '../../../../../ascribe_buttons/acls/share_button'; import TransferButton from '../../../../../ascribe_buttons/acls/transfer_button'; -import UnconsignButton from '../../../../../ascribe_buttons/acls/transfer_button'; +import UnconsignButton from '../../../../../ascribe_buttons/acls/unconsign_button'; import UserActions from '../../../../../../actions/user_actions'; import UserStore from '../../../../../../stores/user_store'; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js index 58ad7813..ebf90b3c 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_piece_list.js @@ -6,15 +6,48 @@ import LumenusAclButtonList from './lumenus_buttons/lumenus_acl_button_list'; import PieceList from '../../../../piece_list'; -import { getLangText } from '../../../../../utils/lang_utils'; +import UserActions from '../../../../../actions/user_actions'; +import UserStore from '../../../../../stores/user_store'; +import WhitelabelActions from '../../../../../actions/whitelabel_actions'; +import WhitelabelStore from '../../../../../stores/whitelabel_store'; + import { setDocumentTitle } from '../../../../../utils/dom_utils'; +import { mergeOptions } from '../../../../../utils/general_utils'; +import { getLangText } from '../../../../../utils/lang_utils'; let LumenusPieceList = React.createClass({ propTypes: { location: React.PropTypes.object }, + getInitialState() { + return mergeOptions( + UserStore.getState(), + WhitelabelStore.getState() + ); + }, + + componentDidMount() { + UserStore.listen(this.onChange); + WhitelabelStore.listen(this.onChange); + + UserActions.fetchCurrentUser(); + WhitelabelActions.fetchWhitelabel(); + }, + + componentWillUnmount() { + UserStore.unlisten(this.onChange); + WhitelabelStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + render() { + const { currentUser, whitelabel } = this.state; + const isUserAdmin = currentUser.email === whitelabel.user; + setDocumentTitle(getLangText('Collection')); return ( @@ -24,8 +57,8 @@ let LumenusPieceList = React.createClass({ filterParams={[{ label: getLangText('Show works I can'), items: [{ - key: 'acl_consign', - label: getLangText('consign to Lumenus'), + key: isUserAdmin ? 'acl_transfer' : 'acl_consign', + label: getLangText(isUserAdmin ? 'transfer' : 'consign to Lumenus'), defaultValue: true }] }]} From 84e8e4612f23809c4ff19c719a1bc0df9450f731 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 10 Nov 2015 18:24:46 +0100 Subject: [PATCH 031/115] Autofocus message field in consignment form and use custom labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For now, we’ll let the artist specify their suggested price in the consignment form’s message field. --- .../ascribe_buttons/acls/acl_button.js | 2 +- .../ascribe_forms/acl_form_factory.js | 10 ++- js/components/ascribe_forms/form_consign.js | 24 ++++--- .../ascribe_forms/input_textarea_toggable.js | 6 ++ .../list_form_request_actions.js | 4 +- js/components/ascribe_forms/property.js | 7 ++ .../lumenus_buttons/lumenus_submit_button.js | 64 +++++++++++-------- .../lumenus_additional_data_form.js | 6 +- js/utils/form_utils.js | 4 ++ 9 files changed, 84 insertions(+), 43 deletions(-) diff --git a/js/components/ascribe_buttons/acls/acl_button.js b/js/components/ascribe_buttons/acls/acl_button.js index 82650bb6..8b21f92a 100644 --- a/js/components/ascribe_buttons/acls/acl_button.js +++ b/js/components/ascribe_buttons/acls/acl_button.js @@ -31,7 +31,7 @@ export default function ({ action, displayName, title, tooltip }) { availableAcls: React.PropTypes.object.isRequired, buttonAcceptName: React.PropTypes.string, buttonAcceptClassName: React.PropTypes.string, - currentUser: React.PropTypes.object.isRequired, + currentUser: React.PropTypes.object, email: React.PropTypes.string, pieceOrEditions: React.PropTypes.oneOfType([ React.PropTypes.object, diff --git a/js/components/ascribe_forms/acl_form_factory.js b/js/components/ascribe_forms/acl_form_factory.js index d5494c2d..9422a351 100644 --- a/js/components/ascribe_forms/acl_form_factory.js +++ b/js/components/ascribe_forms/acl_form_factory.js @@ -20,9 +20,11 @@ import { getAclFormMessage, getAclFormDataId } from '../../utils/form_utils'; let AclFormFactory = React.createClass({ propTypes: { action: React.PropTypes.oneOf(AppConstants.aclList).isRequired, - currentUser: React.PropTypes.object.isRequired, + autoFocusProperty: React.PropTypes.string, + currentUser: React.PropTypes.object, email: React.PropTypes.string, message: React.PropTypes.string, + labels: React.PropTypes.object, pieceOrEditions: React.PropTypes.oneOfType([ React.PropTypes.object, React.PropTypes.array @@ -53,10 +55,12 @@ let AclFormFactory = React.createClass({ render() { const { action, + autoFocusProperty, pieceOrEditions, currentUser, email, message, + labels, handleSuccess, showNotification } = this.props; @@ -64,14 +68,16 @@ let AclFormFactory = React.createClass({ aclName: action, entities: pieceOrEditions, isPiece: this.isPiece(), - senderName: currentUser.username + senderName: currentUser && currentUser.username }); if (action === 'acl_consign') { return ( diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index b8417961..c659610d 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -17,8 +17,10 @@ let ConsignForm = React.createClass({ propTypes: { url: React.PropTypes.string, id: React.PropTypes.object, + autoFocusProperty: React.PropTypes.string, email: React.PropTypes.string, message: React.PropTypes.string, + labels: React.PropTypes.object, handleSuccess: React.PropTypes.func }, @@ -27,10 +29,12 @@ let ConsignForm = React.createClass({ }, render() { + const { autoFocusProperty, email, id, handleSuccess, message, labels, url } = this.props; + return ( }> + label={labels.email || getLangText('Email')} + editable={!email} + overrideForm={!!email}> + label={labels.message || getLangText('Personal Message')} + editable + overrideForm> diff --git a/js/components/ascribe_forms/input_textarea_toggable.js b/js/components/ascribe_forms/input_textarea_toggable.js index c17a0e5a..0be8b87a 100644 --- a/js/components/ascribe_forms/input_textarea_toggable.js +++ b/js/components/ascribe_forms/input_textarea_toggable.js @@ -7,6 +7,7 @@ import TextareaAutosize from 'react-textarea-autosize'; let InputTextAreaToggable = React.createClass({ propTypes: { + autoFocus: React.PropTypes.bool, disabled: React.PropTypes.bool, rows: React.PropTypes.number.isRequired, required: React.PropTypes.bool, @@ -23,6 +24,10 @@ let InputTextAreaToggable = React.createClass({ }, componentDidMount() { + if (this.props.autoFocus) { + this.refs.textarea.focus(); + } + this.setState({ value: this.props.defaultValue }); @@ -51,6 +56,7 @@ let InputTextAreaToggable = React.createClass({ className = className + ' ascribe-textarea-editable'; textarea = ( + {getLangText('CONSIGN TO LUMENUS')} + + ); + const consignForm = ( + + ); if (solePieceId && !canSubmit) { return ( @@ -103,11 +124,7 @@ let LumenusSubmitButton = React.createClass({ aclObject={availableAcls} aclName='acl_consign'> - {buttonTitle} - - } + trigger={triggerButton} handleSuccess={this.handleAdditionalDataSuccess.bind(this, solePieceId)} title={getLangText('Add additional information')}> - + {consignForm} ); } else { return ( - + + + {consignForm} + + ); } } diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js index 3b69ce81..2fbf5679 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js @@ -51,10 +51,8 @@ let LumenusAdditionalDataForm = React.createClass({ componentDidMount() { PieceStore.listen(this.onChange); - // If the Piece store doesn't already have the piece we want loaded, load it - const { pieceId } = this.props; - if (pieceId && this.state.piece.id !== pieceId) { - PieceActions.fetchOne(pieceId); + if (this.props.pieceId) { + PieceActions.fetchOne(this.props.pieceId); } }, diff --git a/js/utils/form_utils.js b/js/utils/form_utils.js index d2d2cd29..8d12a8c1 100644 --- a/js/utils/form_utils.js +++ b/js/utils/form_utils.js @@ -72,6 +72,10 @@ export function getAclFormMessage(options) { throw new Error('Your specified aclName did not match a an acl class.'); } + if (options.additionalMessage) { + message += '\n\n' + options.additionalMessage; + } + if (options.senderName) { message += '\n\n'; message += getLangText('Truly yours,'); From e85747144aacb402df4f1795c5240b23472522aa Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 10 Nov 2015 18:49:08 +0100 Subject: [PATCH 032/115] Add Lumenus landing page --- .../components/lumenus/lumenus_landing.js | 82 +++++++++++++++++++ .../whitelabel/wallet/wallet_routes.js | 2 + 2 files changed, 84 insertions(+) create mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js new file mode 100644 index 00000000..279327d5 --- /dev/null +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js @@ -0,0 +1,82 @@ +'use strict'; + +import React from 'react'; + +import Button from 'react-bootstrap/lib/Button'; +import LinkContainer from 'react-router-bootstrap/lib/LinkContainer'; + +import WhitelabelActions from '../../../../../actions/whitelabel_actions'; +import WhitelabelStore from '../../../../../stores/whitelabel_store'; + +import { mergeOptions } from '../../../../../utils/general_utils'; +import { getLangText } from '../../../../../utils/lang_utils'; +import { setDocumentTitle } from '../../../../../utils/dom_utils'; + + +let LumenusLanding = React.createClass({ + + getInitialState() { + return mergeOptions( + WhitelabelStore.getState() + ); + }, + + componentDidMount() { + WhitelabelStore.listen(this.onChange); + WhitelabelActions.fetchWhitelabel(); + }, + + componentWillUnmount() { + WhitelabelStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + render() { + setDocumentTitle('Lumenus Marketplace'); + + return ( +
    +
    +
    +
    + +
    + {getLangText('Artwork from the Lumenus Marketplace is powered by') + ' '} + + + +
    +
    +
    +
    +

    + {getLangText('Existing ascribe user?')} +

    + + + +
    +
    +

    + {getLangText('Do you need an account?')} +

    + + + +
    +
    +
    +
    +
    + ); + } +}); + +export default LumenusLanding; diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 9b0e9465..86b3474a 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -29,6 +29,7 @@ import IkonotvRegisterPiece from './components/ikonotv/ikonotv_register_piece'; import IkonotvPieceContainer from './components/ikonotv/ikonotv_detail/ikonotv_piece_container'; import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications'; +import LumenusLanding from './components/lumenus/lumenus_landing'; import LumenusPieceList from './components/lumenus/lumenus_piece_list'; import LumenusRegisterPiece from './components/lumenus/lumenus_register_piece'; import LumenusPieceContainer from './components/lumenus/lumenus_detail/lumenus_piece_container'; @@ -157,6 +158,7 @@ let ROUTES = { ), 'lumenus': ( + From c0e0354ce832f106747f68f6a0d135a08d088a8f Mon Sep 17 00:00:00 2001 From: Cevo Date: Fri, 20 Nov 2015 11:11:03 +0100 Subject: [PATCH 033/115] fixing the public note bug --- js/components/ascribe_detail/piece_container.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index 8cd7df1c..1914e3af 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -57,6 +57,7 @@ let PieceContainer = React.createClass({ mixins: [History], getInitialState() { + console.log('Piece initially ... ', PieceStore.getState()); return mergeOptions( UserStore.getState(), PieceListStore.getState(), @@ -103,7 +104,6 @@ let PieceContainer = React.createClass({ let pieceState = mergeOptions({}, state.piece); pieceState.acl.acl_loan = false; - this.setState({ piece: pieceState }); @@ -273,14 +273,17 @@ let PieceContainer = React.createClass({ currentUser={this.state.currentUser}/> + {console.log('hey')} + {console.log(this.state.piece)} + {console.log(this.state.piece.acl.acl_edit)} Date: Fri, 20 Nov 2015 11:47:41 +0100 Subject: [PATCH 034/115] piece note hidden from non-owner --- js/components/ascribe_detail/piece_container.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index 1914e3af..d0ca2e1e 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -57,7 +57,6 @@ let PieceContainer = React.createClass({ mixins: [History], getInitialState() { - console.log('Piece initially ... ', PieceStore.getState()); return mergeOptions( UserStore.getState(), PieceListStore.getState(), @@ -252,7 +251,6 @@ let PieceContainer = React.createClass({ } buttons={this.getActions()}> {this.getCreateEditionsDialog()} - 0}> @@ -261,7 +259,8 @@ let PieceContainer = React.createClass({ + show={(!!(this.state.currentUser.username || this.state.piece.public_note)) && + (!!this.state.piece.acl.acl_edit)}> - {console.log('hey')} - {console.log(this.state.piece)} - {console.log(this.state.piece.acl.acl_edit)} Date: Mon, 23 Nov 2015 10:46:20 +0100 Subject: [PATCH 035/115] Check for piece and edition validity by using their ids instead of titles Also removed a few unnecessary location props. --- js/components/ascribe_detail/edition.js | 6 ++---- js/components/ascribe_detail/edition_container.js | 8 +++----- js/components/ascribe_detail/further_details.js | 6 ++---- js/components/ascribe_detail/piece_container.js | 8 +++----- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index 6b38ddf8..254746a6 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -42,8 +42,7 @@ import { getLangText } from '../../utils/lang_utils'; let Edition = React.createClass({ propTypes: { edition: React.PropTypes.object, - loadEdition: React.PropTypes.func, - location: React.PropTypes.object + loadEdition: React.PropTypes.func }, mixins: [History], @@ -156,8 +155,7 @@ let Edition = React.createClass({ pieceId={this.props.edition.parent} extraData={this.props.edition.extra_data} otherData={this.props.edition.other_data} - handleSuccess={this.props.loadEdition} - location={this.props.location}/> + handleSuccess={this.props.loadEdition} /> diff --git a/js/components/ascribe_detail/edition_container.js b/js/components/ascribe_detail/edition_container.js index 2c479d24..febe652d 100644 --- a/js/components/ascribe_detail/edition_container.js +++ b/js/components/ascribe_detail/edition_container.js @@ -17,8 +17,7 @@ import { setDocumentTitle } from '../../utils/dom_utils'; */ let EditionContainer = React.createClass({ propTypes: { - params: React.PropTypes.object, - location: React.PropTypes.object + params: React.PropTypes.object }, getInitialState() { @@ -67,14 +66,13 @@ let EditionContainer = React.createClass({ }, render() { - if(this.state.edition && this.state.edition.title) { + if(this.state.edition && this.state.edition.id) { setDocumentTitle([this.state.edition.artist_name, this.state.edition.title].join(', ')); return ( + loadEdition={this.loadEdition} /> ); } else { return ( diff --git a/js/components/ascribe_detail/further_details.js b/js/components/ascribe_detail/further_details.js index 91ce87c5..c178fb93 100644 --- a/js/components/ascribe_detail/further_details.js +++ b/js/components/ascribe_detail/further_details.js @@ -23,8 +23,7 @@ let FurtherDetails = React.createClass({ pieceId: React.PropTypes.number, extraData: React.PropTypes.object, otherData: React.PropTypes.arrayOf(React.PropTypes.object), - handleSuccess: React.PropTypes.func, - location: React.PropTypes.object + handleSuccess: React.PropTypes.func }, getInitialState() { @@ -86,8 +85,7 @@ let FurtherDetails = React.createClass({ overrideForm={true} pieceId={this.props.pieceId} otherData={this.props.otherData} - multiple={true} - location={this.props.location}/> + multiple={true} /> diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index 8cd7df1c..cde6eaea 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -50,8 +50,7 @@ import { setDocumentTitle } from '../../utils/dom_utils'; */ let PieceContainer = React.createClass({ propTypes: { - params: React.PropTypes.object, - location: React.PropTypes.object + params: React.PropTypes.object }, mixins: [History], @@ -226,7 +225,7 @@ let PieceContainer = React.createClass({ }, render() { - if(this.state.piece && this.state.piece.title) { + if(this.state.piece && this.state.piece.id) { setDocumentTitle([this.state.piece.artist_name, this.state.piece.title].join(', ')); return ( @@ -292,8 +291,7 @@ let PieceContainer = React.createClass({ pieceId={this.state.piece.id} extraData={this.state.piece.extra_data} otherData={this.state.piece.other_data} - handleSuccess={this.loadPiece} - location={this.props.location}/> + handleSuccess={this.loadPiece} /> From e4cafd4bc3df86e8da992db6bd61c39b120dddaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Mon, 23 Nov 2015 19:02:28 +0100 Subject: [PATCH 036/115] Fix bug in FB button component & simplify injectHead util --- .../ascribe_buttons/acl_button_list.js | 2 +- .../facebook_share_button.js | 16 ++++++------- js/utils/inject_utils.js | 23 ++++++++----------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/js/components/ascribe_buttons/acl_button_list.js b/js/components/ascribe_buttons/acl_button_list.js index 42f86320..35e42c20 100644 --- a/js/components/ascribe_buttons/acl_button_list.js +++ b/js/components/ascribe_buttons/acl_button_list.js @@ -41,7 +41,7 @@ let AclButtonList = React.createClass({ componentDidMount() { UserStore.listen(this.onChange); - UserActions.fetchCurrentUser(); + UserActions.fetchCurrentUser.defer(); window.addEventListener('resize', this.handleResize); window.dispatchEvent(new Event('resize')); diff --git a/js/components/ascribe_social_share/facebook_share_button.js b/js/components/ascribe_social_share/facebook_share_button.js index 87a2aef6..aa0b6691 100644 --- a/js/components/ascribe_social_share/facebook_share_button.js +++ b/js/components/ascribe_social_share/facebook_share_button.js @@ -8,7 +8,6 @@ import { InjectInHeadUtils } from '../../utils/inject_utils'; let FacebookShareButton = React.createClass({ propTypes: { - url: React.PropTypes.string, type: React.PropTypes.string }, @@ -28,12 +27,14 @@ let FacebookShareButton = React.createClass({ * To circumvent this, we always have the sdk parse the entire DOM on the initial load * (see FacebookHandler) and then use FB.XFBML.parse() on the mounting component later. */ - if (!InjectInHeadUtils.isPresent('script', AppConstants.facebook.sdkUrl)) { - InjectInHeadUtils.inject(AppConstants.facebook.sdkUrl); - } else { - // Parse() searches the children of the element we give it, not the element itself. - FB.XFBML.parse(this.refs.fbShareButton.getDOMNode().parentElement); - } + + InjectInHeadUtils + .inject(AppConstants.facebook.sdkUrl) + .then(() => { FB.XFBML.parse(this.refs.fbShareButton.getDOMNode().parentElement) }); + }, + + shouldComponentUpdate(nextProps) { + return this.props.type !== nextProps.type; }, render() { @@ -41,7 +42,6 @@ let FacebookShareButton = React.createClass({ ); diff --git a/js/utils/inject_utils.js b/js/utils/inject_utils.js index 174ac8b6..e9430a5e 100644 --- a/js/utils/inject_utils.js +++ b/js/utils/inject_utils.js @@ -12,16 +12,16 @@ let mapTag = { css: 'link' }; +let tags = {}; + function injectTag(tag, src) { - return Q.Promise((resolve, reject) => { - if (isPresent(tag, src)) { - resolve(); - } else { + if(!tags[src]) { + tags[src] = Q.Promise((resolve, reject) => { let attr = mapAttr[tag]; let element = document.createElement(tag); if (tag === 'script') { - element.onload = () => resolve(); - element.onerror = () => reject(); + element.onload = resolve; + element.onerror = reject; } else { resolve(); } @@ -30,14 +30,10 @@ function injectTag(tag, src) { if (tag === 'link') { element.rel = 'stylesheet'; } - } - }); -} + }); + } -function isPresent(tag, src) { - let attr = mapAttr[tag]; - let query = `head > ${tag}[${attr}="${src}"]`; - return document.querySelector(query); + return tags[src]; } function injectStylesheet(src) { @@ -65,7 +61,6 @@ export const InjectInHeadUtils = { * you don't want to embed everything inside the build file. */ - isPresent, injectStylesheet, injectScript, inject From f7b55e56a9f0e53be1cd231d668e436448fff2db Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 24 Nov 2015 12:04:08 +0100 Subject: [PATCH 037/115] Split Lumenus into a generic market wallet for 23vivi --- .../lumenus_edition_container.js | 24 ------------------- .../market_buttons/market_acl_button_list.js} | 12 +++++----- .../market_buttons/market_submit_button.js} | 12 +++++----- .../market_detail/market_edition_container.js | 24 +++++++++++++++++++ .../market_detail/market_further_details.js} | 8 +++---- .../market_detail/market_piece_container.js} | 8 +++---- .../market_additional_data_form.js} | 8 +++---- .../market_piece_list.js} | 10 ++++---- .../market_register_piece.js} | 10 ++++---- .../whitelabel/wallet/wallet_routes.js | 22 ++++++++--------- 10 files changed, 69 insertions(+), 69 deletions(-) delete mode 100644 js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js rename js/components/whitelabel/wallet/components/{lumenus/lumenus_buttons/lumenus_acl_button_list.js => market/market_buttons/market_acl_button_list.js} (88%) rename js/components/whitelabel/wallet/components/{lumenus/lumenus_buttons/lumenus_submit_button.js => market/market_buttons/market_submit_button.js} (93%) create mode 100644 js/components/whitelabel/wallet/components/market/market_detail/market_edition_container.js rename js/components/whitelabel/wallet/components/{lumenus/lumenus_detail/lumenus_further_details.js => market/market_detail/market_further_details.js} (58%) rename js/components/whitelabel/wallet/components/{lumenus/lumenus_detail/lumenus_piece_container.js => market/market_detail/market_piece_container.js} (56%) rename js/components/whitelabel/wallet/components/{lumenus/lumenus_forms/lumenus_additional_data_form.js => market/market_forms/market_additional_data_form.js} (97%) rename js/components/whitelabel/wallet/components/{lumenus/lumenus_piece_list.js => market/market_piece_list.js} (88%) rename js/components/whitelabel/wallet/components/{lumenus/lumenus_register_piece.js => market/market_register_piece.js} (96%) diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js deleted file mode 100644 index c81fb0bb..00000000 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_edition_container.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -import React from 'react'; - -import LumenusFurtherDetails from './lumenus_further_details'; - -import LumenusAclButtonList from '../lumenus_buttons/lumenus_acl_button_list'; - -import EditionContainer from '../../../../../ascribe_detail/edition_container'; - -let LumenusEditionContainer = React.createClass({ - propTypes: EditionContainer.propTypes, - - render() { - return ( - - ); - } -}); - -export default LumenusEditionContainer; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js b/js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js similarity index 88% rename from js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js rename to js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js index 8398dbda..1dcdd4e5 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_buttons/lumenus_acl_button_list.js +++ b/js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js @@ -2,17 +2,17 @@ import React from 'react'; -import LumenusSubmitButton from './lumenus_submit_button'; +import MarketSubmitButton from './market_submit_button'; import DeleteButton from '../../../../../ascribe_buttons/delete_button'; -import ShareButton from '../../../../../ascribe_buttons/acls/share_button'; +import EmailButton from '../../../../../ascribe_buttons/acls/email_button'; import TransferButton from '../../../../../ascribe_buttons/acls/transfer_button'; import UnconsignButton from '../../../../../ascribe_buttons/acls/unconsign_button'; import UserActions from '../../../../../../actions/user_actions'; import UserStore from '../../../../../../stores/user_store'; -let LumenusAclButtonList = React.createClass({ +let MarketAclButtonList = React.createClass({ propTypes: { availableAcls: React.PropTypes.object.isRequired, className: React.PropTypes.string, @@ -45,12 +45,12 @@ let LumenusAclButtonList = React.createClass({ let { availableAcls, className, pieceOrEditions, handleSuccess } = this.props; return (
    - - - {getLangText('CONSIGN TO LUMENUS')} + {getLangText('CONSIGN TO TODO')} ); const consignForm = ( @@ -127,7 +127,7 @@ let LumenusSubmitButton = React.createClass({ trigger={triggerButton} handleSuccess={this.handleAdditionalDataSuccess.bind(this, solePieceId)} title={getLangText('Add additional information')}> - @@ -147,7 +147,7 @@ let LumenusSubmitButton = React.createClass({ + title={getLangText('Consign artwork to TODO')}> {consignForm} @@ -156,4 +156,4 @@ let LumenusSubmitButton = React.createClass({ } }); -export default LumenusSubmitButton; +export default MarketSubmitButton; diff --git a/js/components/whitelabel/wallet/components/market/market_detail/market_edition_container.js b/js/components/whitelabel/wallet/components/market/market_detail/market_edition_container.js new file mode 100644 index 00000000..97284dbc --- /dev/null +++ b/js/components/whitelabel/wallet/components/market/market_detail/market_edition_container.js @@ -0,0 +1,24 @@ +'use strict'; + +import React from 'react'; + +import MarketFurtherDetails from './market_further_details'; + +import MarketAclButtonList from '../market_buttons/market_acl_button_list'; + +import EditionContainer from '../../../../../ascribe_detail/edition_container'; + +let MarketEditionContainer = React.createClass({ + propTypes: EditionContainer.propTypes, + + render() { + return ( + + ); + } +}); + +export default MarketEditionContainer; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js b/js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js similarity index 58% rename from js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js rename to js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js index 79199b68..4e1e3ee8 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_further_details.js +++ b/js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js @@ -2,9 +2,9 @@ import React from 'react'; -import LumenusAdditionalDataForm from '../lumenus_forms/lumenus_additional_data_form' +import MarketAdditionalDataForm from '../market_forms/market_additional_data_form' -let LumenusFurtherDetails = React.createClass({ +let MarketFurtherDetails = React.createClass({ propTypes: { pieceId: React.PropTypes.number, handleSuccess: React.PropTypes.func, @@ -12,7 +12,7 @@ let LumenusFurtherDetails = React.createClass({ render() { return ( - @@ -20,4 +20,4 @@ let LumenusFurtherDetails = React.createClass({ } }); -export default LumenusFurtherDetails; +export default MarketFurtherDetails; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js b/js/components/whitelabel/wallet/components/market/market_detail/market_piece_container.js similarity index 56% rename from js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js rename to js/components/whitelabel/wallet/components/market/market_detail/market_piece_container.js index 391a7cb5..d41ade56 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_detail/lumenus_piece_container.js +++ b/js/components/whitelabel/wallet/components/market/market_detail/market_piece_container.js @@ -2,20 +2,20 @@ import React from 'react'; -import LumenusFurtherDetails from './lumenus_further_details'; +import MarketFurtherDetails from './market_further_details'; import PieceContainer from '../../../../../ascribe_detail/piece_container'; -let LumenusPieceContainer = React.createClass({ +let MarketPieceContainer = React.createClass({ propTypes: PieceContainer.propTypes, render() { return ( + furtherDetailsType={MarketFurtherDetails} /> ); } }); -export default LumenusPieceContainer; +export default MarketPieceContainer; diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js b/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js similarity index 97% rename from js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js rename to js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js index 2fbf5679..a2318b99 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_forms/lumenus_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js @@ -24,7 +24,7 @@ import requests from '../../../../../../utils/requests'; import { mergeOptions } from '../../../../../../utils/general_utils'; import { getLangText } from '../../../../../../utils/lang_utils'; -let LumenusAdditionalDataForm = React.createClass({ +let MarketAdditionalDataForm = React.createClass({ propTypes: { pieceId: React.PropTypes.oneOfType([ React.PropTypes.number, @@ -163,7 +163,7 @@ let LumenusAdditionalDataForm = React.createClass({ spinner={spinner}> {heading} 0} enableLocalHashing={false} - headerMessage={getLangText('Consign to Lumenus')} + headerMessage={getLangText('Consign to Market')} submitMessage={getLangText('Proceed to additional details')} isFineUploaderActive={this.state.isFineUploaderActive} handleSuccess={this.handleRegisterSuccess} @@ -160,7 +160,7 @@ let LumenusRegisterPiece = React.createClass({
    - @@ -172,4 +172,4 @@ let LumenusRegisterPiece = React.createClass({ } }); -export default LumenusRegisterPiece; +export default MarketRegisterPiece; diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 86b3474a..85e79924 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -15,7 +15,8 @@ import EditionContainer from '../../../components/ascribe_detail/edition_contain import SettingsContainer from '../../../components/ascribe_settings/settings_container'; import ContractSettings from '../../../components/ascribe_settings/contract_settings'; import ErrorNotFoundPage from '../../../components/error_not_found_page'; -import RegisterPiece from '../../../components/register_piece'; //TODO: Remove once finished with LumenusRegisterPiece + +import CCRegisterPiece from './components/cc/cc_register_piece'; import CylandLanding from './components/cyland/cyland_landing'; import CylandPieceContainer from './components/cyland/cyland_detail/cyland_piece_container'; @@ -29,13 +30,12 @@ import IkonotvRegisterPiece from './components/ikonotv/ikonotv_register_piece'; import IkonotvPieceContainer from './components/ikonotv/ikonotv_detail/ikonotv_piece_container'; import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications'; -import LumenusLanding from './components/lumenus/lumenus_landing'; -import LumenusPieceList from './components/lumenus/lumenus_piece_list'; -import LumenusRegisterPiece from './components/lumenus/lumenus_register_piece'; -import LumenusPieceContainer from './components/lumenus/lumenus_detail/lumenus_piece_container'; -import LumenusEditionContainer from './components/lumenus/lumenus_detail/lumenus_edition_container'; +import MarketPieceList from './components/market/market_piece_list'; +import MarketRegisterPiece from './components/market/market_register_piece'; +import MarketPieceContainer from './components/market/market_detail/market_piece_container'; +import MarketEditionContainer from './components/market/market_detail/market_edition_container'; -import CCRegisterPiece from './components/cc/cc_register_piece'; +import LumenusLanding from './components/lumenus/lumenus_landing'; import AuthProxyHandler from '../../../components/ascribe_routes/proxy_routes/auth_proxy_handler'; @@ -179,14 +179,14 @@ let ROUTES = { component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(ContractSettings)}/> - - + + From ee61369d46aff112c6aaec6d583218f13be4ea58 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 24 Nov 2015 12:04:18 +0100 Subject: [PATCH 038/115] Update market api endpoints --- .../whitelabel/wallet/constants/wallet_api_urls.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index 27be363d..56088b03 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -19,12 +19,12 @@ function getWalletApiUrls(subdomain) { }; } else if (subdomain === 'lumenus') { return { - 'editions_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/editions/', - 'edition': walletConstants.walletApiEndpoint + subdomain + '/editions/${bitcoin_id}/', - 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', - 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/', - 'piece_extradata': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/extradata/', - 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' + 'editions_list': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/${piece_id}/editions/', + 'edition': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/editions/${bitcoin_id}/', + 'pieces_list': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/', + 'piece': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/${piece_id}/', + 'piece_extradata': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/${piece_id}/extradata/', + 'user': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/users/' }; } return {}; From a72fea5db4f919f2672c997781904b6e7eb79e90 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 24 Nov 2015 12:08:37 +0100 Subject: [PATCH 039/115] Add 23vivi routes --- .../wallet/constants/wallet_api_urls.js | 2 +- .../whitelabel/wallet/wallet_routes.js | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index 56088b03..cebe4aa6 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -17,7 +17,7 @@ function getWalletApiUrls(subdomain) { 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/', 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' }; - } else if (subdomain === 'lumenus') { + } else if (subdomain === 'lumenus' || subdomain === '23vivi') { return { 'editions_list': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/${piece_id}/editions/', 'edition': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/editions/${bitcoin_id}/', diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 85e79924..08adec44 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -190,10 +190,54 @@ let ROUTES = { + ), + '23vivi': ( + + + + + + + + + + + + + + + ) }; +function getRoutes(commonRoutes, subdomain) { + if(subdomain in ROUTES) { + return ROUTES[subdomain]; + } else { + throw new Error('Subdomain wasn\'t specified in the wallet app.'); + } +}; + + function getRoutes(commonRoutes, subdomain) { if(subdomain in ROUTES) { return ROUTES[subdomain]; From 2e03ab584a874918a51df8428cf25f2a301e9b97 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 24 Nov 2015 18:17:38 +0100 Subject: [PATCH 040/115] Fix api endpoints --- .../whitelabel/wallet/constants/wallet_api_urls.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index cebe4aa6..8ad2eb81 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -19,12 +19,12 @@ function getWalletApiUrls(subdomain) { }; } else if (subdomain === 'lumenus' || subdomain === '23vivi') { return { - 'editions_list': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/${piece_id}/editions/', - 'edition': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/editions/${bitcoin_id}/', - 'pieces_list': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/', - 'piece': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/${piece_id}/', - 'piece_extradata': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/pieces/${piece_id}/extradata/', - 'user': walletConstants.walletApiEndpoint + 'market/' + subdomain + '/users/' + 'editions_list': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/editions/', + 'edition': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/editions/${bitcoin_id}/', + 'pieces_list': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/', + 'piece': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/', + 'piece_extradata': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/extradata/', + 'user': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/users/' }; } return {}; From 47f56b5505bcd7e9ad06c50c07be61010165c06f Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 24 Nov 2015 18:17:51 +0100 Subject: [PATCH 041/115] Add 23vivi to app costs --- js/constants/application_constants.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index 70a9be89..1f2129da 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -58,6 +58,13 @@ const constants = { 'permissions': ['register', 'edit', 'share', 'del_from_collection'], 'type': 'wallet' }, + { + 'subdomain': '23vivi', + 'name': '23vivi', + 'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/23vivi/23vivi-logo.png', + 'permissions': ['register', 'edit', 'share', 'del_from_collection'], + 'type': 'wallet' + }, { 'subdomain': 'portfolioreview', 'name': 'Portfolio Review', From e228a4bf153cb9dec93612153438005cae184198 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 24 Nov 2015 18:18:49 +0100 Subject: [PATCH 042/115] Dynamically get marketplace name for submit button --- .../market/market_buttons/market_submit_button.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js index 5ad9f995..6a617bfb 100644 --- a/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js +++ b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js @@ -91,6 +91,7 @@ let MarketSubmitButton = React.createClass({ render() { const { availableAcls, currentUser, className, editions, handleSuccess } = this.props; + const { whitelabel } = this.state; const { solePieceId, canSubmit } = this.getAggregateEditionDetails(); const message = getAclFormMessage({ aclName: 'acl_consign', @@ -102,14 +103,14 @@ let MarketSubmitButton = React.createClass({ const triggerButton = ( ); const consignForm = ( + title={getLangText('Consign artwork to %s', whitelabel.name)}> {consignForm} From c270977eb92f3cd87ccb5ca2b20bcbbe38e930cc Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 24 Nov 2015 19:26:45 +0100 Subject: [PATCH 043/115] Fix default filter on piece list --- .../piece_list_toolbar_filter_widget.js | 2 +- js/components/piece_list.js | 121 ++++++++++++------ .../components/lumenus/lumenus_landing.js | 6 +- .../components/market/market_piece_list.js | 33 +++-- js/utils/general_utils.js | 6 + package.json | 1 + 6 files changed, 114 insertions(+), 55 deletions(-) diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js index cea41e3b..c463330c 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js @@ -28,7 +28,7 @@ let PieceListToolbarFilterWidget = React.createClass({ }, generateFilterByStatement(param) { - const { filterBy } = this.props; + const filterBy = Object.assign({}, this.props.filterBy); if(filterBy) { // we need hasOwnProperty since the values are all booleans diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 1ea64ff6..758edf7c 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -26,7 +26,7 @@ import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar'; import AscribeSpinner from './ascribe_spinner'; import { getAvailableAcls } from '../utils/acl_utils'; -import { mergeOptions } from '../utils/general_utils'; +import { mergeOptions, isShallowEqual } from '../utils/general_utils'; import { getLangText } from '../utils/lang_utils'; import { setDocumentTitle } from '../utils/dom_utils'; @@ -35,6 +35,7 @@ let PieceList = React.createClass({ propTypes: { accordionListItemType: React.PropTypes.func, bulkModalButtonListType: React.PropTypes.func, + canLoadPieceList: React.PropTypes.bool, redirectTo: React.PropTypes.string, customSubmitButton: React.PropTypes.element, filterParams: React.PropTypes.array, @@ -49,6 +50,7 @@ let PieceList = React.createClass({ return { accordionListItemType: AccordionListItemWallet, bulkModalButtonListType: AclButtonList, + canLoadPieceList: true, orderParams: ['artist_name', 'title'], filterParams: [{ label: getLangText('Show works I can'), @@ -62,27 +64,51 @@ let PieceList = React.createClass({ }, getInitialState() { + const pieceListStore = PieceListStore.getState(); const stores = mergeOptions( - PieceListStore.getState(), - EditionListStore.getState() + pieceListStore, + EditionListStore.getState(), + { + isFilterDirty: false + } ); - // Use the default filters but use the stores' settings if they're available - stores.filterBy = Object.assign(this.getDefaultFilterBy(), stores.filterBy); + // Use the default filters but use the pieceListStore's settings if they're available + stores.filterBy = Object.assign(this.getDefaultFilterBy(), pieceListStore.filterBy); return stores; }, componentDidMount() { - let page = this.props.location.query.page || 1; - PieceListStore.listen(this.onChange); EditionListStore.listen(this.onChange); - let orderBy = this.props.orderBy ? this.props.orderBy : this.state.orderBy; - if (this.state.pieceList.length === 0 || this.state.page !== page){ - PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search, - orderBy, this.state.orderAsc, this.state.filterBy); + let page = this.props.location.query.page || 1; + if (this.props.canLoadPieceList && (this.state.pieceList.length === 0 || this.state.page !== page)) { + this.loadPieceList({ page }); + } + }, + + componentWillReceiveProps(nextProps) { + let filterBy; + let page = this.props.location.query.page || 1; + + // If the user hasn't changed the filter and the new default filter is different + // than the current filter, apply the new default filter + if (!this.state.isFilterDirty) { + const newDefaultFilterBy = this.getDefaultFilterBy(nextProps); + + // Only need to check shallowly since the filterBy shouldn't be nested + if (!isShallowEqual(this.state.filterBy, newDefaultFilterBy)) { + filterBy = newDefaultFilterBy; + page = 1; + } + } + + // Only load if we are applying a new filter or if it's the first time we can + // load the piece list + if (nextProps.canLoadPieceList && (filterBy || !this.props.canLoadPieceList)) { + this.loadPieceList({ page, filterBy }); } }, @@ -102,17 +128,19 @@ let PieceList = React.createClass({ this.setState(state); }, - getDefaultFilterBy() { - const { filterParams } = this.props; + getDefaultFilterBy(props = this.props) { + const { filterParams } = props; const defaultFilterBy = {}; - filterParams.forEach(({ label, items }) => { - items.forEach((item) => { - if (typeof item === 'object' && item.defaultValue) { - defaultFilterBy[item.key] = true; - } + if (filterParams && typeof filterParams.forEach === 'function') { + filterParams.forEach(({ label, items }) => { + items.forEach((item) => { + if (typeof item === 'object' && item.defaultValue) { + defaultFilterBy[item.key] = true; + } + }); }); - }); + } return defaultFilterBy; }, @@ -122,9 +150,7 @@ let PieceList = React.createClass({ // if the users clicks a pager of the pagination, // the site should go to the top document.body.scrollTop = document.documentElement.scrollTop = 0; - PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search, - this.state.orderBy, this.state.orderAsc, - this.state.filterBy); + this.loadPieceList({ page }); }; }, @@ -143,29 +169,35 @@ let PieceList = React.createClass({ }, searchFor(searchTerm) { - PieceListActions.fetchPieceList(1, this.state.pageSize, searchTerm, this.state.orderBy, - this.state.orderAsc, this.state.filterBy); - this.history.pushState(null, this.props.location.pathname, {page: 1}); + this.loadPieceList({ + page: 1, + search: searchTerm + }); + this.history.pushState(null, this.props.location.pathname, {page: 1}); }, applyFilterBy(filterBy){ - // first we need to apply the filter on the piece list - PieceListActions.fetchPieceList(1, this.state.pageSize, this.state.search, - this.state.orderBy, this.state.orderAsc, filterBy) - .then(() => { - // but also, we need to filter all the open edition lists - this.state.pieceList - .forEach((piece) => { - // but only if they're actually open - if(this.state.isEditionListOpenForPieceId[piece.id].show) { - EditionListActions.refreshEditionList({ - pieceId: piece.id, - filterBy - }); - } + this.setState({ + isFilterDirty: true + }); - }); - }); + // first we need to apply the filter on the piece list + this + .loadPieceList({ page: 1, filterBy }) + .then(() => { + // but also, we need to filter all the open edition lists + this.state.pieceList + .forEach((piece) => { + // but only if they're actually open + if(this.state.isEditionListOpenForPieceId[piece.id].show) { + EditionListActions.refreshEditionList({ + pieceId: piece.id, + filterBy + }); + } + + }); + }); // we have to redirect the user always to page one as it could be that there is no page two // for filtered pieces @@ -177,6 +209,13 @@ let PieceList = React.createClass({ orderBy, this.state.orderAsc, this.state.filterBy); }, + loadPieceList({ page, filterBy = this.state.filterBy, search = this.state.search }) { + let orderBy = this.state.orderBy ? this.state.orderBy : this.props.orderBy; + + return PieceListActions.fetchPieceList(page, this.state.pageSize, search, + orderBy, this.state.orderAsc, filterBy); + }, + fetchSelectedPieceEditionList() { let filteredPieceIdList = Object.keys(this.state.editionList) .filter((pieceId) => { diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js index 279327d5..e68b1781 100644 --- a/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js +++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js @@ -21,6 +21,10 @@ let LumenusLanding = React.createClass({ ); }, + componentWillMount() { + setDocumentTitle('Lumenus Marketplace'); + }, + componentDidMount() { WhitelabelStore.listen(this.onChange); WhitelabelActions.fetchWhitelabel(); @@ -35,8 +39,6 @@ let LumenusLanding = React.createClass({ }, render() { - setDocumentTitle('Lumenus Marketplace'); - return (
    diff --git a/js/components/whitelabel/wallet/components/market/market_piece_list.js b/js/components/whitelabel/wallet/components/market/market_piece_list.js index 90564f2b..64198bb3 100644 --- a/js/components/whitelabel/wallet/components/market/market_piece_list.js +++ b/js/components/whitelabel/wallet/components/market/market_piece_list.js @@ -27,6 +27,10 @@ let MarketPieceList = React.createClass({ ); }, + componentWillMount() { + setDocumentTitle(getLangText('Collection')); + }, + componentDidMount() { UserStore.listen(this.onChange); WhitelabelStore.listen(this.onChange); @@ -46,23 +50,30 @@ let MarketPieceList = React.createClass({ render() { const { currentUser, whitelabel } = this.state; - const isUserAdmin = currentUser.email === whitelabel.user; + let filterParams = undefined; + let canLoadPieceList = false; - setDocumentTitle(getLangText('Collection')); + if (currentUser.email && whitelabel.user) { + canLoadPieceList = true; + const isUserAdmin = currentUser.email === whitelabel.user; + + filterParams = [{ + label: getLangText('Show works I can'), + items: [{ + key: isUserAdmin ? 'acl_transfer' : 'acl_consign', + label: getLangText(isUserAdmin ? 'transfer' : 'consign to %s', whitelabel.name), + defaultValue: true + }] + }]; + } return ( + filterParams={filterParams} + location={this.props.location} /> ); } }); diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js index 8283f8cf..e81a806d 100644 --- a/js/utils/general_utils.js +++ b/js/utils/general_utils.js @@ -1,5 +1,11 @@ 'use strict'; +/** + * Checks shallow equality + * Re-export of shallow from shallow-equals + */ +export { default as isShallowEqual } from 'shallow-equals'; + /** * Takes an object and returns a shallow copy without any keys * that fail the passed in filter function. diff --git a/package.json b/package.json index 63c6d1e0..fbae7243 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "react-star-rating": "~1.3.2", "react-textarea-autosize": "^2.5.2", "reactify": "^1.1.0", + "shallow-equals": "0.0.0", "shmui": "^0.1.0", "spark-md5": "~1.0.0", "uglifyjs": "^2.4.10", From 5423f5dcd7b17bdabcd25f11f39f2974a74e0d27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Wed, 25 Nov 2015 11:16:09 +0100 Subject: [PATCH 044/115] Bug fix for collapsing nav --- js/components/contract_notification.js | 36 -------------------------- js/components/header.js | 20 +++++++++++--- 2 files changed, 17 insertions(+), 39 deletions(-) delete mode 100644 js/components/contract_notification.js diff --git a/js/components/contract_notification.js b/js/components/contract_notification.js deleted file mode 100644 index cd6ceb53..00000000 --- a/js/components/contract_notification.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -import React from 'react'; - -import NotificationStore from '../stores/notification_store'; - -import { mergeOptions } from '../utils/general_utils'; - -let ContractNotification = React.createClass({ - getInitialState() { - return mergeOptions( - NotificationStore.getState() - ); - }, - - componentDidMount() { - NotificationStore.listen(this.onChange); - }, - - componentWillUnmount() { - NotificationStore.unlisten(this.onChange); - }, - - onChange(state) { - this.setState(state); - }, - - render() { - - return ( - null - ); - } -}); - -export default ContractNotification; \ No newline at end of file diff --git a/js/components/header.js b/js/components/header.js index 51f91318..7c2331f9 100644 --- a/js/components/header.js +++ b/js/components/header.js @@ -1,9 +1,10 @@ 'use strict'; import React from 'react'; - import { Link } from 'react-router'; +import history from '../history'; + import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; import CollapsibleNav from 'react-bootstrap/lib/CollapsibleNav'; @@ -58,11 +59,17 @@ let Header = React.createClass({ UserStore.listen(this.onChange); WhitelabelActions.fetchWhitelabel(); WhitelabelStore.listen(this.onChange); + + // react-bootstrap 0.25.1 has a bug in which it doesn't + // close the mobile expanded navigation after a click by itself. + // To get rid of this, we set the state of the component ourselves. + history.listen(this.onRouteChange); }, componentWillUnmount() { UserStore.unlisten(this.onChange); WhitelabelStore.unlisten(this.onChange); + history.unlisten(this.onRouteChange); }, getLogo() { @@ -135,6 +142,11 @@ let Header = React.createClass({ this.refs.dropdownbutton.setDropdownState(false); }, + // On route change, close expanded navbar again + onRouteChange() { + this.refs.navbar.state.navExpanded = false; + }, + render() { let account; let signup; @@ -201,8 +213,10 @@ let Header = React.createClass({ - + fixedTop={true} + ref="navbar"> + From 121a1dbb8d5ce7b176e841ce93a738af1b9ae95b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Wed, 25 Nov 2015 15:00:56 +0100 Subject: [PATCH 045/115] Bump react-router to 1.0.0 because of found bug --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 63c6d1e0..83ce69e3 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "react": "0.13.2", "react-bootstrap": "0.25.1", "react-datepicker": "^0.12.0", - "react-router": "^1.0.0-rc3", + "react-router": "1.0.0", "react-router-bootstrap": "^0.19.0", "react-star-rating": "~1.3.2", "react-textarea-autosize": "^2.5.2", From 88d1edd45c78e1e971de3c2e69d3e2cc73a12f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Wed, 25 Nov 2015 15:01:23 +0100 Subject: [PATCH 046/115] Add pageExitWarning for SlidesContainer --- .../proxy_routes/auth_proxy_handler.js | 6 ++++-- .../ascribe_slides_container/slides_container.js | 14 ++++++++++---- .../components/ikonotv/ikonotv_register_piece.js | 13 +++++++++---- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js b/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js index b2d552a7..0eb4ad8f 100644 --- a/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js +++ b/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js @@ -1,7 +1,7 @@ 'use strict'; import React from 'react'; -import { History } from 'react-router'; +import { History, RouteContext } from 'react-router'; import UserStore from '../../../stores/user_store'; import UserActions from '../../../actions/user_actions'; @@ -37,7 +37,9 @@ export default function AuthProxyHandler({to, when}) { location: object }, - mixins: [History], + // We need insert `RouteContext` here in order to be able + // to use the `Lifecycle` widget in further down nested components + mixins: [History, RouteContext], getInitialState() { return UserStore.getState(); diff --git a/js/components/ascribe_slides_container/slides_container.js b/js/components/ascribe_slides_container/slides_container.js index 8ed66c1d..39d515a3 100644 --- a/js/components/ascribe_slides_container/slides_container.js +++ b/js/components/ascribe_slides_container/slides_container.js @@ -1,7 +1,7 @@ 'use strict'; import React from 'react/addons'; -import { History } from 'react-router'; +import { History, Lifecycle } from 'react-router'; import SlidesContainerBreadcrumbs from './slides_container_breadcrumbs'; @@ -17,14 +17,16 @@ const SlidesContainer = React.createClass({ pending: string, complete: string }), - location: object + location: object, + pageExitWarning: string }, - mixins: [History], + mixins: [History, Lifecycle], getInitialState() { return { - containerWidth: 0 + containerWidth: 0, + pageExitWarning: null }; }, @@ -41,6 +43,10 @@ const SlidesContainer = React.createClass({ window.removeEventListener('resize', this.handleContainerResize); }, + routerWillLeave() { + return this.props.pageExitWarning; + }, + handleContainerResize() { this.setState({ // +30 to get rid of the padding of the container which is 15px + 15px left and right diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js index 98fe6715..d3f93ddf 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js @@ -32,6 +32,7 @@ import ApiUrls from '../../../../../constants/api_urls'; import { mergeOptions } from '../../../../../utils/general_utils'; import { getLangText } from '../../../../../utils/lang_utils'; + let IkonotvRegisterPiece = React.createClass({ propTypes: { handleSuccess: React.PropTypes.func, @@ -47,7 +48,8 @@ let IkonotvRegisterPiece = React.createClass({ PieceListStore.getState(), PieceStore.getState(), { - step: 0 + step: 0, + pageExitWarning: getLangText("If you leave this form now, your work will not be loaned to Ikono TV.") }); }, @@ -94,7 +96,6 @@ let IkonotvRegisterPiece = React.createClass({ handleRegisterSuccess(response){ - this.refreshPieceList(); // also start loading the piece for the next step @@ -108,7 +109,6 @@ let IkonotvRegisterPiece = React.createClass({ this.incrementStep(); this.refs.slidesContainer.nextSlide(); } - }, handleAdditionalDataSuccess() { @@ -126,6 +126,8 @@ let IkonotvRegisterPiece = React.createClass({ }, handleLoanSuccess(response) { + this.setState({ pageExitWarning: null }); + let notification = new GlobalNotificationModel(response.notification, 'success', 10000); GlobalNotificationActions.appendGlobalNotification(notification); @@ -238,6 +240,8 @@ let IkonotvRegisterPiece = React.createClass({ }, render() { + const { pageExitWarning } = this.state; + return ( + location={this.props.location} + pageExitWarning={pageExitWarning}>
    From e482caaa994c579fb68602ae7c90f4e3077db4bb Mon Sep 17 00:00:00 2001 From: Cevo Date: Wed, 25 Nov 2015 21:43:15 +0100 Subject: [PATCH 047/115] public note is visible outside --- js/components/ascribe_detail/piece_container.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index d0ca2e1e..6fb7b16c 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -259,12 +259,12 @@ let PieceContainer = React.createClass({ + show={(!!(this.state.currentUser.username || this.state.piece.public_note))}> Date: Thu, 26 Nov 2015 11:45:40 +0100 Subject: [PATCH 048/115] Bump Rackt/History to 1.13.1 Gracefully handle when sessionStorage is unavailable --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 63c6d1e0..30d70815 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "gulp-uglify": "^1.2.0", "gulp-util": "^3.0.4", "harmonize": "^1.4.2", - "history": "^1.11.1", + "history": "^1.13.1", "invariant": "^2.1.1", "isomorphic-fetch": "^2.0.2", "jest-cli": "^0.4.0", From 1c25f8e7765aaba2eb4f663200e3a9dbdb40f1e7 Mon Sep 17 00:00:00 2001 From: diminator Date: Fri, 27 Nov 2015 03:24:37 +0100 Subject: [PATCH 049/115] webhooks in settings without acl --- js/actions/webhook_actions.js | 47 ++++++ .../ascribe_settings/settings_container.js | 2 + .../ascribe_settings/webhook_settings.js | 151 ++++++++++++++++++ js/constants/api_urls.js | 3 + js/fetchers/webhook_fetcher.js | 23 +++ js/stores/webhook_store.js | 27 ++++ 6 files changed, 253 insertions(+) create mode 100644 js/actions/webhook_actions.js create mode 100644 js/components/ascribe_settings/webhook_settings.js create mode 100644 js/fetchers/webhook_fetcher.js create mode 100644 js/stores/webhook_store.js diff --git a/js/actions/webhook_actions.js b/js/actions/webhook_actions.js new file mode 100644 index 00000000..4e5b2462 --- /dev/null +++ b/js/actions/webhook_actions.js @@ -0,0 +1,47 @@ +'use strict'; + +import { alt } from '../alt'; +import WebhookFetcher from '../fetchers/webhook_fetcher'; + + +class WebhookActions { + constructor() { + this.generateActions( + 'updateWebhooks', + 'updateEvents', + 'removeWebhook' + ); + } + + fetchWebhooks() { + WebhookFetcher.fetch() + .then((res) => { + this.actions.updateWebhooks(res.webhooks); + }) + .catch((err) => { + console.logGlobal(err); + }); + } + + fetchWebhookEvents() { + WebhookFetcher.fetchEvents() + .then((res) => { + this.actions.updateEvents(res.events); + }) + .catch((err) => { + console.logGlobal(err); + }); + } + + deleteWebhook(id){ + WebhookFetcher.deleteWebhook(id) + .then((res) => { + this.actions.removeWebhook(id); + }) + .catch((err) => { + console.logGlobal(err); + }); + } +} + +export default alt.createActions(WebhookActions); diff --git a/js/components/ascribe_settings/settings_container.js b/js/components/ascribe_settings/settings_container.js index 5b05e708..35a6fbe5 100644 --- a/js/components/ascribe_settings/settings_container.js +++ b/js/components/ascribe_settings/settings_container.js @@ -11,6 +11,7 @@ import WhitelabelActions from '../../actions/whitelabel_actions'; import AccountSettings from './account_settings'; import BitcoinWalletSettings from './bitcoin_wallet_settings'; import APISettings from './api_settings'; +import WebhookSettings from './webhook_settings'; import AclProxy from '../acl_proxy'; @@ -70,6 +71,7 @@ let SettingsContainer = React.createClass({ aclName="acl_view_settings_api"> + diff --git a/js/components/ascribe_settings/webhook_settings.js b/js/components/ascribe_settings/webhook_settings.js new file mode 100644 index 00000000..1f9eefc5 --- /dev/null +++ b/js/components/ascribe_settings/webhook_settings.js @@ -0,0 +1,151 @@ +'use strict'; + +import React from 'react'; + +import WebhookStore from '../../stores/webhook_store'; +import WebhookActions from '../../actions/webhook_actions'; + +import GlobalNotificationModel from '../../models/global_notification_model'; +import GlobalNotificationActions from '../../actions/global_notification_actions'; + +import Form from '../ascribe_forms/form'; +import Property from '../ascribe_forms/property'; + +import ActionPanel from '../ascribe_panel/action_panel'; +import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph'; + +import ApiUrls from '../../constants/api_urls'; +import AscribeSpinner from '../ascribe_spinner'; + +import { getLangText } from '../../utils/lang_utils'; + + +let WebhookSettings = React.createClass({ + propTypes: { + defaultExpanded: React.PropTypes.bool + }, + + getInitialState() { + return WebhookStore.getState(); + }, + + componentDidMount() { + WebhookStore.listen(this.onChange); + WebhookActions.fetchWebhooks(); + WebhookActions.fetchWebhookEvents(); + }, + + componentWillUnmount() { + WebhookStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + onDeleteWebhook(event) { + let webhookId = event.target.getAttribute('data-id'); + WebhookActions.deleteWebhook(webhookId); + + let notification = new GlobalNotificationModel(getLangText('Webhook deleted'), 'success', 2000); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + + handleCreateSuccess() { + WebhookActions.fetchWebhooks(); + let notification = new GlobalNotificationModel(getLangText('Webhook successfully created'), 'success', 5000); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + + getWebhooks(){ + let content = ; + + if (this.state.webhooks.length > -1) { + content = this.state.webhooks.map(function(webhook, i) { + const event = webhook.event.split('.')[0]; + return ( + +
    + {event.toUpperCase()} +
    +
    + {webhook.target} +
    +
    + } + buttons={ +
    +
    + +
    +
    + }/> + ); + }, this); + } + return content; + }, + + getEvents() { + if (this.state.events && this.state.events.length > 1) { + return ( + + + ); + } + return null; + }, + + + render() { + return ( + +
    + { this.getEvents() } + + + +
    +
    +
    +                    Usage: curl <url> -H 'Authorization: Bearer <token>'
    +                
    + {this.getWebhooks()} +
    + ); + } +}); + +export default WebhookSettings; \ No newline at end of file diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js index a07f29b1..e7f11141 100644 --- a/js/constants/api_urls.js +++ b/js/constants/api_urls.js @@ -72,6 +72,9 @@ let ApiUrls = { 'users_username': AppConstants.apiEndpoint + 'users/username/', 'users_profile': AppConstants.apiEndpoint + 'users/profile/', 'wallet_settings': AppConstants.apiEndpoint + 'users/wallet_settings/', + 'webhook': AppConstants.apiEndpoint + 'webhooks/${webhook_id}/', + 'webhooks': AppConstants.apiEndpoint + 'webhooks/', + 'webhooks_events': AppConstants.apiEndpoint + 'webhooks/events/', 'whitelabel_settings': AppConstants.apiEndpoint + 'whitelabel/settings/${subdomain}/', 'delete_s3_file': AppConstants.serverUrl + 's3/delete/', 'prize_list': AppConstants.apiEndpoint + 'prize/' diff --git a/js/fetchers/webhook_fetcher.js b/js/fetchers/webhook_fetcher.js new file mode 100644 index 00000000..20775c81 --- /dev/null +++ b/js/fetchers/webhook_fetcher.js @@ -0,0 +1,23 @@ +'use strict'; + +import requests from '../utils/requests'; + +let WebhookFetcher = { + /** + * Fetch the registered webhooks of a user from the API. + */ + fetch() { + return requests.get('webhooks'); + }, + + deleteWebhook(id) { + return requests.delete('webhook', {'webhook_id': id }); + }, + + fetchEvents() { + return requests.get('webhooks_events'); + } + +}; + +export default WebhookFetcher; diff --git a/js/stores/webhook_store.js b/js/stores/webhook_store.js new file mode 100644 index 00000000..95b081a6 --- /dev/null +++ b/js/stores/webhook_store.js @@ -0,0 +1,27 @@ +'use strict'; + +import { alt } from '../alt'; +import WebhookActions from '../actions/webhook_actions'; + + +class WebhookStore { + constructor() { + this.webhooks = {}; + this.events = {}; + this.bindActions(WebhookActions); + } + + onUpdateWebhooks(webhooks) { + this.webhooks = webhooks; + } + + onUpdateEvents(events) { + this.events = events; + } + + onRemoveWebhook(id) { + this.webhooks = this.webhooks.filter((webhook) => webhook.id !== parseInt(id)); + } +} + +export default alt.createStore(WebhookStore, 'WebhookStore'); From 7146f00c0510566a00f8a91f79a84c1e844dcdb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Fri, 27 Nov 2015 13:36:42 +0100 Subject: [PATCH 050/115] Refactor webhooks: add source, clear state on create hide create on no items left --- js/actions/webhook_actions.js | 40 ++-------- .../ascribe_settings/webhook_settings.js | 65 +++++++++-------- .../components/pr_login_container.js | 0 js/fetchers/webhook_fetcher.js | 23 ------ js/sources/webhook_source.js | 46 ++++++++++++ js/stores/webhook_store.js | 73 +++++++++++++++++-- 6 files changed, 153 insertions(+), 94 deletions(-) create mode 100644 js/components/whitelabel/prize/portfolioreview/components/pr_login_container.js delete mode 100644 js/fetchers/webhook_fetcher.js create mode 100644 js/sources/webhook_source.js diff --git a/js/actions/webhook_actions.js b/js/actions/webhook_actions.js index 4e5b2462..f9555ce7 100644 --- a/js/actions/webhook_actions.js +++ b/js/actions/webhook_actions.js @@ -1,47 +1,19 @@ 'use strict'; import { alt } from '../alt'; -import WebhookFetcher from '../fetchers/webhook_fetcher'; class WebhookActions { constructor() { this.generateActions( - 'updateWebhooks', - 'updateEvents', - 'removeWebhook' + 'fetchWebhooks', + 'successFetchWebhooks', + 'fetchWebhookEvents', + 'successFetchWebhookEvents', + 'removeWebhook', + 'successRemoveWebhook' ); } - - fetchWebhooks() { - WebhookFetcher.fetch() - .then((res) => { - this.actions.updateWebhooks(res.webhooks); - }) - .catch((err) => { - console.logGlobal(err); - }); - } - - fetchWebhookEvents() { - WebhookFetcher.fetchEvents() - .then((res) => { - this.actions.updateEvents(res.events); - }) - .catch((err) => { - console.logGlobal(err); - }); - } - - deleteWebhook(id){ - WebhookFetcher.deleteWebhook(id) - .then((res) => { - this.actions.removeWebhook(id); - }) - .catch((err) => { - console.logGlobal(err); - }); - } } export default alt.createActions(WebhookActions); diff --git a/js/components/ascribe_settings/webhook_settings.js b/js/components/ascribe_settings/webhook_settings.js index 1f9eefc5..18c21aa6 100644 --- a/js/components/ascribe_settings/webhook_settings.js +++ b/js/components/ascribe_settings/webhook_settings.js @@ -11,6 +11,8 @@ import GlobalNotificationActions from '../../actions/global_notification_actions import Form from '../ascribe_forms/form'; import Property from '../ascribe_forms/property'; +import AclProxy from '../acl_proxy'; + import ActionPanel from '../ascribe_panel/action_panel'; import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph'; @@ -43,16 +45,18 @@ let WebhookSettings = React.createClass({ this.setState(state); }, - onDeleteWebhook(event) { - let webhookId = event.target.getAttribute('data-id'); - WebhookActions.deleteWebhook(webhookId); + onRemoveWebhook(webhookId) { + return (event) => { + WebhookActions.removeWebhook(webhookId); - let notification = new GlobalNotificationModel(getLangText('Webhook deleted'), 'success', 2000); - GlobalNotificationActions.appendGlobalNotification(notification); + let notification = new GlobalNotificationModel(getLangText('Webhook deleted'), 'success', 2000); + GlobalNotificationActions.appendGlobalNotification(notification); + }; }, handleCreateSuccess() { - WebhookActions.fetchWebhooks(); + this.refs.webhookCreateForm.reset(); + WebhookActions.fetchWebhooks(true); let notification = new GlobalNotificationModel(getLangText('Webhook successfully created'), 'success', 5000); GlobalNotificationActions.appendGlobalNotification(notification); }, @@ -60,7 +64,7 @@ let WebhookSettings = React.createClass({ getWebhooks(){ let content = ; - if (this.state.webhooks.length > -1) { + if (this.state.webhooks) { content = this.state.webhooks.map(function(webhook, i) { const event = webhook.event.split('.')[0]; return ( @@ -82,8 +86,7 @@ let WebhookSettings = React.createClass({
    @@ -96,14 +99,13 @@ let WebhookSettings = React.createClass({ }, getEvents() { - if (this.state.events && this.state.events.length > 1) { + if (this.state.webhookEvents && this.state.webhookEvents.length) { return ( - + {this.state.webhookEvents.map((event, i) => { return (