From 5a0663ea1414ecf3f7645b338fff028b54d9722c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Tue, 14 Jul 2015 16:29:01 +0200 Subject: [PATCH] unshare and delete functionality + store updates for all views --- .../ascribe_buttons/delete_button.js | 51 ++++++++++++++----- js/components/ascribe_detail/edition.js | 2 + js/components/ascribe_detail/piece.js | 29 +++++++++++ .../ascribe_forms/form_delete_piece.js | 41 +++++++++++++++ .../form_remove_editions_from_collection.js | 8 +-- .../form_remove_piece_from_collection.js | 42 +++++++++++++++ js/components/ascribe_modal/modal_wrapper.js | 47 +++++++++++------ js/utils/acl_utils.js | 2 +- js/utils/requests.js | 14 ++++- 9 files changed, 201 insertions(+), 35 deletions(-) create mode 100644 js/components/ascribe_forms/form_delete_piece.js create mode 100644 js/components/ascribe_forms/form_remove_piece_from_collection.js diff --git a/js/components/ascribe_buttons/delete_button.js b/js/components/ascribe_buttons/delete_button.js index 0407c647..bd319a2b 100644 --- a/js/components/ascribe_buttons/delete_button.js +++ b/js/components/ascribe_buttons/delete_button.js @@ -6,7 +6,11 @@ import Router from 'react-router'; import Button from 'react-bootstrap/lib/Button'; import EditionDeleteForm from '../ascribe_forms/form_delete_edition'; +import PieceDeleteForm from '../ascribe_forms/form_delete_piece'; + import EditionRemoveFromCollectionForm from '../ascribe_forms/form_remove_editions_from_collection'; +import PieceRemoveFromCollectionForm from '../ascribe_forms/form_remove_piece_from_collection'; + import ModalWrapper from '../ascribe_modal/modal_wrapper'; import { getAvailableAcls } from '../../utils/acl_utils'; @@ -15,23 +19,47 @@ import { getLangText } from '../../utils/lang_utils.js'; let DeleteButton = React.createClass({ propTypes: { - editions: React.PropTypes.array.isRequired, + editions: React.PropTypes.array, + piece: React.PropTypes.object, handleSuccess: React.PropTypes.func }, mixins: [Router.Navigation], render: function () { - let availableAcls = getAvailableAcls(this.props.editions); - let btnDelete = null; - let content = null; + let availableAcls; + let btnDelete; + let content; + let title; - if (availableAcls.acl_delete) { - content = ; - btnDelete = ; + if(this.props.piece && !this.props.editions) { + availableAcls = getAvailableAcls([this.props.piece]); + } else { + availableAcls = getAvailableAcls(this.props.editions); } - else if (availableAcls.acl_unshare || (this.props.editions.constructor !== Array && this.props.editions.acl.acl_unshare)){ - content = ; + + if(availableAcls.acl_delete) { + + if(this.props.piece && !this.props.editions) { + content = ; + title = getLangText('Remove Piece'); + } else { + content = ; + title = getLangText('Remove Edition'); + } + + btnDelete = ; + + } else if(availableAcls.acl_unshare){ + + if(this.props.editions && this.props.editions.constructor !== Array && this.props.editions.acl.acl_unshare) { + content = ; + title = getLangText('Remove Edition from Collection'); + } else { + content = ; + title = getLangText('Remove Piece from Collection'); + } + btnDelete = ; } else { @@ -41,9 +69,8 @@ let DeleteButton = React.createClass({ - { content } + title={title}> + {content} ); } diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index 73595c50..8ff81eb1 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -63,11 +63,13 @@ let Edition = React.createClass({ componentDidMount() { UserStore.listen(this.onChange); + PieceListStore.listen(this.onChange); UserActions.fetchCurrentUser(); }, componentWillUnmount() { UserStore.unlisten(this.onChange); + PieceListStore.unlisten(this.onChange); }, onChange(state) { diff --git a/js/components/ascribe_detail/piece.js b/js/components/ascribe_detail/piece.js index b43b0173..0eae0579 100644 --- a/js/components/ascribe_detail/piece.js +++ b/js/components/ascribe_detail/piece.js @@ -1,6 +1,7 @@ 'use strict'; import React from 'react'; +import Router from 'react-router'; import Row from 'react-bootstrap/lib/Row'; import Col from 'react-bootstrap/lib/Col'; @@ -14,6 +15,11 @@ import FurtherDetails from './further_details'; import UserActions from '../../actions/user_actions'; import UserStore from '../../stores/user_store'; +import PieceListActions from '../../actions/piece_list_actions'; +import PieceListStore from '../../stores/piece_list_store'; + +import EditionListActions from '../../actions/edition_list_actions'; + import PieceActions from '../../actions/piece_actions'; import MediaContainer from './media_container'; @@ -23,6 +29,7 @@ import EditionDetailProperty from './detail_property'; import AclButtonList from './../ascribe_buttons/acl_button_list'; import CreateEditionsForm from '../ascribe_forms/create_editions_form'; import CreateEditionsButton from '../ascribe_buttons/create_editions_button'; +import DeleteButton from '../ascribe_buttons/delete_button'; import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationActions from '../../actions/global_notification_actions'; @@ -39,9 +46,12 @@ let Piece = React.createClass({ loadPiece: React.PropTypes.func }, + mixins: [Router.Navigation], + getInitialState() { return mergeOptions( UserStore.getState(), + PieceListStore.getState(), { showCreateEditionsDialog: false } @@ -50,11 +60,13 @@ let Piece = React.createClass({ componentDidMount() { UserStore.listen(this.onChange); + PieceListStore.listen(this.onChange); UserActions.fetchCurrentUser(); }, componentWillUnmount() { UserStore.unlisten(this.onChange); + PieceListStore.unlisten(this.onChange); }, onChange(state) { @@ -72,6 +84,20 @@ let Piece = React.createClass({ this.toggleCreateEditionsDialog(); }, + handleDeleteSuccess(response) { + PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc); + + // since we're deleting a piece, we just need to close + // all editions dialogs and not reload them + EditionListActions.closeAllEditionLists(); + EditionListActions.clearAllEditionSelections(); + + let notification = new GlobalNotificationModel(response.notification, 'success'); + GlobalNotificationActions.appendGlobalNotification(notification); + + this.transitionTo('pieces'); + }, + getCreateEditionsDialog() { if(this.props.piece.num_editions < 1 && this.state.showCreateEditionsDialog) { return ( @@ -127,6 +153,9 @@ let Piece = React.createClass({ piece={this.props.piece} toggleCreateEditionsDialog={this.toggleCreateEditionsDialog} onPollingSuccess={this.handlePollingSuccess}/> + {this.getCreateEditionsDialog()} diff --git a/js/components/ascribe_forms/form_delete_piece.js b/js/components/ascribe_forms/form_delete_piece.js new file mode 100644 index 00000000..168d9261 --- /dev/null +++ b/js/components/ascribe_forms/form_delete_piece.js @@ -0,0 +1,41 @@ +'use strict'; + +import React from 'react'; + +import requests from '../../utils/requests'; +import ApiUrls from '../../constants/api_urls'; +import FormMixin from '../../mixins/form_mixin'; +import { getLangText } from '../../utils/lang_utils'; + +let PieceDeleteForm = React.createClass({ + propTypes: { + pieceId: React.PropTypes.number + }, + + mixins: [FormMixin], + + url() { + return requests.prepareUrl(ApiUrls.piece, {piece_id: this.props.pieceId}); + }, + + httpVerb() { + return 'delete'; + }, + + renderForm () { + return ( +
+

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

+

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

+
+ + +
+
+ ); + } +}); + + +export default PieceDeleteForm; diff --git a/js/components/ascribe_forms/form_remove_editions_from_collection.js b/js/components/ascribe_forms/form_remove_editions_from_collection.js index e0cbcdf7..4ab8fdf7 100644 --- a/js/components/ascribe_forms/form_remove_editions_from_collection.js +++ b/js/components/ascribe_forms/form_remove_editions_from_collection.js @@ -12,13 +12,9 @@ let EditionRemoveFromCollectionForm = React.createClass({ mixins: [FormMixin], url() { - if (this.props.editions.constructor === Array) { - return requests.prepareUrl(apiUrls.edition_remove_from_collection, {edition_id: this.getBitcoinIds().join()}); - } - else { - return requests.prepareUrl(apiUrls.piece_remove_from_collection, {piece_id: this.props.editions.id}); - } + return requests.prepareUrl(apiUrls.edition_remove_from_collection, {edition_id: this.getBitcoinIds().join()}); }, + httpVerb(){ return 'delete'; }, diff --git a/js/components/ascribe_forms/form_remove_piece_from_collection.js b/js/components/ascribe_forms/form_remove_piece_from_collection.js new file mode 100644 index 00000000..905cfcf6 --- /dev/null +++ b/js/components/ascribe_forms/form_remove_piece_from_collection.js @@ -0,0 +1,42 @@ +'use strict'; + +import React from 'react'; + +import { getLangText } from '../../utils/lang_utils.js'; +import requests from '../../utils/requests'; +import apiUrls from '../../constants/api_urls'; +import FormMixin from '../../mixins/form_mixin'; + +let PieceRemoveFromCollectionForm = React.createClass({ + + propTypes: { + pieceId: React.PropTypes.number + }, + + mixins: [FormMixin], + + url() { + return requests.prepareUrl(apiUrls.piece_remove_from_collection, {piece_id: this.props.pieceId}); + }, + + httpVerb(){ + return 'delete'; + }, + + renderForm () { + return ( +
+

{getLangText('Are you sure you would like to remove this piece from your collection')}?

+

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

+
+ + +
+
+ ); + } +}); + + +export default PieceRemoveFromCollectionForm; diff --git a/js/components/ascribe_modal/modal_wrapper.js b/js/components/ascribe_modal/modal_wrapper.js index e8dcbaa3..a8f7b182 100644 --- a/js/components/ascribe_modal/modal_wrapper.js +++ b/js/components/ascribe_modal/modal_wrapper.js @@ -17,24 +17,41 @@ let ModalWrapper = React.createClass({ handleSuccess: React.PropTypes.func.isRequired, button: React.PropTypes.object.isRequired, children: React.PropTypes.object, - tooltip: React.PropTypes.string.isRequired + tooltip: React.PropTypes.string + }, + + getModalTrigger() { + return ( + + {this.props.children} + + }> + {this.props.button} + + ); }, render() { - return ( - {this.props.tooltip}}> - - {this.props.children} - - }> - {this.props.button} - - - ); + if(this.props.tooltip) { + return ( + {this.props.tooltip}}> + {this.getModalTrigger()} + + ); + } else { + return ( + + {/* This needs to be some kind of inline-block */} + {this.getModalTrigger()} + + ); + } } }); diff --git a/js/utils/acl_utils.js b/js/utils/acl_utils.js index 3fc7bb71..f0075f55 100644 --- a/js/utils/acl_utils.js +++ b/js/utils/acl_utils.js @@ -8,7 +8,7 @@ function intersectAcls(a, b) { export function getAvailableAcls(editions, filterFn) { let availableAcls = []; - if (editions.constructor !== Array){ + if (!editions || editions.constructor !== Array){ return []; } // if you copy a javascript array of objects using slice, then diff --git a/js/utils/requests.js b/js/utils/requests.js index 40c24058..9d2104d1 100644 --- a/js/utils/requests.js +++ b/js/utils/requests.js @@ -54,6 +54,11 @@ class Requests { } getUrl(url) { + // Handle case, that the url string is not defined at all + if (!url) { + throw new Error('Url was not defined and could therefore not be mapped.'); + } + let name = url; if (!url.match(/^http/)) { url = this.urlMap[url]; @@ -66,9 +71,16 @@ class Requests { } prepareUrl(url, params, attachParamsToQuery) { - let newUrl = this.getUrl(url); + let newUrl; let re = /\${(\w+)}/g; + // catch errors and throw them to react + try { + newUrl = this.getUrl(url); + } catch(err) { + throw err; + } + newUrl = newUrl.replace(re, (match, key) => { let val = params[key]; if (!val) {