onion/js/components/ascribe_detail/piece_container.js

348 lines
14 KiB
JavaScript
Raw Normal View History

2015-07-08 22:54:07 +02:00
'use strict';
import React from 'react';
import { History } from 'react-router';
import Moment from 'moment';
2015-07-08 22:54:07 +02:00
import ReactError from '../../mixins/react_error';
import { ResourceNotFoundError } from '../../models/errors';
2016-01-11 17:57:35 +01:00
import EditionListActions from '../../actions/edition_list_actions';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
2015-07-08 22:54:07 +02:00
import PieceActions from '../../actions/piece_actions';
import PieceStore from '../../stores/piece_store';
2015-08-11 17:12:12 +02:00
import PieceListActions from '../../actions/piece_list_actions';
import PieceListStore from '../../stores/piece_list_store';
import FurtherDetails from './further_details';
2015-08-11 17:12:12 +02:00
import DetailProperty from './detail_property';
2015-08-20 15:50:30 +02:00
import HistoryIterator from './history_iterator';
2016-01-11 17:57:35 +01:00
import LicenseDetail from './license_detail';
import Note from './note';
import Piece from './piece';
2015-08-11 17:12:12 +02:00
import AclButtonList from './../ascribe_buttons/acl_button_list';
2016-01-11 17:57:35 +01:00
import AclInformation from '../ascribe_buttons/acl_information';
2015-08-11 17:12:12 +02:00
import CreateEditionsButton from '../ascribe_buttons/create_editions_button';
import DeleteButton from '../ascribe_buttons/delete_button';
2016-01-11 17:57:35 +01:00
import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph';
2015-11-03 10:39:01 +01:00
2016-01-11 17:57:35 +01:00
import CreateEditionsForm from '../ascribe_forms/create_editions_form';
import ListRequestActions from '../ascribe_forms/list_form_request_actions';
2016-01-11 17:57:35 +01:00
import AclProxy from '../acl_proxy';
2015-08-20 14:01:02 +02:00
import ApiUrls from '../../constants/api_urls';
import AscribeSpinner from '../ascribe_spinner';
2015-08-11 17:12:12 +02:00
import { mergeOptions } from '../../utils/general_utils';
import { getLangText } from '../../utils/lang_utils';
2015-10-13 17:52:45 +02:00
import { setDocumentTitle } from '../../utils/dom_utils';
2015-07-08 22:54:07 +02:00
/**
* This is the component that implements resource/data specific functionality
*/
let PieceContainer = React.createClass({
propTypes: {
furtherDetailsType: React.PropTypes.func,
// Provided from AscribeApp
currentUser: React.PropTypes.object.isRequired,
whitelabel: React.PropTypes.object,
2016-01-11 16:26:36 +01:00
// Provided from router
location: React.PropTypes.object,
params: React.PropTypes.object
},
2015-07-08 22:54:07 +02:00
mixins: [History, ReactError],
2015-08-11 17:12:12 +02:00
getDefaultProps() {
return {
furtherDetailsType: FurtherDetails
};
},
2015-08-11 17:12:12 +02:00
getInitialState() {
return mergeOptions(
PieceListStore.getState(),
2016-02-05 10:38:59 +01:00
PieceStore.getInitialState(),
2015-08-11 17:12:12 +02:00
{
showCreateEditionsDialog: false
}
);
2015-07-08 22:54:07 +02:00
},
componentDidMount() {
2015-08-11 17:12:12 +02:00
PieceListStore.listen(this.onChange);
2015-07-08 22:54:07 +02:00
PieceStore.listen(this.onChange);
this.loadPiece();
2015-07-08 22:54:07 +02:00
},
2016-02-05 10:38:59 +01:00
// This is done to update the container when the user clicks on the prev or next
// button to update the URL parameter (and therefore to switch pieces) or
// when the user clicks on a notification while being in another piece view
componentWillReceiveProps(nextProps) {
if (this.props.params.pieceId !== nextProps.params.pieceId) {
PieceActions.flushPiece();
this.loadPiece(nextProps.params.pieceId);
}
},
2015-10-19 15:58:05 +02:00
componentDidUpdate() {
2016-02-05 10:38:59 +01:00
const { err: pieceErr } = this.state.pieceMeta;
2015-10-19 15:58:05 +02:00
2016-02-05 10:38:59 +01:00
if (pieceErr && pieceErr.json && pieceErr.json.status === 404) {
2015-12-07 11:48:28 +01:00
this.throws(new ResourceNotFoundError(getLangText("Oops, the piece you're looking for doesn't exist.")));
2015-10-19 15:58:05 +02:00
}
},
componentWillUnmount() {
2015-07-08 22:54:07 +02:00
PieceStore.unlisten(this.onChange);
2015-08-11 17:12:12 +02:00
PieceListStore.unlisten(this.onChange);
2015-07-08 22:54:07 +02:00
},
2015-08-11 17:12:12 +02:00
onChange(state) {
/*
ATTENTION:
THIS IS JUST A TEMPORARY USABILITY FIX THAT ESSENTIALLY REMOVES THE LOAN BUTTON
FROM THE PIECE DETAIL PAGE SO THAT USERS DO NOT CONFUSE A PIECE WITH AN EDITION.
IT SHOULD BE REMOVED AND REPLACED WITH A BETTER SOLUTION ASAP!
2015-08-28 14:27:02 +02:00
ALSO, WE ENABLED THE LOAN BUTTON FOR IKONOTV TO LET THEM LOAN ON A PIECE LEVEL
*/
2016-02-05 10:38:59 +01:00
if (state && state.piece && state.piece.acl && typeof state.piece.acl.acl_loan !== 'undefined') {
let pieceState = mergeOptions({}, state.piece);
pieceState.acl.acl_loan = false;
this.setState({
piece: pieceState
});
} else {
this.setState(state);
}
2015-08-11 17:12:12 +02:00
},
2015-07-08 22:54:07 +02:00
2016-02-05 10:38:59 +01:00
loadPiece(pieceId = this.props.params.pieceId) {
PieceActions.fetchPiece(pieceId);
2015-07-08 22:54:07 +02:00
},
2015-08-11 17:12:12 +02:00
toggleCreateEditionsDialog() {
this.setState({
showCreateEditionsDialog: !this.state.showCreateEditionsDialog
});
},
handleEditionCreationSuccess() {
2016-02-05 10:38:59 +01:00
const { filterBy, orderAsc, orderBy, page, pageSize, search } = this.state;
PieceActions.updateProperty({ key: 'num_editions', value: 0 });
PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
2015-08-11 17:12:12 +02:00
this.toggleCreateEditionsDialog();
},
handleDeleteSuccess(response) {
2016-02-05 10:38:59 +01:00
const { filterBy, orderAsc, orderBy, page, pageSize, search } = this.state;
PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
2015-08-11 17:12:12 +02:00
// since we're deleting a piece, we just need to close
// all editions dialogs and not reload them
EditionListActions.closeAllEditionLists();
EditionListActions.clearAllEditionSelections();
2016-02-05 10:38:59 +01:00
const notification = new GlobalNotificationModel(response.notification, 'success');
2015-08-11 17:12:12 +02:00
GlobalNotificationActions.appendGlobalNotification(notification);
2016-01-11 17:52:32 +01:00
this.history.push('/collection');
2015-08-11 17:12:12 +02:00
},
getCreateEditionsDialog() {
2016-02-05 10:38:59 +01:00
if (this.state.piece.num_editions < 1 && this.state.showCreateEditionsDialog) {
2015-08-11 17:12:12 +02:00
return (
<div style={{marginTop: '1em'}}>
<CreateEditionsForm
pieceId={this.state.piece.id}
handleSuccess={this.handleEditionCreationSuccess} />
2016-02-05 10:38:59 +01:00
<hr />
2015-08-11 17:12:12 +02:00
</div>
);
} else {
2016-02-05 10:38:59 +01:00
return (<hr />);
2015-08-11 17:12:12 +02:00
}
},
handlePollingSuccess(pieceId, numEditions) {
2016-02-05 10:38:59 +01:00
const { filterBy, orderAsc, orderBy, page, pageSize, search } = this.state;
2015-08-11 17:12:12 +02:00
// we need to refresh the num_editions property of the actual piece we're looking at
PieceActions.updateProperty({
key: 'num_editions',
value: numEditions
});
// as well as its representation in the collection
// btw.: It's not sufficient to just set num_editions to numEditions, since a single accordion
// list item also uses the firstEdition property which we can only get from the server in that case.
// Therefore we need to at least refetch the changed piece from the server or on our case simply all
2016-02-05 10:38:59 +01:00
PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
2015-08-11 17:12:12 +02:00
2016-02-05 10:38:59 +01:00
const notification = new GlobalNotificationModel(getLangText('Editions successfully created'), 'success', 10000);
2015-08-11 17:12:12 +02:00
GlobalNotificationActions.appendGlobalNotification(notification);
},
getId() {
2016-02-05 10:38:59 +01:00
return { 'id': this.state.piece.id };
},
getActions() {
const { piece } = this.state;
const { currentUser } = this.props;
2015-11-03 10:39:01 +01:00
2016-02-05 10:38:59 +01:00
if (piece.notifications && piece.notifications.length > 0) {
return (
<ListRequestActions
2015-11-03 10:39:01 +01:00
currentUser={currentUser}
2016-02-05 10:38:59 +01:00
handleSuccess={this.loadPiece}
notifications={piece.notifications}
2016-02-05 10:38:59 +01:00
pieceOrEditions={piece} />
);
2015-11-03 10:39:01 +01:00
} else {
return (
2015-11-03 10:39:01 +01:00
<AclProxy
show={currentUser && currentUser.email && Object.keys(piece.acl).length > 1}>
{/*
`acl_view` is always available in `edition.acl`, therefore if it has
no more than 1 key, we're hiding the `DetailProperty` actions as otherwise
`AclInformation` would show up
*/}
<DetailProperty
label={getLangText('ACTIONS')}
className="hidden-print">
2015-11-03 10:39:01 +01:00
<AclButtonList
availableAcls={piece.acl}
className="ascribe-button-list"
currentUser={currentUser}
2015-11-03 11:13:32 +01:00
pieceOrEditions={piece}
2015-11-03 10:39:01 +01:00
handleSuccess={this.loadPiece}>
<CreateEditionsButton
label={getLangText('CREATE EDITIONS')}
className="btn-sm"
piece={piece}
toggleCreateEditionsDialog={this.toggleCreateEditionsDialog}
onPollingSuccess={this.handlePollingSuccess}/>
<DeleteButton
handleSuccess={this.handleDeleteSuccess}
2016-02-05 10:38:59 +01:00
piece={piece} />
2015-11-03 10:39:01 +01:00
<AclInformation
aim="button"
2015-11-19 19:47:33 +01:00
verbs={['acl_share', 'acl_transfer', 'acl_create_editions', 'acl_loan', 'acl_delete',
'acl_consign']}
2016-02-05 10:38:59 +01:00
aclObject={piece.acl} />
2015-11-03 10:39:01 +01:00
</AclButtonList>
</DetailProperty>
</AclProxy>
);
}
},
2015-08-28 12:33:00 +02:00
2015-07-08 22:54:07 +02:00
render() {
2016-01-11 17:52:32 +01:00
const { currentUser, furtherDetailsType: FurtherDetailsType } = this.props;
const { piece } = this.state;
2016-02-05 10:38:59 +01:00
if (piece.id) {
setDocumentTitle(`${piece.artist_name}, ${piece.title}`);
2015-10-13 17:52:45 +02:00
2015-07-08 22:54:07 +02:00
return (
<Piece
2016-01-11 17:52:32 +01:00
piece={piece}
currentUser={currentUser}
2015-08-11 17:12:12 +02:00
header={
<div className="ascribe-detail-header">
2016-01-11 17:52:32 +01:00
<hr className="hidden-print" style={{marginTop: 0}} />
<h1 className="ascribe-detail-title">{piece.title}</h1>
2016-02-05 10:38:59 +01:00
<DetailProperty label="CREATED BY" value={piece.artist_name} />
<DetailProperty label="YEAR OF CREATION" value={Moment(piece.date_created, 'YYYY-MM-DD').year() } />
2016-01-11 17:52:32 +01:00
{piece.num_editions > 0 ? <DetailProperty label="EDITIONS" value={ piece.num_editions } /> : null}
2015-08-11 17:12:12 +02:00
<hr/>
</div>
}
subheader={
<div className="ascribe-detail-header">
2016-02-05 10:38:59 +01:00
<DetailProperty label={getLangText('ASCRIBED BY')} value={ piece.user_registered } />
2016-01-11 17:52:32 +01:00
<DetailProperty label={getLangText('ID')} value={ piece.bitcoin_id } ellipsis={true} />
<DetailProperty
label="DATE OF TIMESTAMPING"
value={Moment(piece.datetime_registered).format('MMM. DD, YYYY, h:mm:ss')} />
2016-01-11 17:52:32 +01:00
<LicenseDetail license={piece.license_type} />
2015-08-11 17:12:12 +02:00
</div>
}
buttons={this.getActions()}>
2015-08-11 17:12:12 +02:00
{this.getCreateEditionsDialog()}
2015-08-20 15:50:30 +02:00
<CollapsibleParagraph
title={getLangText('Loan History')}
2016-01-11 17:52:32 +01:00
show={piece.loan_history && piece.loan_history.length > 0}>
2015-08-20 15:50:30 +02:00
<HistoryIterator
2016-01-11 17:52:32 +01:00
history={piece.loan_history} />
2015-08-20 15:50:30 +02:00
</CollapsibleParagraph>
<CollapsibleParagraph
2015-08-21 16:38:18 +02:00
title={getLangText('Notes')}
2016-01-11 17:52:32 +01:00
show={!!(currentUser.username || piece.acl.acl_edit || piece.public_note)}>
<Note
id={this.getId}
label={getLangText('Personal note (private)')}
2016-01-11 17:52:32 +01:00
defaultValue={piece.private_note || null}
2015-08-21 16:38:18 +02:00
placeholder={getLangText('Enter your comments ...')}
editable={true}
2015-08-21 16:38:18 +02:00
successMessage={getLangText('Private note saved')}
url={ApiUrls.note_private_piece}
2016-01-11 17:52:32 +01:00
currentUser={currentUser} />
2015-10-29 15:31:25 +01:00
<Note
id={this.getId}
2015-11-20 11:11:03 +01:00
label={getLangText('Personal note (public)')}
2016-01-11 17:52:32 +01:00
defaultValue={piece.public_note || null}
2015-10-29 15:31:25 +01:00
placeholder={getLangText('Enter your comments ...')}
2016-01-11 17:52:32 +01:00
editable={!!piece.acl.acl_edit}
show={!!(piece.public_note || piece.acl.acl_edit)}
2015-11-20 11:11:03 +01:00
successMessage={getLangText('Public note saved')}
2015-10-29 15:31:25 +01:00
url={ApiUrls.note_public_piece}
2016-01-11 17:52:32 +01:00
currentUser={currentUser} />
</CollapsibleParagraph>
<CollapsibleParagraph
2015-08-21 16:38:18 +02:00
title={getLangText('Further Details')}
2016-01-11 17:52:32 +01:00
show={piece.acl.acl_edit
|| Object.keys(piece.extra_data).length > 0
|| piece.other_data.length > 0}
defaultExpanded={true}>
<FurtherDetailsType
2016-01-11 17:52:32 +01:00
editable={piece.acl.acl_edit}
pieceId={piece.id}
extraData={piece.extra_data}
otherData={piece.other_data}
handleSuccess={this.loadPiece} />
</CollapsibleParagraph>
</Piece>
2015-07-08 22:54:07 +02:00
);
} else {
return (
2015-07-13 15:00:12 +02:00
<div className="fullpage-spinner">
<AscribeSpinner color='dark-blue' size='lg'/>
2015-07-13 15:00:12 +02:00
</div>
2015-07-08 22:54:07 +02:00
);
}
}
});
export default PieceContainer;