1
0
mirror of https://github.com/ascribe/onion.git synced 2025-02-14 21:10:27 +01:00

Merge pull request #105 from ascribe/AD-1558-piece-and-edition-detail-show-stale-data

AD-1558 Piece and edition detail show stale data
This commit is contained in:
Brett Sun 2016-01-18 14:31:42 +01:00
commit 821fe79461
30 changed files with 634 additions and 546 deletions

View File

@ -7,11 +7,11 @@ class EditionActions {
constructor() { constructor() {
this.generateActions( this.generateActions(
'fetchEdition', 'fetchEdition',
'successFetchEdition',
'successFetchCoa', 'successFetchCoa',
'flushEdition', 'successFetchEdition',
'errorCoa', 'errorCoa',
'errorEdition' 'errorEdition',
'flushEdition'
); );
} }
} }

View File

@ -1,28 +1,19 @@
'use strict'; 'use strict';
import { alt } from '../alt'; import { alt } from '../alt';
import PieceFetcher from '../fetchers/piece_fetcher';
class PieceActions { class PieceActions {
constructor() { constructor() {
this.generateActions( this.generateActions(
'fetchPiece',
'successFetchPiece',
'errorPiece',
'flushPiece',
'updatePiece', 'updatePiece',
'updateProperty', 'updateProperty'
'pieceFailed'
); );
} }
fetchOne(pieceId) {
PieceFetcher.fetchOne(pieceId)
.then((res) => {
this.actions.updatePiece(res.piece);
})
.catch((err) => {
console.logGlobal(err);
this.actions.pieceFailed(err.json);
});
}
} }
export default alt.createActions(PieceActions); export default alt.createActions(PieceActions);

View File

@ -35,7 +35,7 @@ let EditionContainer = React.createClass({
getInitialState() { getInitialState() {
return mergeOptions( return mergeOptions(
EditionStore.getState(), EditionStore.getInitialState(),
UserStore.getState() UserStore.getState()
); );
}, },
@ -44,27 +44,23 @@ let EditionContainer = React.createClass({
EditionStore.listen(this.onChange); EditionStore.listen(this.onChange);
UserStore.listen(this.onChange); UserStore.listen(this.onChange);
// Every time we're entering the edition detail page, this.loadEdition();
// 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.flushEdition();
EditionActions.fetchEdition(this.props.params.editionId);
UserActions.fetchCurrentUser(); UserActions.fetchCurrentUser();
}, },
// This is done to update the container when the user clicks on the prev or next // 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) // button to update the URL parameter (and therefore to switch pieces)
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if(this.props.params.editionId !== nextProps.params.editionId) { if (this.props.params.editionId !== nextProps.params.editionId) {
EditionActions.fetchEdition(this.props.params.editionId); EditionActions.flushEdition();
this.loadEdition(nextProps.params.editionId);
} }
}, },
componentDidUpdate() { componentDidUpdate() {
const { editionMeta } = this.state; const { err: editionErr } = this.state.editionMeta;
if(editionMeta.err && editionMeta.err.json && editionMeta.err.json.status === 404) {
if (editionErr && editionErr.json && editionErr.json.status === 404) {
this.throws(new ResourceNotFoundError(getLangText("Oops, the edition you're looking for doesn't exist."))); this.throws(new ResourceNotFoundError(getLangText("Oops, the edition you're looking for doesn't exist.")));
} }
}, },
@ -81,18 +77,22 @@ let EditionContainer = React.createClass({
if(state && state.edition && state.edition.digital_work) { if(state && state.edition && state.edition.digital_work) {
let isEncoding = state.edition.digital_work.isEncoding; let isEncoding = state.edition.digital_work.isEncoding;
if (state.edition.digital_work.mime === 'video' && typeof isEncoding === 'number' && isEncoding !== 100 && !this.state.timerId) { 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); let timerId = window.setInterval(() => EditionActions.fetchEdition(this.props.params.editionId), 10000);
this.setState({timerId: timerId}); this.setState({timerId: timerId});
} }
} }
}, },
loadEdition(editionId = this.props.params.editionId) {
EditionActions.fetchEdition(editionId);
},
render() { render() {
const { edition, currentUser, coaMeta } = this.state; const { edition, currentUser, coaMeta } = this.state;
const { actionPanelButtonListType, furtherDetailsType } = this.props; const { actionPanelButtonListType, furtherDetailsType } = this.props;
if (Object.keys(edition).length && edition.id) { if (edition.id) {
setDocumentTitle([edition.artist_name, edition.title].join(', ')); setDocumentTitle(`${edition.artist_name}, ${edition.title}`);
return ( return (
<Edition <Edition
@ -101,7 +101,7 @@ let EditionContainer = React.createClass({
edition={edition} edition={edition}
coaError={coaMeta.err} coaError={coaMeta.err}
currentUser={currentUser} currentUser={currentUser}
loadEdition={() => EditionActions.fetchEdition(this.props.params.editionId)} /> loadEdition={this.loadEdition} />
); );
} else { } else {
return ( return (

View File

@ -5,25 +5,27 @@ import React from 'react';
import Row from 'react-bootstrap/lib/Row'; import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col'; import Col from 'react-bootstrap/lib/Col';
import Form from './../ascribe_forms/form';
import PieceExtraDataForm from './../ascribe_forms/form_piece_extradata';
import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions'; import GlobalNotificationActions from '../../actions/global_notification_actions';
import FurtherDetailsFileuploader from './further_details_fileuploader'; import FurtherDetailsFileuploader from './further_details_fileuploader';
import Form from './../ascribe_forms/form';
import PieceExtraDataForm from './../ascribe_forms/form_piece_extradata';
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils'; import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils';
import { getLangText } from '../../utils/lang_utils';
let FurtherDetails = React.createClass({ let FurtherDetails = React.createClass({
propTypes: { propTypes: {
pieceId: React.PropTypes.number.isRequired,
editable: React.PropTypes.bool, editable: React.PropTypes.bool,
pieceId: React.PropTypes.number,
extraData: React.PropTypes.object, extraData: React.PropTypes.object,
handleSuccess: React.PropTypes.func,
otherData: React.PropTypes.arrayOf(React.PropTypes.object), otherData: React.PropTypes.arrayOf(React.PropTypes.object),
handleSuccess: React.PropTypes.func
}, },
getInitialState() { getInitialState() {
@ -32,13 +34,18 @@ let FurtherDetails = React.createClass({
}; };
}, },
showNotification(){ showNotification() {
this.props.handleSuccess(); const { handleSuccess } = this.props;
let notification = new GlobalNotificationModel('Details updated', 'success');
if (typeof handleSucess === 'function') {
handleSuccess();
}
const notification = new GlobalNotificationModel(getLangText('Details updated'), 'success');
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
submitFile(file){ submitFile(file) {
this.setState({ this.setState({
otherDataKey: file.key otherDataKey: file.key
}); });
@ -60,8 +67,7 @@ let FurtherDetails = React.createClass({
handleSuccess={this.showNotification} handleSuccess={this.showNotification}
editable={this.props.editable} editable={this.props.editable}
pieceId={this.props.pieceId} pieceId={this.props.pieceId}
extraData={this.props.extraData} extraData={this.props.extraData} />
/>
<PieceExtraDataForm <PieceExtraDataForm
name='display_instructions' name='display_instructions'
title='Display Instructions' title='Display Instructions'

View File

@ -28,7 +28,7 @@ let Piece = React.createClass({
updateObject() { updateObject() {
return PieceActions.fetchOne(this.props.piece.id); return PieceActions.fetchPiece(this.props.piece.id);
}, },
render() { render() {

View File

@ -69,7 +69,7 @@ let PieceContainer = React.createClass({
return mergeOptions( return mergeOptions(
UserStore.getState(), UserStore.getState(),
PieceListStore.getState(), PieceListStore.getState(),
PieceStore.getState(), PieceStore.getInitialState(),
{ {
showCreateEditionsDialog: false showCreateEditionsDialog: false
} }
@ -81,19 +81,24 @@ let PieceContainer = React.createClass({
PieceListStore.listen(this.onChange); PieceListStore.listen(this.onChange);
PieceStore.listen(this.onChange); PieceStore.listen(this.onChange);
// Every time we enter the piece detail page, just reset the piece
// store as it will otherwise display wrong/old data once the user loads
// the piece detail a second time
PieceActions.updatePiece({});
this.loadPiece(); this.loadPiece();
UserActions.fetchCurrentUser(); UserActions.fetchCurrentUser();
}, },
componentDidUpdate() { // This is done to update the container when the user clicks on the prev or next
const { pieceError } = this.state; // 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);
}
},
if (pieceError && pieceError.status === 404) { componentDidUpdate() {
const { err: pieceErr } = this.state.pieceMeta;
if (pieceErr && pieceErr.json && pieceErr.json.status === 404) {
this.throws(new ResourceNotFoundError(getLangText("Oops, the piece you're looking for doesn't exist."))); this.throws(new ResourceNotFoundError(getLangText("Oops, the piece you're looking for doesn't exist.")));
} }
}, },
@ -116,8 +121,7 @@ let PieceContainer = React.createClass({
ALSO, WE ENABLED THE LOAN BUTTON FOR IKONOTV TO LET THEM LOAN ON A PIECE LEVEL ALSO, WE ENABLED THE LOAN BUTTON FOR IKONOTV TO LET THEM LOAN ON A PIECE LEVEL
*/ */
if(state && state.piece && state.piece.acl && typeof state.piece.acl.acl_loan !== 'undefined') { if (state && state.piece && state.piece.acl && typeof state.piece.acl.acl_loan !== 'undefined') {
let pieceState = mergeOptions({}, state.piece); let pieceState = mergeOptions({}, state.piece);
pieceState.acl.acl_loan = false; pieceState.acl.acl_loan = false;
this.setState({ this.setState({
@ -129,11 +133,10 @@ let PieceContainer = React.createClass({
} }
}, },
loadPiece() { loadPiece(pieceId = this.props.params.pieceId) {
PieceActions.fetchOne(this.props.params.pieceId); PieceActions.fetchPiece(pieceId);
}, },
toggleCreateEditionsDialog() { toggleCreateEditionsDialog() {
this.setState({ this.setState({
showCreateEditionsDialog: !this.state.showCreateEditionsDialog showCreateEditionsDialog: !this.state.showCreateEditionsDialog
@ -143,8 +146,9 @@ let PieceContainer = React.createClass({
handleEditionCreationSuccess() { handleEditionCreationSuccess() {
const { filterBy, orderAsc, orderBy, page, pageSize, search } = this.state; const { filterBy, orderAsc, orderBy, page, pageSize, search } = this.state;
PieceActions.updateProperty({key: 'num_editions', value: 0}); PieceActions.updateProperty({ key: 'num_editions', value: 0 });
PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy }); PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
this.toggleCreateEditionsDialog(); this.toggleCreateEditionsDialog();
}, },
@ -158,24 +162,24 @@ let PieceContainer = React.createClass({
EditionListActions.closeAllEditionLists(); EditionListActions.closeAllEditionLists();
EditionListActions.clearAllEditionSelections(); EditionListActions.clearAllEditionSelections();
let notification = new GlobalNotificationModel(response.notification, 'success'); const notification = new GlobalNotificationModel(response.notification, 'success');
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
this.history.push('/collection'); this.history.push('/collection');
}, },
getCreateEditionsDialog() { getCreateEditionsDialog() {
if(this.state.piece.num_editions < 1 && this.state.showCreateEditionsDialog) { if (this.state.piece.num_editions < 1 && this.state.showCreateEditionsDialog) {
return ( return (
<div style={{marginTop: '1em'}}> <div style={{marginTop: '1em'}}>
<CreateEditionsForm <CreateEditionsForm
pieceId={this.state.piece.id} pieceId={this.state.piece.id}
handleSuccess={this.handleEditionCreationSuccess} /> handleSuccess={this.handleEditionCreationSuccess} />
<hr/> <hr />
</div> </div>
); );
} else { } else {
return (<hr/>); return (<hr />);
} }
}, },
@ -194,24 +198,24 @@ let PieceContainer = React.createClass({
// Therefore we need to at least refetch the changed piece from the server or on our case simply all // Therefore we need to at least refetch the changed piece from the server or on our case simply all
PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy }); PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy });
let notification = new GlobalNotificationModel('Editions successfully created', 'success', 10000); const notification = new GlobalNotificationModel(getLangText('Editions successfully created'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
getId() { getId() {
return {'id': this.state.piece.id}; return { 'id': this.state.piece.id };
}, },
getActions() { getActions() {
const { piece, currentUser } = this.state; const { piece, currentUser } = this.state;
if (piece && piece.notifications && piece.notifications.length > 0) { if (piece.notifications && piece.notifications.length > 0) {
return ( return (
<ListRequestActions <ListRequestActions
pieceOrEditions={piece} pieceOrEditions={piece}
currentUser={currentUser} currentUser={currentUser}
handleSuccess={this.loadPiece} handleSuccess={this.loadPiece}
notifications={piece.notifications}/>); notifications={piece.notifications} />);
} else { } else {
return ( return (
<AclProxy <AclProxy
@ -237,12 +241,12 @@ let PieceContainer = React.createClass({
onPollingSuccess={this.handlePollingSuccess}/> onPollingSuccess={this.handlePollingSuccess}/>
<DeleteButton <DeleteButton
handleSuccess={this.handleDeleteSuccess} handleSuccess={this.handleDeleteSuccess}
piece={piece}/> piece={piece} />
<AclInformation <AclInformation
aim="button" aim="button"
verbs={['acl_share', 'acl_transfer', 'acl_create_editions', 'acl_loan', 'acl_delete', verbs={['acl_share', 'acl_transfer', 'acl_create_editions', 'acl_loan', 'acl_delete',
'acl_consign']} 'acl_consign']}
aclObject={piece.acl}/> aclObject={piece.acl} />
</AclButtonList> </AclButtonList>
</DetailProperty> </DetailProperty>
</AclProxy> </AclProxy>
@ -254,8 +258,8 @@ let PieceContainer = React.createClass({
const { furtherDetailsType: FurtherDetailsType } = this.props; const { furtherDetailsType: FurtherDetailsType } = this.props;
const { currentUser, piece } = this.state; const { currentUser, piece } = this.state;
if (piece && piece.id) { if (piece.id) {
setDocumentTitle([piece.artist_name, piece.title].join(', ')); setDocumentTitle(`${piece.artist_name}, ${piece.title}`);
return ( return (
<Piece <Piece
@ -298,7 +302,7 @@ let PieceContainer = React.createClass({
editable={true} editable={true}
successMessage={getLangText('Private note saved')} successMessage={getLangText('Private note saved')}
url={ApiUrls.note_private_piece} url={ApiUrls.note_private_piece}
currentUser={currentUser}/> currentUser={currentUser} />
<Note <Note
id={this.getId} id={this.getId}
label={getLangText('Personal note (public)')} label={getLangText('Personal note (public)')}
@ -308,7 +312,7 @@ let PieceContainer = React.createClass({
show={!!(piece.public_note || piece.acl.acl_edit)} show={!!(piece.public_note || piece.acl.acl_edit)}
successMessage={getLangText('Public note saved')} successMessage={getLangText('Public note saved')}
url={ApiUrls.note_public_piece} url={ApiUrls.note_public_piece}
currentUser={currentUser}/> currentUser={currentUser} />
</CollapsibleParagraph> </CollapsibleParagraph>
<CollapsibleParagraph <CollapsibleParagraph
title={getLangText('Further Details')} title={getLangText('Further Details')}

View File

@ -13,43 +13,46 @@ import InputTextAreaToggable from './input_textarea_toggable';
let PieceExtraDataForm = React.createClass({ let PieceExtraDataForm = React.createClass({
propTypes: { propTypes: {
pieceId: React.PropTypes.number, name: React.PropTypes.string.isRequired,
pieceId: React.PropTypes.number.isRequired,
editable: React.PropTypes.bool,
extraData: React.PropTypes.object, extraData: React.PropTypes.object,
handleSuccess: React.PropTypes.func, handleSuccess: React.PropTypes.func,
name: React.PropTypes.string, title: React.PropTypes.string
title: React.PropTypes.string,
editable: React.PropTypes.bool
}, },
getFormData() { getFormData() {
let extradata = {};
extradata[this.props.name] = this.refs.form.refs[this.props.name].state.value;
return { return {
extradata: extradata, extradata: {
[this.props.name]: this.refs.form.refs[this.props.name].state.value
},
piece_id: this.props.pieceId piece_id: this.props.pieceId
}; };
}, },
render() { render() {
let defaultValue = this.props.extraData[this.props.name] || ''; const { editable, extraData, handleSuccess, name, pieceId, title } = this.props;
if (defaultValue.length === 0 && !this.props.editable){ const defaultValue = (extraData && extraData[name]) || null;
if (!defaultValue && !editable) {
return null; return null;
} }
let url = requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: this.props.pieceId});
return ( return (
<Form <Form
ref='form' ref='form'
url={url} url={requests.prepareUrl(ApiUrls.piece_extradata, { piece_id: pieceId })}
handleSuccess={this.props.handleSuccess} handleSuccess={handleSuccess}
getFormData={this.getFormData} getFormData={this.getFormData}
disabled={!this.props.editable}> disabled={!editable}>
<Property <Property
name={this.props.name} name={name}
label={this.props.title}> label={title}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={defaultValue} defaultValue={defaultValue}
placeholder={getLangText('Fill in%s', ' ') + this.props.title} placeholder={getLangText('Fill in%s', ' ') + title}
required /> required />
</Property> </Property>
<hr /> <hr />

View File

@ -1,19 +1,20 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import ReactAddons from 'react/addons';
import Modal from 'react-bootstrap/lib/Modal'; import Modal from 'react-bootstrap/lib/Modal';
let ModalWrapper = React.createClass({ let ModalWrapper = React.createClass({
propTypes: { propTypes: {
trigger: React.PropTypes.element,
title: React.PropTypes.oneOfType([ title: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element, React.PropTypes.element,
React.PropTypes.string React.PropTypes.string
]).isRequired, ]).isRequired,
handleSuccess: React.PropTypes.func.isRequired,
handleCancel: React.PropTypes.func,
handleSuccess: React.PropTypes.func,
trigger: React.PropTypes.element,
children: React.PropTypes.oneOfType([ children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element React.PropTypes.element
@ -38,14 +39,25 @@ let ModalWrapper = React.createClass({
}); });
}, },
handleCancel() {
if (typeof this.props.handleCancel === 'function') {
this.props.handleCancel();
}
this.hide();
},
handleSuccess(response) { handleSuccess(response) {
this.props.handleSuccess(response); if (typeof this.props.handleSuccess === 'function') {
this.props.handleSuccess(response);
}
this.hide(); this.hide();
}, },
renderChildren() { renderChildren() {
return ReactAddons.Children.map(this.props.children, (child) => { return React.Children.map(this.props.children, (child) => {
return ReactAddons.addons.cloneWithProps(child, { return React.cloneElement(child, {
handleSuccess: (response) => { handleSuccess: (response) => {
if (typeof child.props.handleSuccess === 'function') { if (typeof child.props.handleSuccess === 'function') {
child.props.handleSuccess(response); child.props.handleSuccess(response);
@ -60,14 +72,23 @@ let ModalWrapper = React.createClass({
render() { render() {
const { trigger, title } = this.props; const { trigger, title } = this.props;
// If the trigger component exists, we add the ModalWrapper's show() as its onClick method. // If the trigger component exists, we add the ModalWrapper's show() to its onClick method.
// The trigger component should, in most cases, be a button. // The trigger component should, in most cases, be a button.
const clonedTrigger = React.isValidElement(trigger) ? React.cloneElement(trigger, {onClick: this.show}) const clonedTrigger = React.isValidElement(trigger) ?
: null; React.cloneElement(trigger, {
onClick: (...params) => {
if (typeof trigger.props.onClick === 'function') {
trigger.props.onClick(...params);
}
this.show();
}
}) : null;
return ( return (
<span> <span>
{clonedTrigger} {clonedTrigger}
<Modal show={this.state.showModal} onHide={this.hide}> <Modal show={this.state.showModal} onHide={this.handleCancel}>
<Modal.Header closeButton> <Modal.Header closeButton>
<Modal.Title> <Modal.Title>
{title} {title}

View File

@ -36,17 +36,11 @@ const ReactS3FineUploader = React.createClass({
keyRoutine: shape({ keyRoutine: shape({
url: string, url: string,
fileClass: string, fileClass: string,
pieceId: oneOfType([ pieceId: number
string,
number
])
}), }),
createBlobRoutine: shape({ createBlobRoutine: shape({
url: string, url: string,
pieceId: oneOfType([ pieceId: number
string,
number
])
}), }),
handleChangedFile: func, // is for when a file is dropped or selected handleChangedFile: func, // is for when a file is dropped or selected
submitFile: func, // is for when a file has been successfully uploaded, TODO: rename to handleSubmitFile submitFile: func, // is for when a file has been successfully uploaded, TODO: rename to handleSubmitFile

View File

@ -60,16 +60,10 @@ let PrizePieceContainer = React.createClass({
mixins: [ReactError], mixins: [ReactError],
getInitialState() { getInitialState() {
//FIXME: this component uses the PieceStore, but we avoid using the getState() here since it may contain stale data return mergeOptions(
// It should instead use something like getInitialState() where that call also resets the state. PieceStore.getInitialState(),
return UserStore.getState(); UserStore.getState()
}, );
componentWillMount() {
// Every time we enter the piece detail page, just reset the piece
// store as it will otherwise display wrong/old data once the user loads
// the piece detail a second time
PieceActions.updatePiece({});
}, },
componentDidMount() { componentDidMount() {
@ -81,18 +75,19 @@ let PrizePieceContainer = React.createClass({
}, },
// This is done to update the container when the user clicks on the prev or next // 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) // 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) { componentWillReceiveProps(nextProps) {
if (this.props.params.pieceId !== nextProps.params.pieceId) { if (this.props.params.pieceId !== nextProps.params.pieceId) {
PieceActions.updatePiece({}); PieceActions.flushPiece();
this.loadPiece(nextProps.params.pieceId); this.loadPiece(nextProps.params.pieceId);
} }
}, },
componentDidUpdate() { componentDidUpdate() {
const { pieceError } = this.state; const { pieceMeta: { err: pieceErr } } = this.state;
if (pieceError && pieceError.status === 404) { if (pieceErr && pieceErr.status === 404) {
this.throws(new ResourceNotFoundError(getLangText("Oops, the piece you're looking for doesn't exist."))); this.throws(new ResourceNotFoundError(getLangText("Oops, the piece you're looking for doesn't exist.")));
} }
}, },
@ -109,25 +104,25 @@ let PrizePieceContainer = React.createClass({
getActions() { getActions() {
const { currentUser, piece } = this.state; const { currentUser, piece } = this.state;
if (piece && piece.notifications && piece.notifications.length > 0) { if (piece.notifications && piece.notifications.length > 0) {
return ( return (
<ListRequestActions <ListRequestActions
pieceOrEditions={piece} pieceOrEditions={piece}
currentUser={currentUser} currentUser={currentUser}
handleSuccess={this.loadPiece} handleSuccess={this.loadPiece}
notifications={piece.notifications}/>); notifications={piece.notifications} />);
} }
}, },
loadPiece(pieceId = this.props.params.pieceId) { loadPiece(pieceId = this.props.params.pieceId) {
PieceActions.fetchOne(pieceId); PieceActions.fetchPiece(pieceId);
}, },
render() { render() {
const { selectedPrizeActionButton } = this.props; const { selectedPrizeActionButton } = this.props;
const { currentUser, piece } = this.state; const { currentUser, piece } = this.state;
if (piece && piece.id) { if (piece.id) {
/* /*
This really needs a refactor! This really needs a refactor!
@ -138,16 +133,19 @@ let PrizePieceContainer = React.createClass({
// Only show the artist name if you are the participant or if you are a judge and the piece is shortlisted // Only show the artist name if you are the participant or if you are a judge and the piece is shortlisted
let artistName; let artistName;
if ((currentUser.is_jury && !currentUser.is_judge) || (currentUser.is_judge && !piece.selected )) { if ((currentUser.is_jury && !currentUser.is_judge) || (currentUser.is_judge && !piece.selected )) {
artistName = <span className="glyphicon glyphicon-eye-close" aria-hidden="true"/>; artistName = <span className="glyphicon glyphicon-eye-close" aria-hidden="true" />;
setDocumentTitle(piece.title); setDocumentTitle(piece.title);
} else { } else {
artistName = piece.artist_name; artistName = piece.artist_name;
setDocumentTitle([artistName, piece.title].join(', ')); setDocumentTitle(`${artistName}, ${piece.title}`);
} }
// Only show the artist email if you are a judge and the piece is shortlisted // Only show the artist email if you are a judge and the piece is shortlisted
const artistEmail = (currentUser.is_judge && piece.selected ) ? const artistEmail = currentUser.is_judge && piece.selected ? (
<DetailProperty label={getLangText('REGISTREE')} value={ piece.user_registered } /> : null; <DetailProperty
label={getLangText('REGISTREE')}
value={piece.user_registered} />
) : null;
return ( return (
<Piece <Piece
@ -157,16 +155,16 @@ let PrizePieceContainer = React.createClass({
<div className="ascribe-detail-header"> <div className="ascribe-detail-header">
<NavigationHeader <NavigationHeader
piece={piece} piece={piece}
currentUser={currentUser}/> currentUser={currentUser} />
<h1 className="ascribe-detail-title">{piece.title}</h1> <h1 className="ascribe-detail-title">{piece.title}</h1>
<DetailProperty label={getLangText('BY')} value={artistName} /> <DetailProperty label={getLangText('BY')} value={artistName} />
<DetailProperty label={getLangText('DATE')} value={Moment(piece.date_created, 'YYYY-MM-DD').year()} /> <DetailProperty label={getLangText('DATE')} value={Moment(piece.date_created, 'YYYY-MM-DD').year()} />
{artistEmail} {artistEmail}
{this.getActions()} {this.getActions()}
<hr/> <hr />
</div> </div>
} }
subheader={ subheader={
<PrizePieceRatings <PrizePieceRatings
loadPiece={this.loadPiece} loadPiece={this.loadPiece}
@ -196,9 +194,8 @@ let NavigationHeader = React.createClass({
render() { render() {
const { currentUser, piece } = this.props; const { currentUser, piece } = this.props;
if (currentUser && currentUser.email && currentUser.is_judge && currentUser.is_jury && if (currentUser.email && currentUser.is_judge && currentUser.is_jury && !currentUser.is_admin && piece.navigation) {
!currentUser.is_admin && piece && piece.navigation) { const nav = piece.navigation;
let nav = piece.navigation;
return ( return (
<div style={{marginBottom: '1em'}}> <div style={{marginBottom: '1em'}}>
@ -218,8 +215,9 @@ let NavigationHeader = React.createClass({
<hr/> <hr/>
</div> </div>
); );
} else {
return null;
} }
return null;
} }
}); });
@ -323,7 +321,7 @@ let PrizePieceRatings = React.createClass({
}, },
render() { render() {
if (this.props.piece && this.props.currentUser && this.props.currentUser.is_judge && this.state.average) { if (this.props.piece.id && this.props.currentUser.is_judge && this.state.average) {
// Judge sees shortlisting, average and per-jury notes // Judge sees shortlisting, average and per-jury notes
return ( return (
<div> <div>
@ -355,7 +353,7 @@ let PrizePieceRatings = React.createClass({
size='md' size='md'
step={0.5} step={0.5}
rating={this.state.average} rating={this.state.average}
ratingAmount={5}/> ratingAmount={5} />
</div> </div>
<hr /> <hr />
{this.state.ratings.map((item, i) => { {this.state.ratings.map((item, i) => {
@ -380,7 +378,7 @@ let PrizePieceRatings = React.createClass({
size='sm' size='sm'
step={0.5} step={0.5}
rating={item.rating} rating={item.rating}
ratingAmount={5}/> ratingAmount={5} />
</span> </span>
<span> {item.user}</span> <span> {item.user}</span>
{note} {note}
@ -391,7 +389,7 @@ let PrizePieceRatings = React.createClass({
<hr /> <hr />
</CollapsibleParagraph> </CollapsibleParagraph>
</div>); </div>);
} else if (this.props.currentUser && this.props.currentUser.is_jury) { } else if (this.props.currentUser.is_jury) {
// Jury can set rating and note // Jury can set rating and note
return ( return (
<CollapsibleParagraph <CollapsibleParagraph
@ -409,14 +407,14 @@ let PrizePieceRatings = React.createClass({
ratingAmount={5} /> ratingAmount={5} />
</div> </div>
<Note <Note
id={() => {return {'piece_id': this.props.piece.id}; }} id={() => ({ 'piece_id': this.props.piece.id })}
label={getLangText('Jury note')} label={getLangText('Jury note')}
defaultValue={this.props.piece && this.props.piece.note_from_user ? this.props.piece.note_from_user.note : null} defaultValue={this.props.piece.note_from_user || null}
placeholder={getLangText('Enter your comments ...')} placeholder={getLangText('Enter your comments ...')}
editable={true} editable={true}
successMessage={getLangText('Jury note saved')} successMessage={getLangText('Jury note saved')}
url={ApiUrls.notes} url={ApiUrls.notes}
currentUser={this.props.currentUser}/> currentUser={this.props.currentUser} />
</CollapsibleParagraph>); </CollapsibleParagraph>);
} else { } else {
return null; return null;
@ -433,33 +431,34 @@ let PrizePieceDetails = React.createClass({
render() { render() {
const { piece } = this.props; const { piece } = this.props;
if (piece && if (piece.prize && piece.prize.name && Object.keys(piece.extra_data).length) {
piece.prize &&
piece.prize.name &&
Object.keys(piece.extra_data).length !== 0) {
return ( return (
<CollapsibleParagraph <CollapsibleParagraph
title={getLangText('Prize Details')} title={getLangText('Prize Details')}
defaultExpanded={true}> defaultExpanded={true}>
<Form ref='form'> <Form>
{Object.keys(piece.extra_data).sort().map((data) => { {Object
// Remove leading number (for sorting), if any, and underscores with spaces .keys(piece.extra_data)
let label = data.replace(/^\d-/, '').replace(/_/g, ' '); .sort()
const value = piece.extra_data[data] || 'N/A'; .map((data) => {
// Remove leading number (for sorting), if any, and underscores with spaces
const label = data.replace(/^\d-/, '').replace(/_/g, ' ');
const value = piece.extra_data[data] || 'N/A';
return ( return (
<Property <Property
key={label} key={label}
name={data} name={data}
label={label} label={label}
editable={false} editable={false}
overrideForm={true}> overrideForm={true}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={value}/> defaultValue={value} />
</Property> </Property>
); );
})} })
}
<FurtherDetailsFileuploader <FurtherDetailsFileuploader
submitFile={() => {}} submitFile={() => {}}
setIsUploadReady={() => {}} setIsUploadReady={() => {}}
@ -472,8 +471,9 @@ let PrizePieceDetails = React.createClass({
</Form> </Form>
</CollapsibleParagraph> </CollapsibleParagraph>
); );
} else {
return null;
} }
return null;
} }
}); });

View File

@ -14,6 +14,18 @@ class PrizeRatingStore {
}); });
} }
getInitialState() {
this.ratings = [];
this.currentRating = null;
this.average = null;
return {
ratings: this.ratings,
currentRating: this.currentRating,
average: this.average
};
}
onUpdatePrizeRatings(ratings) { onUpdatePrizeRatings(ratings) {
this.ratings = ratings; this.ratings = ratings;
} }
@ -30,18 +42,6 @@ class PrizeRatingStore {
onResetPrizeRatings() { onResetPrizeRatings() {
this.getInitialState(); this.getInitialState();
} }
getInitialState() {
this.ratings = [];
this.currentRating = null;
this.average = null;
return {
ratings: this.ratings,
currentRating: this.currentRating,
average: this.average
};
}
} }
export default alt.createStore(PrizeRatingStore, 'PrizeRatingStore'); export default alt.createStore(PrizeRatingStore, 'PrizeRatingStore');

View File

@ -40,7 +40,7 @@ let CylandPieceContainer = React.createClass({
getInitialState() { getInitialState() {
return mergeOptions( return mergeOptions(
PieceStore.getState(), PieceStore.getInitialState(),
UserStore.getState(), UserStore.getState(),
PieceListStore.getState() PieceListStore.getState()
); );
@ -51,14 +51,17 @@ let CylandPieceContainer = React.createClass({
UserStore.listen(this.onChange); UserStore.listen(this.onChange);
PieceListStore.listen(this.onChange); PieceListStore.listen(this.onChange);
// Every time we enter the piece detail page, just reset the piece
// store as it will otherwise display wrong/old data once the user loads
// the piece detail a second time
PieceActions.updatePiece({});
this.loadPiece(); this.loadPiece();
}, },
// We need this for 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();
}
},
componentWillUnmount() { componentWillUnmount() {
PieceStore.unlisten(this.onChange); PieceStore.unlisten(this.onChange);
UserStore.unlisten(this.onChange); UserStore.unlisten(this.onChange);
@ -70,7 +73,7 @@ let CylandPieceContainer = React.createClass({
}, },
loadPiece() { loadPiece() {
PieceActions.fetchOne(this.props.params.pieceId); PieceActions.fetchPiece(this.props.params.pieceId);
}, },
handleDeleteSuccess(response) { handleDeleteSuccess(response) {
@ -83,19 +86,21 @@ let CylandPieceContainer = React.createClass({
EditionListActions.closeAllEditionLists(); EditionListActions.closeAllEditionLists();
EditionListActions.clearAllEditionSelections(); EditionListActions.clearAllEditionSelections();
let notification = new GlobalNotificationModel(response.notification, 'success'); const notification = new GlobalNotificationModel(response.notification, 'success');
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
this.history.push('/collection'); this.history.push('/collection');
}, },
render() { render() {
if(this.state.piece && this.state.piece.id) { const { piece } = this.state;
setDocumentTitle([this.state.piece.artist_name, this.state.piece.title].join(', '));
if (piece.id) {
setDocumentTitle(`${piece.artist_name}, ${piece.title}`);
return ( return (
<WalletPieceContainer <WalletPieceContainer
piece={this.state.piece} piece={piece}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
loadPiece={this.loadPiece} loadPiece={this.loadPiece}
handleDeleteSuccess={this.handleDeleteSuccess} handleDeleteSuccess={this.handleDeleteSuccess}
@ -104,14 +109,13 @@ let CylandPieceContainer = React.createClass({
title={getLangText('Further Details')} title={getLangText('Further Details')}
defaultExpanded={true}> defaultExpanded={true}>
<CylandAdditionalDataForm <CylandAdditionalDataForm
piece={this.state.piece} piece={piece}
disabled={!this.state.piece.acl.acl_edit} disabled={!piece.acl.acl_edit}
isInline={true} /> isInline={true} />
</CollapsibleParagraph> </CollapsibleParagraph>
</WalletPieceContainer> </WalletPieceContainer>
); );
} } else {
else {
return ( return (
<div className="fullpage-spinner"> <div className="fullpage-spinner">
<AscribeSpinner color='dark-blue' size='lg' /> <AscribeSpinner color='dark-blue' size='lg' />

View File

@ -23,9 +23,10 @@ import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_
let CylandAdditionalDataForm = React.createClass({ let CylandAdditionalDataForm = React.createClass({
propTypes: { propTypes: {
handleSuccess: React.PropTypes.func,
piece: React.PropTypes.object.isRequired, piece: React.PropTypes.object.isRequired,
disabled: React.PropTypes.bool, disabled: React.PropTypes.bool,
handleSuccess: React.PropTypes.func,
isInline: React.PropTypes.bool isInline: React.PropTypes.bool
}, },
@ -42,13 +43,13 @@ let CylandAdditionalDataForm = React.createClass({
}, },
handleSuccess() { handleSuccess() {
let notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000); const notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
getFormData() { getFormData() {
let extradata = {}; const extradata = {};
let formRefs = this.refs.form.refs; const formRefs = this.refs.form.refs;
// Put additional fields in extra data object // Put additional fields in extra data object
Object Object
@ -71,10 +72,13 @@ let CylandAdditionalDataForm = React.createClass({
}, },
render() { render() {
let { piece, isInline, disabled, handleSuccess, location } = this.props; const { disabled, handleSuccess, isInline, piece } = this.props;
let buttons, spinner, heading;
if(!isInline) { let buttons;
let spinner;
let heading;
if (!isInline) {
buttons = ( buttons = (
<button <button
type="submit" type="submit"
@ -87,7 +91,7 @@ let CylandAdditionalDataForm = React.createClass({
spinner = ( spinner = (
<div className="modal-footer"> <div className="modal-footer">
<p className="pull-right"> <p className="pull-right">
<AscribeSpinner color='dark-blue' size='md'/> <AscribeSpinner color='dark-blue' size='md' />
</p> </p>
</div> </div>
); );
@ -101,13 +105,15 @@ let CylandAdditionalDataForm = React.createClass({
); );
} }
if(piece && piece.id) { if (piece.id) {
const { extra_data: extraData = {} } = piece;
return ( return (
<Form <Form
disabled={disabled} disabled={disabled}
className="ascribe-form-bordered" className="ascribe-form-bordered"
ref='form' ref='form'
url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: piece.id})} url={requests.prepareUrl(ApiUrls.piece_extradata, { piece_id: piece.id })}
handleSuccess={handleSuccess || this.handleSuccess} handleSuccess={handleSuccess || this.handleSuccess}
getFormData={this.getFormData} getFormData={this.getFormData}
buttons={buttons} buttons={buttons}
@ -116,65 +122,65 @@ let CylandAdditionalDataForm = React.createClass({
<Property <Property
name='artist_bio' name='artist_bio'
label={getLangText('Artist Biography')} label={getLangText('Artist Biography')}
expanded={!disabled || !!piece.extra_data.artist_bio}> expanded={!disabled || !!extraData.artist_bio}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={piece.extra_data.artist_bio} defaultValue={extraData.artist_bio}
placeholder={getLangText('Enter the artist\'s biography...')}/> placeholder={getLangText('Enter the artist\'s biography...')} />
</Property> </Property>
<Property <Property
name='artist_contact_information' name='artist_contact_information'
label={getLangText('Artist Contact Information')} label={getLangText('Artist Contact Information')}
expanded={!disabled || !!piece.extra_data.artist_contact_information}> expanded={!disabled || !!extraData.artist_contact_information}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={piece.extra_data.artist_contact_information} defaultValue={extraData.artist_contact_information}
placeholder={getLangText('Enter the artist\'s contact information...')}/> placeholder={getLangText('Enter the artist\'s contact information...')} />
</Property> </Property>
<Property <Property
name='conceptual_overview' name='conceptual_overview'
label={getLangText('Conceptual Overview')} label={getLangText('Conceptual Overview')}
expanded={!disabled || !!piece.extra_data.conceptual_overview}> expanded={!disabled || !!extraData.conceptual_overview}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={piece.extra_data.conceptual_overview} defaultValue={extraData.conceptual_overview}
placeholder={getLangText('Enter a conceptual overview...')}/> placeholder={getLangText('Enter a conceptual overview...')} />
</Property> </Property>
<Property <Property
name='medium' name='medium'
label={getLangText('Medium (technical specifications)')} label={getLangText('Medium (technical specifications)')}
expanded={!disabled || !!piece.extra_data.medium}> expanded={!disabled || !!extraData.medium}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={piece.extra_data.medium} defaultValue={extraData.medium}
placeholder={getLangText('Enter the medium (and other technical specifications)...')}/> placeholder={getLangText('Enter the medium (and other technical specifications)...')} />
</Property> </Property>
<Property <Property
name='size_duration' name='size_duration'
label={getLangText('Size / Duration')} label={getLangText('Size / Duration')}
expanded={!disabled || !!piece.extra_data.size_duration}> expanded={!disabled || !!extraData.size_duration}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={piece.extra_data.size_duration} defaultValue={extraData.size_duration}
placeholder={getLangText('Enter the size / duration...')}/> placeholder={getLangText('Enter the size / duration...')} />
</Property> </Property>
<Property <Property
name='display_instructions' name='display_instructions'
label={getLangText('Display instructions')} label={getLangText('Display instructions')}
expanded={!disabled || !!piece.extra_data.display_instructions}> expanded={!disabled || !!extraData.display_instructions}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={piece.extra_data.display_instructions} defaultValue={extraData.display_instructions}
placeholder={getLangText('Enter the display instructions...')}/> placeholder={getLangText('Enter the display instructions...')} />
</Property> </Property>
<Property <Property
name='additional_details' name='additional_details'
label={getLangText('Additional details')} label={getLangText('Additional details')}
expanded={!disabled || !!piece.extra_data.additional_details}> expanded={!disabled || !!extraData.additional_details}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={piece.extra_data.additional_details} defaultValue={extraData.additional_details}
placeholder={getLangText('Enter additional details...')}/> placeholder={getLangText('Enter additional details...')} />
</Property> </Property>
<FurtherDetailsFileuploader <FurtherDetailsFileuploader
label={getLangText('Additional files (e.g. still images, pdf)')} label={getLangText('Additional files (e.g. still images, pdf)')}
@ -189,7 +195,7 @@ let CylandAdditionalDataForm = React.createClass({
} else { } else {
return ( return (
<div className="ascribe-loading-position"> <div className="ascribe-loading-position">
<AscribeSpinner color='dark-blue' size='md'/> <AscribeSpinner color='dark-blue' size='md' />
</div> </div>
); );
} }

View File

@ -52,7 +52,7 @@ let CylandRegisterPiece = React.createClass({
return mergeOptions( return mergeOptions(
UserStore.getState(), UserStore.getState(),
PieceListStore.getState(), PieceListStore.getState(),
PieceStore.getState(), PieceStore.getInitialState(),
WhitelabelStore.getState(), WhitelabelStore.getState(),
{ {
step: 0 step: 0
@ -67,7 +67,7 @@ let CylandRegisterPiece = React.createClass({
UserActions.fetchCurrentUser(); UserActions.fetchCurrentUser();
WhitelabelActions.fetchWhitelabel(); WhitelabelActions.fetchWhitelabel();
let queryParams = this.props.location.query; const queryParams = this.props.location.query;
// Since every step of this register process is atomic, // Since every step of this register process is atomic,
// we may need to enter the process at step 1 or 2. // we may need to enter the process at step 1 or 2.
@ -76,8 +76,8 @@ let CylandRegisterPiece = React.createClass({
// //
// We're using 'in' here as we want to know if 'piece_id' is present in the url, // 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. // we don't care about the value.
if(queryParams && 'piece_id' in queryParams) { if ('piece_id' in queryParams) {
PieceActions.fetchOne(queryParams.piece_id); PieceActions.fetchPiece(queryParams.piece_id);
} }
}, },
@ -92,53 +92,44 @@ let CylandRegisterPiece = React.createClass({
this.setState(state); this.setState(state);
}, },
handleRegisterSuccess(response){ handleRegisterSuccess(response) {
this.refreshPieceList(); this.refreshPieceList();
// also start loading the piece for the next step // Also load the newly registered piece for the next step
if(response && response.piece) { if (response && response.piece) {
PieceActions.updatePiece({});
PieceActions.updatePiece(response.piece); PieceActions.updatePiece(response.piece);
} }
this.incrementStep(); this.nextSlide({ piece_id: response.piece.id });
this.refs.slidesContainer.nextSlide({ piece_id: response.piece.id });
}, },
handleAdditionalDataSuccess() { handleAdditionalDataSuccess() {
// We need to refetch the piece again after submitting the additional data // 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 // since we want its otherData to be displayed when the user choses to click
// on the browsers back button. // on the browsers back button.
PieceActions.fetchOne(this.state.piece.id); PieceActions.fetchPiece(this.state.piece.id);
this.refreshPieceList(); this.refreshPieceList();
this.incrementStep(); this.nextSlide();
this.refs.slidesContainer.nextSlide();
}, },
handleLoanSuccess(response) { handleLoanSuccess(response) {
let notification = new GlobalNotificationModel(response.notification, 'success', 10000); const notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
this.refreshPieceList(); this.refreshPieceList();
PieceActions.fetchOne(this.state.piece.id);
this.history.push(`/pieces/${this.state.piece.id}`); this.history.push(`/pieces/${this.state.piece.id}`);
}, },
// We need to increase the step to lock the forms that are already filled out nextSlide(queryParams) {
incrementStep() { // We need to increase the step to lock the forms that are already filled out
// also increase step
let newStep = this.state.step + 1;
this.setState({ this.setState({
step: newStep step: this.state.step + 1
}); });
this.refs.slidesContainer.nextSlide(queryParams);
}, },
refreshPieceList() { refreshPieceList() {
@ -152,8 +143,7 @@ let CylandRegisterPiece = React.createClass({
const { currentUser, piece, step, whitelabel } = this.state; const { currentUser, piece, step, whitelabel } = this.state;
const today = new Moment(); const today = new Moment();
const datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain = new Moment(); const datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain = new Moment().add(1000, 'years');
datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain.add(1000, 'years');
const loanHeading = getLangText('Loan to Cyland archive'); const loanHeading = getLangText('Loan to Cyland archive');
const loanButtons = ( const loanButtons = (
@ -196,7 +186,7 @@ let CylandRegisterPiece = React.createClass({
submitMessage={getLangText('Submit')} submitMessage={getLangText('Submit')}
isFineUploaderActive={true} isFineUploaderActive={true}
handleSuccess={this.handleRegisterSuccess} handleSuccess={this.handleRegisterSuccess}
location={location}/> location={location} />
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@ -41,7 +41,7 @@ let IkonotvPieceContainer = React.createClass({
getInitialState() { getInitialState() {
return mergeOptions( return mergeOptions(
PieceStore.getState(), PieceStore.getInitialState(),
UserStore.getState(), UserStore.getState(),
PieceListStore.getState() PieceListStore.getState()
); );
@ -52,19 +52,14 @@ let IkonotvPieceContainer = React.createClass({
UserStore.listen(this.onChange); UserStore.listen(this.onChange);
PieceListStore.listen(this.onChange); PieceListStore.listen(this.onChange);
// Every time we enter the piece detail page, just reset the piece
// store as it will otherwise display wrong/old data once the user loads
// the piece detail a second time
PieceActions.updatePiece({});
this.loadPiece(); this.loadPiece();
}, },
// We need this for when the user clicks on a notification while being in another piece view // We need this for when the user clicks on a notification while being in another piece view
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if(this.props.params.pieceId !== nextProps.params.pieceId) { if (this.props.params.pieceId !== nextProps.params.pieceId) {
PieceActions.updatePiece({}); PieceActions.flushPiece();
PieceActions.fetchOne(nextProps.params.pieceId); this.loadPiece();
} }
}, },
@ -79,7 +74,7 @@ let IkonotvPieceContainer = React.createClass({
}, },
loadPiece() { loadPiece() {
PieceActions.fetchOne(this.props.params.pieceId); PieceActions.fetchPiece(this.props.params.pieceId);
}, },
handleDeleteSuccess(response) { handleDeleteSuccess(response) {
@ -92,13 +87,15 @@ let IkonotvPieceContainer = React.createClass({
EditionListActions.closeAllEditionLists(); EditionListActions.closeAllEditionLists();
EditionListActions.clearAllEditionSelections(); EditionListActions.clearAllEditionSelections();
let notification = new GlobalNotificationModel(response.notification, 'success'); const notification = new GlobalNotificationModel(response.notification, 'success');
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
this.history.push('/collection'); this.history.push('/collection');
}, },
render() { render() {
const { piece } = this.state;
let furtherDetails = ( let furtherDetails = (
<CollapsibleParagraph <CollapsibleParagraph
title={getLangText('Further Details')} title={getLangText('Further Details')}
@ -107,28 +104,29 @@ let IkonotvPieceContainer = React.createClass({
</CollapsibleParagraph> </CollapsibleParagraph>
); );
if(this.state.piece.extra_data && Object.keys(this.state.piece.extra_data).length > 0 && this.state.piece.acl) { if (piece.extra_data && Object.keys(piece.extra_data).length && piece.acl) {
furtherDetails = ( furtherDetails = (
<CollapsibleParagraph <CollapsibleParagraph
title={getLangText('Further Details')} title={getLangText('Further Details')}
defaultExpanded={true}> defaultExpanded={true}>
<IkonotvArtistDetailsForm <IkonotvArtistDetailsForm
piece={this.state.piece} piece={piece}
isInline={true} isInline={true}
disabled={!this.state.piece.acl.acl_edit} /> disabled={!piece.acl.acl_edit} />
<IkonotvArtworkDetailsForm <IkonotvArtworkDetailsForm
piece={this.state.piece} piece={piece}
isInline={true} isInline={true}
disabled={!this.state.piece.acl.acl_edit} /> disabled={!piece.acl.acl_edit} />
</CollapsibleParagraph> </CollapsibleParagraph>
); );
} }
if(this.state.piece && this.state.piece.id) { if (piece.id) {
setDocumentTitle([this.state.piece.artist_name, this.state.piece.title].join(', ')); setDocumentTitle(`${piece.artist_name}, ${piece.title}`);
return ( return (
<WalletPieceContainer <WalletPieceContainer
piece={this.state.piece} piece={piece}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
loadPiece={this.loadPiece} loadPiece={this.loadPiece}
handleDeleteSuccess={this.handleDeleteSuccess} handleDeleteSuccess={this.handleDeleteSuccess}
@ -136,8 +134,7 @@ let IkonotvPieceContainer = React.createClass({
{furtherDetails} {furtherDetails}
</WalletPieceContainer> </WalletPieceContainer>
); );
} } else {
else {
return ( return (
<div className="fullpage-spinner"> <div className="fullpage-spinner">
<AscribeSpinner color='dark-blue' size='lg' /> <AscribeSpinner color='dark-blue' size='lg' />

View File

@ -20,11 +20,10 @@ import { getLangText } from '../../../../../../utils/lang_utils';
let IkonotvArtistDetailsForm = React.createClass({ let IkonotvArtistDetailsForm = React.createClass({
propTypes: { propTypes: {
handleSuccess: React.PropTypes.func,
piece: React.PropTypes.object.isRequired, piece: React.PropTypes.object.isRequired,
disabled: React.PropTypes.bool, disabled: React.PropTypes.bool,
handleSuccess: React.PropTypes.func,
isInline: React.PropTypes.bool isInline: React.PropTypes.bool
}, },
@ -35,8 +34,8 @@ let IkonotvArtistDetailsForm = React.createClass({
}, },
getFormData() { getFormData() {
let extradata = {}; const extradata = {};
let formRefs = this.refs.form.refs; const formRefs = this.refs.form.refs;
// Put additional fields in extra data object // Put additional fields in extra data object
Object Object
@ -53,20 +52,23 @@ let IkonotvArtistDetailsForm = React.createClass({
}, },
handleSuccess() { handleSuccess() {
let notification = new GlobalNotificationModel('Artist details successfully updated', 'success', 10000); const notification = new GlobalNotificationModel(getLangText('Artist details successfully updated'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
render() { render() {
let buttons, spinner, heading; const { disabled, isInline, handleSuccess, piece } = this.props;
let { isInline, handleSuccess } = this.props;
let buttons;
let spinner;
let heading;
if (!isInline) { if (!isInline) {
buttons = ( buttons = (
<button <button
type="submit" type="submit"
className="btn btn-default btn-wide" className="btn btn-default btn-wide"
disabled={this.props.disabled}> disabled={disabled}>
{getLangText('Proceed to loan')} {getLangText('Proceed to loan')}
</button> </button>
); );
@ -74,7 +76,7 @@ let IkonotvArtistDetailsForm = React.createClass({
spinner = ( spinner = (
<div className="modal-footer"> <div className="modal-footer">
<p className="pull-right"> <p className="pull-right">
<AscribeSpinner color='dark-blue' size='md'/> <AscribeSpinner color='dark-blue' size='md' />
</p> </p>
</div> </div>
); );
@ -88,13 +90,15 @@ let IkonotvArtistDetailsForm = React.createClass({
); );
} }
if (this.props.piece && this.props.piece.id && this.props.piece.extra_data) { if (piece.id) {
const { extra_data: extraData = {} } = piece;
return ( return (
<Form <Form
disabled={this.props.disabled} disabled={disabled}
className="ascribe-form-bordered" className="ascribe-form-bordered"
ref='form' ref='form'
url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: this.props.piece.id})} url={requests.prepareUrl(ApiUrls.piece_extradata, { piece_id: piece.id })}
handleSuccess={handleSuccess || this.handleSuccess} handleSuccess={handleSuccess || this.handleSuccess}
getFormData={this.getFormData} getFormData={this.getFormData}
buttons={buttons} buttons={buttons}
@ -103,39 +107,38 @@ let IkonotvArtistDetailsForm = React.createClass({
<Property <Property
name='artist_website' name='artist_website'
label={getLangText('Artist Website')} label={getLangText('Artist Website')}
expanded={!this.props.disabled || !!this.props.piece.extra_data.artist_website}> expanded={!disabled || !!extraData.artist_website}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={this.props.piece.extra_data.artist_website} defaultValue={extraData.artist_website}
placeholder={getLangText('The artist\'s website if present...')}/> placeholder={getLangText('The artist\'s website if present...')} />
</Property> </Property>
<Property <Property
name='gallery_website' name='gallery_website'
label={getLangText('Website of related Gallery, Museum, etc.')} label={getLangText('Website of related Gallery, Museum, etc.')}
expanded={!this.props.disabled || !!this.props.piece.extra_data.gallery_website}> expanded={!disabled || !!extraData.gallery_website}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={this.props.piece.extra_data.gallery_website} defaultValue={extraData.gallery_website}
placeholder={getLangText('The website of any related Gallery or Museum')}/> placeholder={getLangText('The website of any related Gallery or Museum')} />
</Property> </Property>
<Property <Property
name='additional_websites' name='additional_websites'
label={getLangText('Additional Websites/Publications/Museums/Galleries')} label={getLangText('Additional Websites/Publications/Museums/Galleries')}
expanded={!this.props.disabled || !!this.props.piece.extra_data.additional_websites}> expanded={!disabled || !!extraData.additional_websites}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={this.props.piece.extra_data.additional_websites} defaultValue={extraData.additional_websites}
placeholder={getLangText('Enter additional Websites/Publications if any')}/> placeholder={getLangText('Enter additional Websites/Publications if any')} />
</Property> </Property>
<Property <Property
name='conceptual_overview' name='conceptual_overview'
label={getLangText('Short text about the Artist')} label={getLangText('Short text about the Artist')}
expanded={!this.props.disabled || !!this.props.piece.extra_data.conceptual_overview}> expanded={!disabled || !!extraData.conceptual_overview}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={this.props.piece.extra_data.conceptual_overview} defaultValue={extraData.conceptual_overview}
placeholder={getLangText('Enter a short bio about the Artist')} placeholder={getLangText('Enter a short bio about the Artist')} />
/>
</Property> </Property>
</Form> </Form>
); );

View File

@ -20,11 +20,10 @@ import { getLangText } from '../../../../../../utils/lang_utils';
let IkonotvArtworkDetailsForm = React.createClass({ let IkonotvArtworkDetailsForm = React.createClass({
propTypes: { propTypes: {
handleSuccess: React.PropTypes.func,
piece: React.PropTypes.object.isRequired, piece: React.PropTypes.object.isRequired,
disabled: React.PropTypes.bool, disabled: React.PropTypes.bool,
handleSuccess: React.PropTypes.func,
isInline: React.PropTypes.bool isInline: React.PropTypes.bool
}, },
@ -35,8 +34,8 @@ let IkonotvArtworkDetailsForm = React.createClass({
}, },
getFormData() { getFormData() {
let extradata = {}; const extradata = {};
let formRefs = this.refs.form.refs; const formRefs = this.refs.form.refs;
// Put additional fields in extra data object // Put additional fields in extra data object
Object Object
@ -53,20 +52,23 @@ let IkonotvArtworkDetailsForm = React.createClass({
}, },
handleSuccess() { handleSuccess() {
let notification = new GlobalNotificationModel('Artwork details successfully updated', 'success', 10000); const notification = new GlobalNotificationModel(getLangText('Artwork details successfully updated'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
render() { render() {
let buttons, spinner, heading; const { disabled, isInline, handleSuccess, piece } = this.props;
let { isInline, handleSuccess } = this.props;
let buttons;
let spinner;
let heading;
if (!isInline) { if (!isInline) {
buttons = ( buttons = (
<button <button
type="submit" type="submit"
className="btn btn-default btn-wide" className="btn btn-default btn-wide"
disabled={this.props.disabled}> disabled={disabled}>
{getLangText('Proceed to artist details')} {getLangText('Proceed to artist details')}
</button> </button>
); );
@ -74,7 +76,7 @@ let IkonotvArtworkDetailsForm = React.createClass({
spinner = ( spinner = (
<div className="modal-footer"> <div className="modal-footer">
<p className="pull-right"> <p className="pull-right">
<AscribeSpinner color='dark-blue' size='md'/> <AscribeSpinner color='dark-blue' size='md' />
</p> </p>
</div> </div>
); );
@ -88,13 +90,15 @@ let IkonotvArtworkDetailsForm = React.createClass({
); );
} }
if (this.props.piece && this.props.piece.id && this.props.piece.extra_data) { if (piece.id && piece.extra_data) {
const { extra_data: extraData = {} } = piece;
return ( return (
<Form <Form
disabled={this.props.disabled} disabled={disabled}
className="ascribe-form-bordered" className="ascribe-form-bordered"
ref='form' ref='form'
url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: this.props.piece.id})} url={requests.prepareUrl(ApiUrls.piece_extradata, { piece_id: piece.id })}
handleSuccess={handleSuccess || this.handleSuccess} handleSuccess={handleSuccess || this.handleSuccess}
getFormData={this.getFormData} getFormData={this.getFormData}
buttons={buttons} buttons={buttons}
@ -103,56 +107,56 @@ let IkonotvArtworkDetailsForm = React.createClass({
<Property <Property
name='medium' name='medium'
label={getLangText('Medium')} label={getLangText('Medium')}
expanded={!this.props.disabled || !!this.props.piece.extra_data.medium}> expanded={!disabled || !!extraData.medium}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={this.props.piece.extra_data.medium} defaultValue={extraData.medium}
placeholder={getLangText('The medium of the file (i.e. photo, video, other, ...)')}/> placeholder={getLangText('The medium of the file (i.e. photo, video, other, ...)')} />
</Property> </Property>
<Property <Property
name='size_duration' name='size_duration'
label={getLangText('Size/Duration')} label={getLangText('Size/Duration')}
expanded={!this.props.disabled || !!this.props.piece.extra_data.size_duration}> expanded={!disabled || !!extraData.size_duration}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={this.props.piece.extra_data.size_duration} defaultValue={extraData.size_duration}
placeholder={getLangText('Size in centimeters. Duration in minutes.')}/> placeholder={getLangText('Size in centimeters. Duration in minutes.')} />
</Property> </Property>
<Property <Property
name='copyright' name='copyright'
label={getLangText('Copyright')} label={getLangText('Copyright')}
expanded={!this.props.disabled || !!this.props.piece.extra_data.copyright}> expanded={!disabled || !!extraData.copyright}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={this.props.piece.extra_data.copyright} defaultValue={extraData.copyright}
placeholder={getLangText('Which copyright is attached to this work?')}/> placeholder={getLangText('Which copyright is attached to this work?')} />
</Property> </Property>
<Property <Property
name='courtesy_of' name='courtesy_of'
label={getLangText('Courtesy of')} label={getLangText('Courtesy of')}
expanded={!this.props.disabled || !!this.props.piece.extra_data.courtesy_of}> expanded={!disabled || !!extraData.courtesy_of}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={this.props.piece.extra_data.courtesy_of} defaultValue={extraData.courtesy_of}
placeholder={getLangText('The current owner of the artwork')}/> placeholder={getLangText('The current owner of the artwork')} />
</Property> </Property>
<Property <Property
name='copyright_of_photography' name='copyright_of_photography'
label={getLangText('Copyright of Photography')} label={getLangText('Copyright of Photography')}
expanded={!this.props.disabled || !!this.props.piece.extra_data.copyright_of_photography}> expanded={!disabled || !!extraData.copyright_of_photography}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={this.props.piece.extra_data.copyright_of_photography} defaultValue={extraData.copyright_of_photography}
placeholder={getLangText('Who should be attributed for the photography?')}/> placeholder={getLangText('Who should be attributed for the photography?')} />
</Property> </Property>
<Property <Property
name='additional_details' name='additional_details'
label={getLangText('Additional Details about the artwork')} label={getLangText('Additional Details about the artwork')}
expanded={!this.props.disabled || !!this.props.piece.extra_data.additional_details}> expanded={!disabled || !!extraData.additional_details}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={this.props.piece.extra_data.additional_details} defaultValue={extraData.additional_details}
placeholder={getLangText('Insert artwork overview')}/> placeholder={getLangText('Insert artwork overview')} />
</Property> </Property>
</Form> </Form>
); );

View File

@ -49,7 +49,7 @@ let IkonotvRegisterPiece = React.createClass({
return mergeOptions( return mergeOptions(
UserStore.getState(), UserStore.getState(),
PieceListStore.getState(), PieceListStore.getState(),
PieceStore.getState(), PieceStore.getInitialState(),
WhitelabelStore.getState(), WhitelabelStore.getState(),
{ {
step: 0, step: 0,
@ -65,11 +65,7 @@ let IkonotvRegisterPiece = React.createClass({
UserActions.fetchCurrentUser(); UserActions.fetchCurrentUser();
WhitelabelActions.fetchWhitelabel(); WhitelabelActions.fetchWhitelabel();
// Before we load the new piece, we reset the piece store to delete old data that we do const queryParams = this.props.location.query;
// not want to display to the user.
PieceActions.updatePiece({});
let queryParams = this.props.location.query;
// Since every step of this register process is atomic, // Since every step of this register process is atomic,
// we may need to enter the process at step 1 or 2. // we may need to enter the process at step 1 or 2.
@ -78,8 +74,8 @@ let IkonotvRegisterPiece = React.createClass({
// //
// We're using 'in' here as we want to know if 'piece_id' is present in the url, // 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. // we don't care about the value.
if (queryParams && 'piece_id' in queryParams) { if ('piece_id' in queryParams) {
PieceActions.fetchOne(queryParams.piece_id); PieceActions.fetchPiece(queryParams.piece_id);
} }
}, },
@ -95,55 +91,50 @@ let IkonotvRegisterPiece = React.createClass({
}, },
handleRegisterSuccess(response){ handleRegisterSuccess(response) {
this.refreshPieceList(); this.refreshPieceList();
// also start loading the piece for the next step // Also load the newly registered piece for the next step
if(response && response.piece) { if (response && response.piece) {
PieceActions.updatePiece(response.piece); PieceActions.updatePiece(response.piece);
} }
if (!this.canSubmit()) { if (!this.canSubmit()) {
this.history.push('/collection'); this.history.push('/collection');
} } else {
else { this.nextSlide({ piece_id: response.piece.id });
this.incrementStep();
this.refs.slidesContainer.nextSlide();
} }
}, },
handleAdditionalDataSuccess() { handleAdditionalDataSuccess() {
// We need to refetch the piece again after submitting the additional data // 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 // since we want it's otherData to be displayed when the user choses to click
// on the browsers back button. // on the browsers back button.
PieceActions.fetchOne(this.state.piece.id); PieceActions.fetchPiece(this.state.piece.id);
this.refreshPieceList(); this.refreshPieceList();
this.incrementStep(); this.nextSlide();
this.refs.slidesContainer.nextSlide();
}, },
handleLoanSuccess(response) { handleLoanSuccess(response) {
this.setState({ pageExitWarning: null }); this.setState({ pageExitWarning: null });
let notification = new GlobalNotificationModel(response.notification, 'success', 10000); const notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
this.refreshPieceList(); this.refreshPieceList();
PieceActions.fetchOne(this.state.piece.id);
this.history.push(`/pieces/${this.state.piece.id}`); this.history.push(`/pieces/${this.state.piece.id}`);
}, },
// We need to increase the step to lock the forms that are already filled out nextSlide(queryParams) {
incrementStep() { // We need to increase the step to lock the forms that are already filled out
// also increase step
let newStep = this.state.step + 1;
this.setState({ this.setState({
step: newStep step: this.state.step + 1
}); });
this.refs.slidesContainer.nextSlide(queryParams);
}, },
refreshPieceList() { refreshPieceList() {
@ -155,7 +146,7 @@ let IkonotvRegisterPiece = React.createClass({
canSubmit() { canSubmit() {
let currentUser = this.state.currentUser; let currentUser = this.state.currentUser;
let whitelabel = this.state.whitelabel; let whitelabel = this.state.whitelabel;
return currentUser && currentUser.acl && currentUser.acl.acl_wallet_submit && whitelabel && whitelabel.user; return currentUser.acl && currentUser.acl.acl_wallet_submit && whitelabel.user;
}, },
getSlideArtistDetails() { getSlideArtistDetails() {
@ -166,13 +157,14 @@ let IkonotvRegisterPiece = React.createClass({
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}> <Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<IkonotvArtistDetailsForm <IkonotvArtistDetailsForm
handleSuccess={this.handleAdditionalDataSuccess} handleSuccess={this.handleAdditionalDataSuccess}
piece={this.state.piece}/> piece={this.state.piece} />
</Col> </Col>
</Row> </Row>
</div> </div>
); );
} else {
return null;
} }
return null;
}, },
getSlideArtworkDetails() { getSlideArtworkDetails() {
@ -183,21 +175,21 @@ let IkonotvRegisterPiece = React.createClass({
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}> <Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<IkonotvArtworkDetailsForm <IkonotvArtworkDetailsForm
handleSuccess={this.handleAdditionalDataSuccess} handleSuccess={this.handleAdditionalDataSuccess}
piece={this.state.piece}/> piece={this.state.piece} />
</Col> </Col>
</Row> </Row>
</div> </div>
); );
} else {
return null;
} }
return null;
}, },
getSlideLoan() { getSlideLoan() {
if (this.canSubmit()) { if (this.canSubmit()) {
const { piece, whitelabel } = this.state; const { piece, whitelabel } = this.state;
let today = new Moment(); const today = new Moment();
let endDate = new Moment(); const endDate = new Moment().add(2, 'years');
endDate.add(2, 'years');
return ( return (
<div data-slide-title={getLangText('Loan')}> <div data-slide-title={getLangText('Loan')}>
@ -220,8 +212,9 @@ let IkonotvRegisterPiece = React.createClass({
</Row> </Row>
</div> </div>
); );
} else {
return null;
} }
return null;
}, },
render() { render() {
@ -247,7 +240,7 @@ let IkonotvRegisterPiece = React.createClass({
submitMessage={getLangText('Register')} submitMessage={getLangText('Register')}
isFineUploaderActive={true} isFineUploaderActive={true}
handleSuccess={this.handleRegisterSuccess} handleSuccess={this.handleRegisterSuccess}
location={this.props.location}/> location={this.props.location} />
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@ -30,7 +30,7 @@ let MarketAclButtonList = React.createClass({
componentDidMount() { componentDidMount() {
UserStore.listen(this.onChange); UserStore.listen(this.onChange);
UserActions.fetchCurrentUser(); UserActions.fetchCurrentUser.defer();
}, },
componentWillUnmount() { componentWillUnmount() {

View File

@ -3,6 +3,11 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import EditionActions from '../../../../../../actions/edition_actions';
import WhitelabelActions from '../../../../../../actions/whitelabel_actions';
import WhitelabelStore from '../../../../../../stores/whitelabel_store';
import MarketAdditionalDataForm from '../market_forms/market_additional_data_form'; import MarketAdditionalDataForm from '../market_forms/market_additional_data_form';
import AclFormFactory from '../../../../../ascribe_forms/acl_form_factory'; import AclFormFactory from '../../../../../ascribe_forms/acl_form_factory';
@ -11,10 +16,7 @@ import ConsignForm from '../../../../../ascribe_forms/form_consign';
import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper'; import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper';
import AclProxy from '../../../../../acl_proxy'; import AclProxy from '../../../../../acl_proxy';
import AscribeSpinner from '../../../../../ascribe_spinner';
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 ApiUrls from '../../../../../../constants/api_urls';
@ -26,8 +28,9 @@ let MarketSubmitButton = React.createClass({
availableAcls: React.PropTypes.object.isRequired, availableAcls: React.PropTypes.object.isRequired,
currentUser: React.PropTypes.object, currentUser: React.PropTypes.object,
editions: React.PropTypes.array.isRequired, editions: React.PropTypes.array.isRequired,
handleSuccess: React.PropTypes.func.isRequired,
className: React.PropTypes.string, className: React.PropTypes.string,
handleSuccess: React.PropTypes.func
}, },
getInitialState() { getInitialState() {
@ -50,22 +53,21 @@ let MarketSubmitButton = React.createClass({
canEditionBeSubmitted(edition) { canEditionBeSubmitted(edition) {
if (edition && edition.extra_data && edition.other_data) { if (edition && edition.extra_data && edition.other_data) {
const { extra_data, other_data } = edition; const {
extra_data: {
artist_bio: artistBio,
display_instructions: displayInstructions,
technology_details: technologyDetails,
work_description: workDescription
},
other_data: otherData } = edition;
if (extra_data.artist_bio && extra_data.work_description && return artistBio && displayInstructions && technologyDetails && workDescription && otherData.length;
extra_data.technology_details && extra_data.display_instructions &&
other_data.length > 0) {
return true;
}
} }
return false; return false;
}, },
getFormDataId() {
return getAclFormDataId(false, this.props.editions);
},
getAggregateEditionDetails() { getAggregateEditionDetails() {
const { editions } = this.props; const { editions } = this.props;
@ -82,13 +84,20 @@ let MarketSubmitButton = React.createClass({
}); });
}, },
handleAdditionalDataSuccess(pieceId) { getFormDataId() {
// Fetch newly updated piece to update the views return getAclFormDataId(false, this.props.editions);
PieceActions.fetchOne(pieceId); },
handleAdditionalDataSuccess() {
this.refs.consignModal.show(); this.refs.consignModal.show();
}, },
refreshEdition() {
if (this.props.editions.length === 1) {
EditionActions.fetchEdition(this.props.editions[0].bitcoin_id);
}
},
render() { render() {
const { availableAcls, currentUser, className, editions, handleSuccess } = this.props; const { availableAcls, currentUser, className, editions, handleSuccess } = this.props;
const { whitelabel: { name: whitelabelName = 'Market', user: whitelabelAdminEmail } } = this.state; const { whitelabel: { name: whitelabelName = 'Market', user: whitelabelAdminEmail } } = this.state;
@ -101,6 +110,10 @@ let MarketSubmitButton = React.createClass({
senderName: currentUser.username senderName: currentUser.username
}); });
// If only a single piece is selected, all the edition's extra_data and other_data will
// be the same, so we just take the first edition's
const { extra_data: extraData, other_data: otherData } = solePieceId ? editions[0] : {};
const triggerButton = ( const triggerButton = (
<button className={classNames('btn', 'btn-default', 'btn-sm', className)}> <button className={classNames('btn', 'btn-default', 'btn-sm', className)}>
{getLangText('CONSIGN TO %s', whitelabelName.toUpperCase())} {getLangText('CONSIGN TO %s', whitelabelName.toUpperCase())}
@ -126,16 +139,25 @@ let MarketSubmitButton = React.createClass({
aclName='acl_consign'> aclName='acl_consign'>
<ModalWrapper <ModalWrapper
trigger={triggerButton} trigger={triggerButton}
handleSuccess={this.handleAdditionalDataSuccess.bind(this, solePieceId)} handleSuccess={this.handleAdditionalDataSuccess}
title={getLangText('Add additional information')}> title={getLangText('Add additional information')}>
<MarketAdditionalDataForm <MarketAdditionalDataForm
extraData={extraData}
otherData={otherData}
pieceId={solePieceId} pieceId={solePieceId}
submitLabel={getLangText('Continue to consignment')} /> submitLabel={getLangText('Continue to consignment')} />
</ModalWrapper> </ModalWrapper>
<ModalWrapper <ModalWrapper
ref="consignModal" ref="consignModal"
handleSuccess={handleSuccess} handleCancel={this.refreshEdition}
handleSuccess={(...params) => {
if (typeof handleSuccess === 'function') {
handleSuccess(...params);
}
this.refreshEdition();
}}
title={getLangText('Consign artwork')}> title={getLangText('Consign artwork')}>
{consignForm} {consignForm}
</ModalWrapper> </ModalWrapper>

View File

@ -6,8 +6,12 @@ import MarketAdditionalDataForm from '../market_forms/market_additional_data_for
let MarketFurtherDetails = React.createClass({ let MarketFurtherDetails = React.createClass({
propTypes: { propTypes: {
pieceId: React.PropTypes.number, pieceId: React.PropTypes.number.isRequired,
editable: React.PropTypes.bool,
extraData: React.PropTypes.object,
handleSuccess: React.PropTypes.func, handleSuccess: React.PropTypes.func,
otherData: React.PropTypes.arrayOf(React.PropTypes.object)
}, },
render() { render() {

View File

@ -2,21 +2,18 @@
import React from 'react'; import React from 'react';
import Form from '../../../../../ascribe_forms/form';
import Property from '../../../../../ascribe_forms/property';
import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable';
import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
import AscribeSpinner from '../../../../../ascribe_spinner';
import GlobalNotificationModel from '../../../../../../models/global_notification_model'; import GlobalNotificationModel from '../../../../../../models/global_notification_model';
import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable';
import Form from '../../../../../ascribe_forms/form';
import Property from '../../../../../ascribe_forms/property';
import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_s3_fine_uploader_utils'; import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_s3_fine_uploader_utils';
import PieceActions from '../../../../../../actions/piece_actions'; import AscribeSpinner from '../../../../../ascribe_spinner';
import PieceStore from '../../../../../../stores/piece_store';
import ApiUrls from '../../../../../../constants/api_urls'; import ApiUrls from '../../../../../../constants/api_urls';
import AppConstants from '../../../../../../constants/application_constants'; import AppConstants from '../../../../../../constants/application_constants';
@ -28,16 +25,16 @@ import { getLangText } from '../../../../../../utils/lang_utils';
let MarketAdditionalDataForm = React.createClass({ let MarketAdditionalDataForm = React.createClass({
propTypes: { propTypes: {
pieceId: React.PropTypes.oneOfType([ pieceId: React.PropTypes.number.isRequired,
React.PropTypes.number,
React.PropTypes.string
]),
editable: React.PropTypes.bool, editable: React.PropTypes.bool,
extraData: React.PropTypes.object,
handleSuccess: React.PropTypes.func,
isInline: React.PropTypes.bool, isInline: React.PropTypes.bool,
otherData: React.PropTypes.arrayOf(React.PropTypes.object),
showHeading: React.PropTypes.bool, showHeading: React.PropTypes.bool,
showNotification: React.PropTypes.bool, showNotification: React.PropTypes.bool,
submitLabel: React.PropTypes.string, submitLabel: React.PropTypes.string
handleSuccess: React.PropTypes.func
}, },
getDefaultProps() { getDefaultProps() {
@ -48,50 +45,34 @@ let MarketAdditionalDataForm = React.createClass({
}, },
getInitialState() { getInitialState() {
const pieceStore = PieceStore.getState(); return {
// Allow the form to be submitted if there's already an additional image uploaded
return mergeOptions( isUploadReady: this.isUploadReadyOnChange(),
pieceStore, forceUpdateKey: 0
{
// Allow the form to be submitted if there's already an additional image uploaded
isUploadReady: this.isUploadReadyOnChange(pieceStore.piece),
forceUpdateKey: 0
});
},
componentDidMount() {
PieceStore.listen(this.onChange);
if (this.props.pieceId) {
PieceActions.fetchOne(this.props.pieceId);
} }
}, },
componentWillUnmount() { componentWillReceiveProps(nextProps) {
PieceStore.unlisten(this.onChange); if (this.props.extraData !== nextProps.extraData || this.props.otherData !== nextProps.otherData) {
}, this.setState({
// Allow the form to be submitted if the updated piece has an additional image uploaded
isUploadReady: this.isUploadReadyOnChange(),
onChange(state) { /**
Object.assign({}, state, { * Increment the forceUpdateKey to force the form to rerender on each change
// Allow the form to be submitted if the updated piece already has an additional image uploaded *
isUploadReady: this.isUploadReadyOnChange(state.piece), * 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).
* Increment the forceUpdateKey to force the form to rerender on each change */
* forceUpdateKey: this.state.forceUpdateKey + 1
* 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
});
this.setState(state);
}, },
getFormData() { getFormData() {
let extradata = {}; const extradata = {};
let formRefs = this.refs.form.refs; const formRefs = this.refs.form.refs;
// Put additional fields in extra data object // Put additional fields in extra data object
Object Object
@ -102,12 +83,12 @@ let MarketAdditionalDataForm = React.createClass({
return { return {
extradata: extradata, extradata: extradata,
piece_id: this.state.piece.id piece_id: this.props.pieceId
}; };
}, },
isUploadReadyOnChange(piece) { isUploadReadyOnChange() {
return piece && piece.other_data && piece.other_data.length > 0; return this.props.otherData && this.props.otherData.length;
}, },
handleSuccessWithNotification() { handleSuccessWithNotification() {
@ -115,7 +96,7 @@ let MarketAdditionalDataForm = React.createClass({
this.props.handleSuccess(); this.props.handleSuccess();
} }
let notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000); const notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
@ -126,11 +107,20 @@ let MarketAdditionalDataForm = React.createClass({
}, },
render() { render() {
const { editable, isInline, handleSuccess, showHeading, showNotification, submitLabel } = this.props; const {
const { piece } = this.state; editable,
let buttons, heading; extraData = {},
isInline,
handleSuccess,
otherData,
pieceId,
showHeading,
showNotification,
submitLabel } = this.props;
let spinner = <AscribeSpinner color='dark-blue' size='lg' />; let buttons;
let heading;
let spinner;
if (!isInline) { if (!isInline) {
buttons = ( buttons = (
@ -145,7 +135,7 @@ let MarketAdditionalDataForm = React.createClass({
spinner = ( spinner = (
<div className="modal-footer"> <div className="modal-footer">
<p className="pull-right"> <p className="pull-right">
{spinner} <AscribeSpinner color='dark-blue' size='md' />
</p> </p>
</div> </div>
); );
@ -159,64 +149,64 @@ let MarketAdditionalDataForm = React.createClass({
) : null; ) : null;
} }
if (piece && piece.id) { if (pieceId) {
return ( return (
<Form <Form
className="ascribe-form-bordered" className="ascribe-form-bordered"
ref='form' ref='form'
key={this.state.forceUpdateKey} key={this.state.forceUpdateKey}
url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: piece.id})} url={requests.prepareUrl(ApiUrls.piece_extradata, { piece_id: pieceId })}
handleSuccess={showNotification ? this.handleSuccessWithNotification : handleSuccess} handleSuccess={showNotification ? this.handleSuccessWithNotification : handleSuccess}
getFormData={this.getFormData} getFormData={this.getFormData}
buttons={buttons} buttons={buttons}
spinner={spinner} spinner={spinner}
disabled={!this.props.editable || !piece.acl.acl_edit}> disabled={!this.props.editable}>
{heading} {heading}
<FurtherDetailsFileuploader <FurtherDetailsFileuploader
label={getLangText('Marketplace Thumbnail Image')} label={getLangText('Marketplace Thumbnail Image')}
submitFile={function () {}} submitFile={function () {}}
setIsUploadReady={this.setIsUploadReady} setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile} isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
pieceId={piece.id} pieceId={pieceId}
otherData={piece.other_data} otherData={otherData}
editable={editable} /> editable={editable} />
<Property <Property
name='artist_bio' name='artist_bio'
label={getLangText('Artist Bio')} label={getLangText('Artist Bio')}
expanded={editable || !!piece.extra_data.artist_bio}> expanded={editable || !!extraData.artist_bio}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={piece.extra_data.artist_bio} defaultValue={extraData.artist_bio}
placeholder={getLangText('Enter a biography of the artist...')} placeholder={getLangText('Enter a biography of the artist...')}
required /> required />
</Property> </Property>
<Property <Property
name='work_description' name='work_description'
label={getLangText('Work Description')} label={getLangText('Work Description')}
expanded={editable || !!piece.extra_data.work_description}> expanded={editable || !!extraData.work_description}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={piece.extra_data.work_description} defaultValue={extraData.work_description}
placeholder={getLangText('Enter a description of the work...')} placeholder={getLangText('Enter a description of the work...')}
required /> required />
</Property> </Property>
<Property <Property
name='technology_details' name='technology_details'
label={getLangText('Technology Details')} label={getLangText('Technology Details')}
expanded={editable || !!piece.extra_data.technology_details}> expanded={editable || !!extraData.technology_details}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={piece.extra_data.technology_details} defaultValue={extraData.technology_details}
placeholder={getLangText('Enter technological details about the work...')} placeholder={getLangText('Enter technological details about the work...')}
required /> required />
</Property> </Property>
<Property <Property
name='display_instructions' name='display_instructions'
label={getLangText('Display Instructions')} label={getLangText('Display Instructions')}
expanded={editable || !!piece.extra_data.display_instructions}> expanded={editable || !!extraData.display_instructions}>
<InputTextAreaToggable <InputTextAreaToggable
rows={1} rows={1}
defaultValue={piece.extra_data.display_instructions} defaultValue={extraData.display_instructions}
placeholder={getLangText('Enter instructions on how to best display the work...')} placeholder={getLangText('Enter instructions on how to best display the work...')}
required /> required />
</Property> </Property>
@ -225,7 +215,7 @@ let MarketAdditionalDataForm = React.createClass({
} else { } else {
return ( return (
<div className="ascribe-loading-position"> <div className="ascribe-loading-position">
{spinner} <AscribeSpinner color='dark-blue' size='lg' />
</div> </div>
); );
} }

View File

@ -6,19 +6,23 @@ import { History } from 'react-router';
import Col from 'react-bootstrap/lib/Col'; import Col from 'react-bootstrap/lib/Col';
import Row from 'react-bootstrap/lib/Row'; import Row from 'react-bootstrap/lib/Row';
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';
import UserStore from '../../../../../stores/user_store';
import UserActions from '../../../../../actions/user_actions';
import WhitelabelActions from '../../../../../actions/whitelabel_actions';
import WhitelabelStore from '../../../../../stores/whitelabel_store';
import MarketAdditionalDataForm from './market_forms/market_additional_data_form'; import MarketAdditionalDataForm from './market_forms/market_additional_data_form';
import Property from '../../../../ascribe_forms/property'; import Property from '../../../../ascribe_forms/property';
import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece'; import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece';
import PieceActions from '../../../../../actions/piece_actions';
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 WhitelabelActions from '../../../../../actions/whitelabel_actions';
import WhitelabelStore from '../../../../../stores/whitelabel_store';
import SlidesContainer from '../../../../ascribe_slides_container/slides_container'; import SlidesContainer from '../../../../ascribe_slides_container/slides_container';
import { getLangText } from '../../../../../utils/lang_utils'; import { getLangText } from '../../../../../utils/lang_utils';
@ -35,6 +39,7 @@ let MarketRegisterPiece = React.createClass({
getInitialState(){ getInitialState(){
return mergeOptions( return mergeOptions(
PieceListStore.getState(), PieceListStore.getState(),
PieceStore.getInitialState(),
UserStore.getState(), UserStore.getState(),
WhitelabelStore.getState(), WhitelabelStore.getState(),
{ {
@ -44,19 +49,27 @@ let MarketRegisterPiece = React.createClass({
componentDidMount() { componentDidMount() {
PieceListStore.listen(this.onChange); PieceListStore.listen(this.onChange);
PieceStore.listen(this.onChange);
UserStore.listen(this.onChange); UserStore.listen(this.onChange);
WhitelabelStore.listen(this.onChange); WhitelabelStore.listen(this.onChange);
UserActions.fetchCurrentUser(); UserActions.fetchCurrentUser();
WhitelabelActions.fetchWhitelabel(); WhitelabelActions.fetchWhitelabel();
// Reset the piece store to make sure that we don't display old data const queryParams = this.props.location.query;
// if the user repeatedly registers works
PieceActions.updatePiece({}); // Load the correct piece if the user loads the second step directly
// by pressing on the back button or using the url
// 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 ('piece_id' in queryParams) {
PieceActions.fetchPiece(queryParams.piece_id);
}
}, },
componentWillUnmount() { componentWillUnmount() {
PieceListStore.unlisten(this.onChange); PieceListStore.unlisten(this.onChange);
PieceStore.unlisten(this.onChange);
UserStore.unlisten(this.onChange); UserStore.unlisten(this.onChange);
WhitelabelStore.unlisten(this.onChange); WhitelabelStore.unlisten(this.onChange);
}, },
@ -68,15 +81,12 @@ let MarketRegisterPiece = React.createClass({
handleRegisterSuccess(response) { handleRegisterSuccess(response) {
this.refreshPieceList(); this.refreshPieceList();
// Use the response's piece for the next step if available // Also load the newly registered piece for the next step
let pieceId = null;
if (response && response.piece) { if (response && response.piece) {
pieceId = response.piece.id;
PieceActions.updatePiece(response.piece); PieceActions.updatePiece(response.piece);
} }
this.incrementStep(); this.nextSlide({ piece_id: response.piece.id });
this.refs.slidesContainer.nextSlide({ piece_id: pieceId });
}, },
handleAdditionalDataSuccess() { handleAdditionalDataSuccess() {
@ -85,21 +95,13 @@ let MarketRegisterPiece = React.createClass({
this.history.push('/collection'); this.history.push('/collection');
}, },
// We need to increase the step to lock the forms that are already filled out nextSlide(queryParams) {
incrementStep() { // We need to increase the step to lock the forms that are already filled out
this.setState({ this.setState({
step: this.state.step + 1 step: this.state.step + 1
}); });
},
getPieceFromQueryParam() { this.refs.slidesContainer.nextSlide(queryParams);
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() { refreshPieceList() {
@ -109,7 +111,9 @@ let MarketRegisterPiece = React.createClass({
}, },
render() { render() {
const { location } = this.props;
const { const {
piece,
step, step,
whitelabel: { whitelabel: {
name: whitelabelName = 'Market' name: whitelabelName = 'Market'
@ -125,7 +129,7 @@ let MarketRegisterPiece = React.createClass({
pending: 'glyphicon glyphicon-chevron-right', pending: 'glyphicon glyphicon-chevron-right',
completed: 'glyphicon glyphicon-lock' completed: 'glyphicon glyphicon-lock'
}} }}
location={this.props.location}> location={location}>
<div data-slide-title={getLangText('Register work')}> <div data-slide-title={getLangText('Register work')}>
<Row className="no-margin"> <Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}> <Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
@ -137,7 +141,7 @@ let MarketRegisterPiece = React.createClass({
isFineUploaderActive={true} isFineUploaderActive={true}
enableSeparateThumbnail={false} enableSeparateThumbnail={false}
handleSuccess={this.handleRegisterSuccess} handleSuccess={this.handleRegisterSuccess}
location={this.props.location}> location={location}>
<Property <Property
name="num_editions" name="num_editions"
label={getLangText('Specify editions')}> label={getLangText('Specify editions')}>
@ -155,8 +159,10 @@ let MarketRegisterPiece = React.createClass({
<Row className="no-margin"> <Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}> <Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<MarketAdditionalDataForm <MarketAdditionalDataForm
extraData={piece.extra_data}
handleSuccess={this.handleAdditionalDataSuccess} handleSuccess={this.handleAdditionalDataSuccess}
pieceId={this.getPieceFromQueryParam()} otherData={piece.other_data}
pieceId={piece.id}
showHeading /> showHeading />
</Col> </Col>
</Row> </Row>

View File

@ -1,15 +0,0 @@
'use strict';
import requests from '../utils/requests';
let PieceFetcher = {
/**
* Fetch a piece from the API.
*/
fetchOne(id) {
return requests.get('piece', {'piece_id': id});
}
};
export default PieceFetcher;

View File

@ -0,0 +1,19 @@
'use strict';
import requests from '../utils/requests';
import PieceActions from '../actions/piece_actions';
const PieceSource = {
lookupPiece: {
remote(state, pieceId) {
return requests.get('piece', { piece_id: pieceId });
},
success: PieceActions.successFetchPiece,
error: PieceActions.errorPiece
}
};
export default PieceSource;

View File

@ -120,7 +120,7 @@ class EditionListStore {
.catch(console.logGlobal); .catch(console.logGlobal);
} }
onSelectEdition({pieceId, editionId, toValue}) { onSelectEdition({ pieceId, editionId, toValue }) {
this.editionList[pieceId].forEach((edition) => { this.editionList[pieceId].forEach((edition) => {
// Taken from: http://stackoverflow.com/a/519157/1263876 // Taken from: http://stackoverflow.com/a/519157/1263876

View File

@ -12,6 +12,16 @@ import { mergeOptions } from '../utils/general_utils';
class EditionStore { class EditionStore {
constructor() { constructor() {
this.getInitialState();
this.bindActions(EditionActions);
this.registerAsync(mergeOptions(EditionSource, CoaSource));
this.exportPublicMethods({
getInitialState: this.getInitialState.bind(this)
});
}
getInitialState() {
this.edition = {}; this.edition = {};
this.editionMeta = { this.editionMeta = {
err: null err: null
@ -20,8 +30,11 @@ class EditionStore {
err: null err: null
}; };
this.bindActions(EditionActions); return {
this.registerAsync(mergeOptions(EditionSource, CoaSource)); edition: this.edition,
editionMeta: this.editionMeta,
coaMeta: this.coaMeta
};
} }
onFetchEdition(editionId) { onFetchEdition(editionId) {
@ -61,17 +74,6 @@ class EditionStore {
} }
} }
onFlushEdition() {
this.edition = {};
this.editionMeta = {
err: null,
idToFetch: null
};
this.coaMeta = {
err: null
};
}
onErrorEdition(err) { onErrorEdition(err) {
console.logGlobal(err); console.logGlobal(err);
this.editionMeta.err = err; this.editionMeta.err = err;
@ -86,6 +88,10 @@ class EditionStore {
this.coaMeta.err = err; this.coaMeta.err = err;
} }
} }
onFlushEdition() {
this.getInitialState();
}
} }
export default alt.createStore(EditionStore, 'EditionStore'); export default alt.createStore(EditionStore, 'EditionStore');

View File

@ -60,8 +60,8 @@ class PieceListStore {
* point anyway. Then, this problem is automatically resolved. * point anyway. Then, this problem is automatically resolved.
*/ */
pieceList.forEach((piece, i) => { pieceList.forEach((piece, i) => {
let oldPiece = this.pieceList[i]; const oldPiece = this.pieceList[i];
if(oldPiece) { if (oldPiece) {
piece = React.addons.update(piece, { piece = React.addons.update(piece, {
show: { $set: oldPiece.show } show: { $set: oldPiece.show }
}); });
@ -76,12 +76,11 @@ class PieceListStore {
this.requestActions = res.actions; this.requestActions = res.actions;
} }
onUpdatePropertyForPiece({pieceId, key, value}) { onUpdatePropertyForPiece({ pieceId, key, value }) {
let filteredPieceList = this.pieceList.filter((piece) => piece.id === pieceId); const filteredPieceList = this.pieceList.filter((piece) => piece.id === pieceId);
if(filteredPieceList.length === 1) { if (filteredPieceList.length === 1) {
const piece = filteredPieceList[0];
let piece = filteredPieceList[0];
piece[key] = value; piece[key] = value;
} else { } else {
throw new Error('Could not find a matching piece in piece list since its either not there or piecelist contains duplicates.'); throw new Error('Could not find a matching piece in piece list since its either not there or piecelist contains duplicates.');

View File

@ -1,32 +1,73 @@
'use strict'; 'use strict';
import { alt } from '../alt'; import { alt } from '../alt';
import PieceActions from '../actions/piece_actions'; import PieceActions from '../actions/piece_actions';
import PieceSource from '../sources/piece_source';
class PieceStore { class PieceStore {
constructor() { constructor() {
this.piece = {}; this.getInitialState();
this.pieceError = null;
this.bindActions(PieceActions); this.bindActions(PieceActions);
this.registerAsync(PieceSource);
this.exportPublicMethods({
getInitialState: this.getInitialState.bind(this)
});
}
getInitialState() {
this.piece = {};
this.pieceMeta = {
err: null
};
return {
piece: this.piece,
pieceMeta: this.pieceMeta
}
}
onFetchPiece(pieceId) {
this.getInstance().lookupPiece(pieceId);
// Prevent alt from sending an empty change event when a request is sent
// off to the source
this.preventDefault();
}
onSuccessFetchPiece({ piece }) {
if (piece) {
this.onUpdatePiece(piece);
} else {
this.pieceMeta.err = new Error('Problem fetching the piece');
console.logGlobal(this.pieceMeta.err);
}
}
onErrorPiece(err) {
console.logGlobal(err);
this.pieceMeta.err = err;
}
onFlushPiece() {
this.getInitialState();
} }
onUpdatePiece(piece) { onUpdatePiece(piece) {
this.piece = piece; this.piece = piece;
this.pieceError = null; this.pieceMeta.err = null;
} }
onUpdateProperty({key, value}) { onUpdateProperty({ key, value }) {
if(this.piece && key in this.piece) { if (this.piece && key in this.piece) {
this.piece[key] = value; this.piece[key] = value;
} else { } else {
throw new Error('There is no piece defined in PieceStore or the piece object does not have the property you\'re looking for.'); 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'); export default alt.createStore(PieceStore, 'PieceStore');

View File

@ -238,7 +238,7 @@ $vivi23--highlight-color: #de2600;
&.active:hover{ &.active:hover{
background-color: $vivi23--highlight-color; background-color: $vivi23--highlight-color;
border-color: $vivi23--highlight-color; border-color: $vivi23--highlight-color;
color: $vivi23--highlight-color; color: $vivi23--bg-color;
} }
} }