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

Add getInitialState and source pattern for PieceStore

This commit is contained in:
Brett Sun 2016-01-14 13:20:45 +01:00
parent a5442e5acd
commit 7136f2ce6c
14 changed files with 252 additions and 232 deletions

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

@ -52,6 +52,7 @@ let EditionContainer = React.createClass({
// 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.flushEdition();
this.loadEdition(nextProps.params.editionId); this.loadEdition(nextProps.params.editionId);
} }
}, },

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 { pieceMeta: { err: pieceErr } } = this.state;
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.")));
} }
}, },
@ -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
@ -141,7 +144,7 @@ let PieceContainer = React.createClass({
}, },
handleEditionCreationSuccess() { handleEditionCreationSuccess() {
PieceActions.updateProperty({key: 'num_editions', value: 0}); PieceActions.updateProperty({ key: 'num_editions', value: 0 });
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy); this.state.orderBy, this.state.orderAsc, this.state.filterBy);
this.toggleCreateEditionsDialog(); this.toggleCreateEditionsDialog();
@ -156,29 +159,28 @@ 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 />);
} }
}, },
handlePollingSuccess(pieceId, numEditions) { handlePollingSuccess(pieceId, numEditions) {
// we need to refresh the num_editions property of the actual piece we're looking at // we need to refresh the num_editions property of the actual piece we're looking at
PieceActions.updateProperty({ PieceActions.updateProperty({
key: 'num_editions', key: 'num_editions',
@ -192,24 +194,24 @@ let PieceContainer = React.createClass({
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy); this.state.orderBy, this.state.orderAsc, this.state.filterBy);
let notification = new GlobalNotificationModel('Editions successfully created', 'success', 10000); const notification = new GlobalNotificationModel('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
@ -235,12 +237,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>
@ -252,7 +254,7 @@ 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].join(', '));
return ( return (
@ -296,7 +298,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)')}
@ -306,7 +308,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

@ -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,7 +133,7 @@ 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;
@ -146,8 +141,11 @@ let PrizePieceContainer = React.createClass({
} }
// 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;
} }
}); });
@ -322,7 +320,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>
@ -354,7 +352,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) => {
@ -379,7 +377,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}
@ -390,7 +388,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
@ -408,14 +406,14 @@ let PrizePieceRatings = React.createClass({
ratingAmount={5} /> ratingAmount={5} />
</div> </div>
<Note <Note
id={() => {return {'piece_id': this.props.piece.id}; }} id={() => { return { '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;
@ -432,33 +430,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={() => {}}
@ -471,8 +470,9 @@ let PrizePieceDetails = React.createClass({
</Form> </Form>
</CollapsibleParagraph> </CollapsibleParagraph>
); );
} else {
return null;
} }
return null;
} }
}); });

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) {
@ -82,19 +85,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].join(', '));
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}
@ -103,14 +108,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

@ -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() {
@ -157,8 +148,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 = (
@ -201,7 +191,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) {
@ -91,13 +86,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')}
@ -106,28 +103,28 @@ 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].join(', '));
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}
@ -135,8 +132,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

@ -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() {
@ -160,7 +151,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() {
@ -171,13 +162,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() {
@ -188,21 +180,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')}>
@ -225,8 +217,9 @@ let IkonotvRegisterPiece = React.createClass({
</Row> </Row>
</div> </div>
); );
} else {
return null;
} }
return null;
}, },
render() { render() {
@ -252,7 +245,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

@ -84,7 +84,7 @@ let MarketSubmitButton = React.createClass({
handleAdditionalDataSuccess(pieceId) { handleAdditionalDataSuccess(pieceId) {
// Fetch newly updated piece to update the views // Fetch newly updated piece to update the views
PieceActions.fetchOne(pieceId); PieceActions.fetchPiece(pieceId);
this.refs.consignModal.show(); this.refs.consignModal.show();
}, },

View File

@ -63,7 +63,7 @@ let MarketAdditionalDataForm = React.createClass({
PieceStore.listen(this.onChange); PieceStore.listen(this.onChange);
if (this.props.pieceId) { if (this.props.pieceId) {
PieceActions.fetchOne(this.props.pieceId); PieceActions.fetchPiece(this.props.pieceId);
} }
}, },
@ -115,7 +115,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);
}, },
@ -159,7 +159,7 @@ let MarketAdditionalDataForm = React.createClass({
) : null; ) : null;
} }
if (piece && piece.id) { if (piece.id) {
return ( return (
<Form <Form
className="ascribe-form-bordered" className="ascribe-form-bordered"

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/edition_actions';
const PieceSource = {
lookupPiece: {
remote(state) {
return requests.get('piece', { piece_id: state.pieceMeta.idToFetch });
},
success: PieceActions.successFetchPiece,
error: PieceActions.errorPiece
}
};
export default PieceSource;

View File

@ -1,32 +1,71 @@
'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,
idToFetch: null
};
return {
piece: this.piece,
pieceMeta: this.pieceMeta
}
}
onFetchPiece(idToFetch) {
this.pieceMeta.idToFetch = idToFetch;
this.getInstance.lookupPiece();
}
onSuccessFetchPiece({ piece }) {
if (piece) {
this.onUpdatePiece(piece);
} else {
this.pieceMeta.err = new Error('Problem fetching the piece');
}
}
onErrorPiece(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;
this.pieceMeta.idToFetch = 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');