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

Merge branch 'AD-419-decouple-piece-registration-from-' into AD-565-add-landing-page-for-sluice

This commit is contained in:
vrde 2015-07-13 20:02:42 +02:00
commit 3f0cdbad06
22 changed files with 496 additions and 397 deletions

View File

@ -7,7 +7,8 @@ import PieceFetcher from '../fetchers/piece_fetcher';
class PieceActions { class PieceActions {
constructor() { constructor() {
this.generateActions( this.generateActions(
'updatePiece' 'updatePiece',
'updateProperty'
); );
} }

View File

@ -14,6 +14,21 @@ class PieceListActions {
} }
fetchPieceList(page, pageSize, search, orderBy, orderAsc) { fetchPieceList(page, pageSize, search, orderBy, orderAsc) {
// To prevent flickering on a pagination request,
// we overwrite the piecelist with an empty list before
// pieceListCount === -1 defines the loading state
this.actions.updatePieceList({
page,
pageSize,
search,
orderBy,
orderAsc,
'pieceList': [],
'pieceListCount': -1
});
// afterwards, we can load the list
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
PieceListFetcher PieceListFetcher
.fetch(page, pageSize, search, orderBy, orderAsc) .fetch(page, pageSize, search, orderBy, orderAsc)

View File

@ -8,7 +8,7 @@ import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
import Tooltip from 'react-bootstrap/lib/Tooltip'; import Tooltip from 'react-bootstrap/lib/Tooltip';
import AccordionListItemEditionWidget from './accordion_list_item_edition_widget'; import AccordionListItemEditionWidget from './accordion_list_item_edition_widget';
import AccordionListItemCreateEditions from './accordion_list_item_create_editions'; import CreateEditionsForm from '../ascribe_forms/create_editions_form';
import PieceListActions from '../../actions/piece_list_actions'; import PieceListActions from '../../actions/piece_list_actions';
import EditionListActions from '../../actions/edition_list_actions'; import EditionListActions from '../../actions/edition_list_actions';
@ -35,20 +35,6 @@ let AccordionListItem = React.createClass({
}; };
}, },
componentDidUpdate() {
if(this.props.content.num_editions === 0 && typeof this.state.pollingIntervalIndex === 'undefined') {
this.startPolling();
}
},
componentWillUnmount() {
clearInterval(this.state.pollingIntervalIndex);
},
onChange(state) {
this.setState(state);
},
getGlyphicon(){ getGlyphicon(){
if (this.props.content.requestAction){ if (this.props.content.requestAction){
return ( return (
@ -66,39 +52,32 @@ let AccordionListItem = React.createClass({
}); });
}, },
handleEditionCreationSuccess(response) { handleEditionCreationSuccess() {
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
PieceListActions.updatePropertyForPiece({pieceId: this.props.content.id, key: 'num_editions', value: 0}); PieceListActions.updatePropertyForPiece({pieceId: this.props.content.id, key: 'num_editions', value: 0});
this.toggleCreateEditionsDialog(); this.toggleCreateEditionsDialog();
}, },
startPolling() { onPollingSuccess(pieceId, numEditions) {
// start polling until editions are defined
let pollingIntervalIndex = setInterval(() => {
EditionListActions.fetchEditionList(this.props.content.id)
.then((res) => {
clearInterval(this.state.pollingIntervalIndex);
PieceListActions.updatePropertyForPiece({ PieceListActions.updatePropertyForPiece({
pieceId: this.props.content.id, pieceId,
key: 'num_editions', key: 'num_editions',
value: res.editions[0].num_editions value: numEditions
}); });
EditionListActions.toggleEditionList(this.props.content.id); EditionListActions.toggleEditionList(pieceId);
},
}) getCreateEditionsDialog() {
.catch(() => { if(this.props.content.num_editions < 1 && this.state.showCreateEditionsDialog) {
/* Ignore and keep going */ return (
}); <div className="ascribe-accordion-list-item-table col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2">
}, 5000); <CreateEditionsForm
pieceId={this.props.content.id}
this.setState({ handleSuccess={this.handleEditionCreationSuccess} />
pollingIntervalIndex </div>
}); );
}
}, },
render() { render() {
@ -141,7 +120,8 @@ let AccordionListItem = React.createClass({
<AccordionListItemEditionWidget <AccordionListItemEditionWidget
className="pull-right" className="pull-right"
piece={this.props.content} piece={this.props.content}
toggleCreateEditionsDialog={this.toggleCreateEditionsDialog}/> toggleCreateEditionsDialog={this.toggleCreateEditionsDialog}
onPollingSuccess={this.onPollingSuccess}/>
</div> </div>
</div> </div>
<span style={{'clear': 'both'}}></span> <span style={{'clear': 'both'}}></span>
@ -150,7 +130,8 @@ let AccordionListItem = React.createClass({
</div> </div>
</div> </div>
</div> </div>
{this.props.content.num_editions < 1 && this.state.showCreateEditionsDialog ? <AccordionListItemCreateEditions pieceId={this.props.content.id} handleSuccess={this.handleEditionCreationSuccess}/> : null}
{this.getCreateEditionsDialog()}
{/* this.props.children is AccordionListItemTableEditions */} {/* this.props.children is AccordionListItemTableEditions */}
{this.props.children} {this.props.children}

View File

@ -1,51 +0,0 @@
'use strict';
import React from 'react';
import Form from '../ascribe_forms/form';
import Property from '../ascribe_forms/property';
import apiUrls from '../../constants/api_urls';
import { getLangText } from '../../utils/lang_utils';
let AccordionListItemCreateEditions = React.createClass({
propTypes: {
pieceId: React.PropTypes.number,
handleSuccess: React.PropTypes.func
},
getFormData(){
return {
piece_id: parseInt(this.props.pieceId, 10)
};
},
render() {
return (
<div className="ascribe-accordion-list-item-table col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2">
<Form
ref='form'
url={apiUrls.editions}
getFormData={this.getFormData}
handleSuccess={this.props.handleSuccess}
spinner={
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button>
}>
<Property
name='num_editions'
label={getLangText('Number of editions')}>
<input
type="number"
placeholder="(e.g. 32)"
min={0}/>
</Property>
</Form>
</div>
);
}
});
export default AccordionListItemCreateEditions;

View File

@ -6,13 +6,16 @@ import classNames from 'classnames';
import EditionListActions from '../../actions/edition_list_actions'; import EditionListActions from '../../actions/edition_list_actions';
import EditionListStore from '../../stores/edition_list_store'; import EditionListStore from '../../stores/edition_list_store';
import CreateEditionsButton from '../ascribe_buttons/create_editions_button';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
let AccordionListItemEditionWidget = React.createClass({ let AccordionListItemEditionWidget = React.createClass({
propTypes: { propTypes: {
className: React.PropTypes.string, className: React.PropTypes.string,
piece: React.PropTypes.object.isRequired, piece: React.PropTypes.object.isRequired,
toggleCreateEditionsDialog: React.PropTypes.func.isRequired toggleCreateEditionsDialog: React.PropTypes.func.isRequired,
onPollingSuccess: React.PropTypes.func
}, },
getInitialState() { getInitialState() {
@ -55,16 +58,9 @@ let AccordionListItemEditionWidget = React.createClass({
let isEditionListOpen = this.state.isEditionListOpenForPieceId[pieceId] ? this.state.isEditionListOpenForPieceId[pieceId].show : false; let isEditionListOpen = this.state.isEditionListOpenForPieceId[pieceId] ? this.state.isEditionListOpenForPieceId[pieceId].show : false;
if(isEditionListOpen) { if(isEditionListOpen) {
if(typeof this.state.editionList[pieceId] === 'undefined') {
return (
<span className="glyph-ascribe-spool-chunked spin"/>
);
} else {
return ( return (
<span className="glyphicon glyphicon-menu-up" aria-hidden="true" style={{top: 2}}></span> <span className="glyphicon glyphicon-menu-up" aria-hidden="true" style={{top: 2}}></span>
); );
}
} else { } else {
return ( return (
<span className="glyphicon glyphicon-menu-down" aria-hidden="true" style={{top: 2}}></span> <span className="glyphicon glyphicon-menu-down" aria-hidden="true" style={{top: 2}}></span>
@ -76,23 +72,16 @@ let AccordionListItemEditionWidget = React.createClass({
let piece = this.props.piece; let piece = this.props.piece;
let numEditions = piece.num_editions; let numEditions = piece.num_editions;
if(numEditions === -1) { if(numEditions <= 0) {
return ( return (
<button <CreateEditionsButton
onClick={this.props.toggleCreateEditionsDialog} label={getLangText('Create editions')}
className={classNames('btn', 'btn-default', 'btn-xs', 'ascribe-accordion-list-item-edition-widget', this.props.className)}> className="btn-xs pull-right"
+ Editions piece={piece}
</button> toggleCreateEditionsDialog={this.props.toggleCreateEditionsDialog}
onPollingSuccess={this.props.onPollingSuccess}/>
); );
} } else if(numEditions === 1) {
else if(numEditions === 0) {
return (
<button disabled className={classNames('btn', 'btn-default', 'btn-xs', this.props.className)}>
Creating Editions <span className="glyph-ascribe-spool-chunked spin"/>
</button>
);
}
else if(numEditions === 1) {
let editionMapping = piece && piece.first_edition ? piece.first_edition.edition_number + '/' + piece.num_editions : ''; let editionMapping = piece && piece.first_edition ? piece.first_edition.edition_number + '/' + piece.num_editions : '';
return ( return (

View File

@ -105,6 +105,20 @@ let AclButton = React.createClass({
} }
}, },
getShareMessage(){
return (
`
${getLangText('Hi')},
${getLangText('I am sharing')}:
${this.getTitlesString()} ${getLangText('with you')}.
${getLangText('Truly yours')},
${this.props.currentUser.username}
`
);
},
render() { render() {
let shouldDisplay = this.props.availableAcls.indexOf(this.props.action) > -1; let shouldDisplay = this.props.availableAcls.indexOf(this.props.action) > -1;
let aclProps = this.actionProperties(); let aclProps = this.actionProperties();
@ -121,18 +135,6 @@ let AclButton = React.createClass({
{ aclProps.form } { aclProps.form }
</ModalWrapper> </ModalWrapper>
); );
},
getShareMessage(){
return (
`${getLangText('Hi')},
${getLangText('I am sharing')} :
${this.getTitlesString()}${getLangText('with you')}.
${getLangText('Truly yours')},
${this.props.currentUser.username}`
);
} }
}); });

View File

@ -7,14 +7,20 @@ import UserStore from '../../stores/user_store';
import AclButton from '../ascribe_buttons/acl_button'; import AclButton from '../ascribe_buttons/acl_button';
import DeleteButton from '../ascribe_buttons/delete_button'; import DeleteButton from '../ascribe_buttons/delete_button';
import CreateEditionButton from '../ascribe_buttons/create_editions_button';
let AclButtonList = React.createClass({ let AclButtonList = React.createClass({
propTypes: { propTypes: {
className: React.PropTypes.string, className: React.PropTypes.string,
editions: React.PropTypes.object, editions: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.array
]),
availableAcls: React.PropTypes.array, availableAcls: React.PropTypes.array,
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func,
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
])
}, },
getInitialState() { getInitialState() {
@ -69,8 +75,7 @@ let AclButtonList = React.createClass({
handleSuccess={this.props.handleSuccess} /> handleSuccess={this.props.handleSuccess} />
<DeleteButton <DeleteButton
editions={this.props.editions}/> editions={this.props.editions}/>
<CreateEditionButton {this.props.children}
piece={this.props.editions}/>
</div> </div>
); );
} }

View File

@ -1,42 +1,93 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router';
import Button from 'react-bootstrap/lib/Button';
import EditionDeleteForm from '../ascribe_forms/form_delete_edition';
import EditionRemoveFromCollectionForm from '../ascribe_forms/form_remove_editions_from_collection';
import ModalWrapper from '../ascribe_modal/modal_wrapper';
import AccordionListItemCreateEditions from './../ascribe_accordion_list/accordion_list_item_create_editions';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getAvailableAcls } from '../../utils/acl_utils';
import { getLangText } from '../../utils/lang_utils.js'
import EditionListActions from '../../actions/edition_list_actions'; import EditionListActions from '../../actions/edition_list_actions';
import EditionListStore from '../../stores/edition_list_store';
import { getAvailableAcls } from '../../utils/acl_utils';
import { getLangText } from '../../utils/lang_utils';
import classNames from 'classnames';
let CreateEditionsButton = React.createClass({ let CreateEditionsButton = React.createClass({
propTypes: { propTypes: {
piece: React.PropTypes.object.isRequired label: React.PropTypes.string,
className: React.PropTypes.string,
piece: React.PropTypes.object.isRequired,
toggleCreateEditionsDialog: React.PropTypes.func.isRequired,
onPollingSuccess: React.PropTypes.func
}, },
mixins: [Router.Navigation], getInitialState() {
return EditionListStore.getState();
},
componentDidMount() {
EditionListStore.listen(this.onChange);
},
componentWillUnmount() {
EditionListStore.unlisten(this.onChange);
clearInterval(this.state.pollingIntervalIndex);
},
onChange(state) {
this.setState(state);
},
componentDidUpdate() {
if(this.props.piece.num_editions === 0 && typeof this.state.pollingIntervalIndex === 'undefined') {
this.startPolling();
}
},
startPolling() {
// start polling until editions are defined
let pollingIntervalIndex = setInterval(() => {
EditionListActions.fetchEditionList(this.props.piece.id)
.then((res) => {
clearInterval(this.state.pollingIntervalIndex);
this.props.onPollingSuccess(this.props.piece.id, res.editions[0].num_editions);
})
.catch(() => {
/* Ignore and keep going */
});
}, 5000);
this.setState({
pollingIntervalIndex
});
},
render: function () { render: function () {
if (this.props.piece.constructor === Array){ let piece = this.props.piece;
return null;
} let availableAcls = getAvailableAcls(piece);
let availableAcls = getAvailableAcls([this.props.piece]);
if (availableAcls.indexOf('editions') === -1){ if (availableAcls.indexOf('editions') < -1 || piece.num_editions > 0){
return null; return null;
} }
if(piece.num_editions === 0 && typeof this.state.editionList[piece.id] === 'undefined') {
return ( return (
<button className="btn btn-default btn-sm"> <button
CREATE EDITIONS disabled
className={classNames('btn', 'btn-default', this.props.className)}>
{getLangText('Creating editions')} <span className="glyph-ascribe-spool-chunked spin"/>
</button> </button>
); );
} else {
return (
<button
className={classNames('btn', 'btn-default', this.props.className)}
onClick={this.props.toggleCreateEditionsDialog}>
{this.props.label}
</button>
);
}
} }
}); });

View File

@ -5,7 +5,6 @@ import Router from 'react-router';
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 Button from 'react-bootstrap/lib/Button';
import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import UserActions from '../../actions/user_actions'; import UserActions from '../../actions/user_actions';
@ -22,7 +21,6 @@ import Property from './../ascribe_forms/property';
import EditionDetailProperty from './detail_property'; import EditionDetailProperty from './detail_property';
import InputTextAreaToggable from './../ascribe_forms/input_textarea_toggable'; import InputTextAreaToggable from './../ascribe_forms/input_textarea_toggable';
import EditionHeader from './header';
import EditionFurtherDetails from './further_details'; import EditionFurtherDetails from './further_details';
//import PieceExtraDataForm from './../ascribe_forms/form_piece_extradata'; //import PieceExtraDataForm from './../ascribe_forms/form_piece_extradata';
@ -38,7 +36,6 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
import apiUrls from '../../constants/api_urls'; import apiUrls from '../../constants/api_urls';
import AppConstants from '../../constants/application_constants'; import AppConstants from '../../constants/application_constants';
import { getCookie } from '../../utils/fetch_api_utils';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
let Link = Router.Link; let Link = Router.Link;
@ -76,7 +73,12 @@ let Edition = React.createClass({
content={this.props.edition}/> content={this.props.edition}/>
</Col> </Col>
<Col md={6} className="ascribe-edition-details"> <Col md={6} className="ascribe-edition-details">
<EditionHeader content={this.props.edition}/> <div className="ascribe-detail-header">
<EditionDetailProperty label="TITLE" value={<div className="ascribe-detail-title">{this.props.edition.title}</div>} />
<EditionDetailProperty label="BY" value={this.props.edition.artist_name} />
<EditionDetailProperty label="DATE" value={ this.props.edition.date_created.slice(0, 4) } />
<hr/>
</div>
<EditionSummary <EditionSummary
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
edition={this.props.edition} /> edition={this.props.edition} />

View File

@ -1,26 +0,0 @@
'use strict';
import React from 'react';
import EditionDetailProperty from './detail_property';
let Header = React.createClass({
propTypes: {
content: React.PropTypes.object
},
render() {
var titleHtml = <div className="ascribe-detail-title">{this.props.content.title}</div>;
return (
<div className="ascribe-detail-header">
<EditionDetailProperty label="TITLE" value={titleHtml} />
<EditionDetailProperty label="BY" value={this.props.content.artist_name} />
<EditionDetailProperty label="DATE" value={ this.props.content.date_created.slice(0, 4) } />
<hr/>
</div>
);
}
});
export default Header;

View File

@ -10,26 +10,22 @@ import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph
import DetailProperty from './detail_property'; import DetailProperty from './detail_property';
import FurtherDetails from './further_details'; import FurtherDetails from './further_details';
import UserActions from '../../actions/user_actions'; import UserActions from '../../actions/user_actions';
import UserStore from '../../stores/user_store'; import UserStore from '../../stores/user_store';
import PieceActions from '../../actions/piece_actions';
import MediaContainer from './media_container'; import MediaContainer from './media_container';
import Header from './header'; import EditionDetailProperty from './detail_property';
import Form from './../ascribe_forms/form';
import Property from './../ascribe_forms/property';
import RequestActionForm from './../ascribe_forms/form_request_action';
import EditionActions from '../../actions/edition_actions';
import AclButtonList from './../ascribe_buttons/acl_button_list'; import AclButtonList from './../ascribe_buttons/acl_button_list';
import CreateEditionsForm from '../ascribe_forms/create_editions_form';
import CreateEditionsButton from '../ascribe_buttons/create_editions_button';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import apiUrls from '../../constants/api_urls';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
import { mergeOptions } from '../../utils/general_utils';
/** /**
* This is the component that implements display-specific functionality * This is the component that implements display-specific functionality
@ -41,7 +37,12 @@ let Piece = React.createClass({
}, },
getInitialState() { getInitialState() {
return UserStore.getState(); return mergeOptions(
UserStore.getState(),
{
showCreateEditionsDialog: false
}
);
}, },
componentDidMount() { componentDidMount() {
@ -57,8 +58,40 @@ let Piece = React.createClass({
this.setState(state); this.setState(state);
}, },
render() { toggleCreateEditionsDialog() {
this.setState({
showCreateEditionsDialog: !this.state.showCreateEditionsDialog
});
},
handleEditionCreationSuccess() {
PieceActions.updateProperty({key: 'num_editions', value: 0});
this.toggleCreateEditionsDialog();
},
getCreateEditionsDialog() {
if(this.props.piece.num_editions < 1 && this.state.showCreateEditionsDialog) {
return (
<div style={{marginTop: '1em'}}>
<CreateEditionsForm
pieceId={this.props.piece.id}
handleSuccess={this.handleEditionCreationSuccess} />
<hr/>
</div>
);
} else {
return (<hr/>);
}
},
handlePollingSuccess(pieceId, numEditions) {
PieceActions.updateProperty({
key: 'num_editions',
value: numEditions
});
},
render() {
return ( return (
<Row> <Row>
<Col md={6}> <Col md={6}>
@ -66,12 +99,31 @@ let Piece = React.createClass({
content={this.props.piece}/> content={this.props.piece}/>
</Col> </Col>
<Col md={6} className="ascribe-edition-details"> <Col md={6} className="ascribe-edition-details">
<Header <div className="ascribe-detail-header">
content={this.props.piece}/> <EditionDetailProperty label="TITLE" value={<div className="ascribe-detail-title">{this.props.piece.title}</div>} />
<PieceSummary <EditionDetailProperty label="BY" value={this.props.piece.artist_name} />
currentUser={this.state.currentUser} <EditionDetailProperty label="DATE" value={ this.props.piece.date_created.slice(0, 4) } />
{this.props.piece.num_editions > 0 ? <EditionDetailProperty label="NUMBER OF EDITIONS" value={ this.props.piece.num_editions } /> : null}
<hr/>
</div>
<div className="ascribe-detail-header">
<DetailProperty label={getLangText('REGISTREE')} value={ this.props.piece.user_registered } />
</div>
<AclButtonList
className="text-center ascribe-button-list"
availableAcls={this.props.piece.acl}
editions={this.props.piece}>
<CreateEditionsButton
label={getLangText('CREATE EDITIONS')}
className="btn-sm"
piece={this.props.piece} piece={this.props.piece}
handleSuccess={this.props.loadPiece}/> toggleCreateEditionsDialog={this.toggleCreateEditionsDialog}
onPollingSuccess={this.handlePollingSuccess}/>
</AclButtonList>
{this.getCreateEditionsDialog()}
<CollapsibleParagraph <CollapsibleParagraph
title="Further Details" title="Further Details"
show={this.props.piece.acl.indexOf('edit') > -1 show={this.props.piece.acl.indexOf('edit') > -1
@ -85,42 +137,10 @@ let Piece = React.createClass({
otherData={this.props.piece.other_data} otherData={this.props.piece.other_data}
handleSuccess={this.props.loadPiece}/> handleSuccess={this.props.loadPiece}/>
</CollapsibleParagraph> </CollapsibleParagraph>
</Col> </Col>
</Row> </Row>
); );
} }
}); });
let PieceSummary = React.createClass({
propTypes: {
piece: React.PropTypes.object
},
getActions(){
let actions = (
<Row>
<Col md={12}>
<AclButtonList
className="text-center ascribe-button-list"
availableAcls={this.props.piece.acl}
editions={this.props.piece}
handleSuccess={this.props.handleSuccess} />
</Col>
</Row>);
return actions;
//return null;
},
render() {
return (
<div className="ascribe-detail-header">
<DetailProperty label={getLangText('REGISTREE')} value={ this.props.piece.user_registered } />
{this.getActions()}
<hr/>
</div>
);
}
});
export default Piece; export default Piece;

View File

@ -0,0 +1,62 @@
'use strict';
import React from 'react';
import Form from '../ascribe_forms/form';
import Property from '../ascribe_forms/property';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import apiUrls from '../../constants/api_urls';
import { getLangText } from '../../utils/lang_utils';
let CreateEditionsForm = React.createClass({
propTypes: {
handleSuccess: React.PropTypes.func,
pieceId: React.PropTypes.number
},
getFormData(){
return {
piece_id: parseInt(this.props.pieceId, 10)
};
},
handleSuccess(response) {
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
if(this.props.handleSuccess) {
this.props.handleSuccess(response);
}
},
render() {
return (
<Form
ref='form'
url={apiUrls.editions}
getFormData={this.getFormData}
handleSuccess={this.handleSuccess}
spinner={
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button>
}>
<Property
name='num_editions'
label={getLangText('Number of editions')}>
<input
type="number"
placeholder="(e.g. 32)"
min={0}/>
</Property>
</Form>
);
}
});
export default CreateEditionsForm;

View File

@ -97,9 +97,9 @@ let LoginForm = React.createClass({
{getLangText(this.props.submitMessage)} {getLangText(this.props.submitMessage)}
</button>} </button>}
spinner={ spinner={
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner"> <span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" /> <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button> </span>
}> }>
<FormPropertyHeader> <FormPropertyHeader>
<h3>{getLangText(this.props.headerMessage)}</h3> <h3>{getLangText(this.props.headerMessage)}</h3>

View File

@ -0,0 +1,165 @@
'use strict';
import React from 'react';
import AppConstants from '../../constants/application_constants';
import Form from './form';
import Property from './property';
import FormPropertyHeader from './form_property_header';
import apiUrls from '../../constants/api_urls';
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
import { getCookie } from '../../utils/fetch_api_utils';
import { getLangText } from '../../utils/lang_utils';
let RegisterPieceForm = React.createClass({
getInitialState(){
return {
digitalWorkKey: null,
isUploadReady: false
};
},
getFormData(){
return {
digital_work_key: this.state.digitalWorkKey
};
},
submitKey(key){
this.setState({
digitalWorkKey: key
});
},
setIsUploadReady(isReady) {
this.setState({
isUploadReady: isReady
});
},
isReadyForFormSubmission(files) {
files = files.filter((file) => file.status !== 'deleted' && file.status !== 'canceled');
if (files.length > 0 && files[0].status === 'upload successful') {
return true;
} else {
return false;
}
},
render() {
return (
<Form
className="ascribe-form-bordered"
ref='form'
url={apiUrls.pieces_list}
getFormData={this.getFormData}
handleSuccess={this.props.handleSuccess}
buttons={<button
type="submit"
className="btn ascribe-btn ascribe-btn-login"
disabled={!this.state.isUploadReady}>
{getLangText('Register work')}
</button>}
spinner={
<span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</span>
}>
<FormPropertyHeader>
<h3>{getLangText('Register your work')}</h3>
</FormPropertyHeader>
<Property
ignoreFocus={true}>
<FileUploader
submitKey={this.submitKey}
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={this.isReadyForFormSubmission}
editable={this.props.isFineUploaderEditable}/>
</Property>
<Property
name='artist_name'
label={getLangText('Artist Name')}>
<input
type="text"
placeholder="(e.g. Andy Warhol)"
required/>
</Property>
<Property
name='title'
label={getLangText('Title')}>
<input
type="text"
placeholder="(e.g. 32 Campbell's Soup Cans)"
required/>
</Property>
<Property
name='date_created'
label={getLangText('Year Created')}>
<input
type="number"
placeholder="(e.g. 1962)"
min={0}
required/>
</Property>
{this.props.children}
</Form>
);
}
});
let FileUploader = React.createClass({
propTypes: {
setIsUploadReady: React.PropTypes.func,
submitKey: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func,
onClick: React.PropTypes.func,
// editable is used to lock react fine uploader in case
// a user is actually not logged in already to prevent him from droping files
// before login in
editable: React.PropTypes.bool
},
render() {
return (
<ReactS3FineUploader
onClick={this.props.onClick}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'digitalwork'
}}
createBlobRoutine={{
url: apiUrls.blob_digitalworks
}}
submitKey={this.props.submitKey}
validation={{
itemLimit: 100000,
sizeLimit: '25000000000'
}}
setIsUploadReady={this.props.setIsUploadReady}
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
areAssetsDownloadable={false}
areAssetsEditable={this.props.editable}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/',
customHeaders: {
'X-CSRFToken': getCookie('csrftoken')
}
}}
deleteFile={{
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
customHeaders: {
'X-CSRFToken': getCookie('csrftoken')
}
}}/>
);
}
});
export default RegisterPieceForm;

View File

@ -84,9 +84,9 @@ let SignupForm = React.createClass({
{getLangText(this.props.submitMessage)} {getLangText(this.props.submitMessage)}
</button>} </button>}
spinner={ spinner={
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner"> <span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" /> <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button> </span>
}> }>
<FormPropertyHeader> <FormPropertyHeader>
<h3>{getLangText(this.props.headerMessage)}</h3> <h3>{getLangText(this.props.headerMessage)}</h3>

View File

@ -68,9 +68,9 @@ let CoaVerifyForm = React.createClass({
{getLangText('Verify your Certificate of Authenticity')} {getLangText('Verify your Certificate of Authenticity')}
</button>} </button>}
spinner={ spinner={
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner"> <span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" /> <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button> </span>
}> }>
<Property <Property
name='message' name='message'

View File

@ -12,12 +12,16 @@ let Link = Router.Link;
let LoginContainer = React.createClass({ let LoginContainer = React.createClass({
propTypes: { propTypes: {
message: React.PropTypes.string message: React.PropTypes.string,
redirectOnLoggedIn: React.PropTypes.bool,
redirectOnLoginSuccess: React.PropTypes.bool
}, },
getDefaultProps() { getDefaultProps() {
return { return {
message: getLangText('Enter') + ' ascribe' message: getLangText('Enter') + ' ascribe',
redirectOnLoggedIn: true,
redirectOnLoginSuccess: true
}; };
}, },
@ -25,6 +29,8 @@ let LoginContainer = React.createClass({
return ( return (
<div className="ascribe-login-wrapper"> <div className="ascribe-login-wrapper">
<LoginForm <LoginForm
redirectOnLoggedIn={this.props.redirectOnLoggedIn}
redirectOnLoginSuccess={this.props.redirectOnLoginSuccess}
message={this.props.message} /> message={this.props.message} />
<div className="ascribe-login-text"> <div className="ascribe-login-text">
{getLangText('Not an ascribe user')}&#63; <Link to="signup">{getLangText('Sign up')}...</Link><br/> {getLangText('Not an ascribe user')}&#63; <Link to="signup">{getLangText('Sign up')}...</Link><br/>

View File

@ -82,9 +82,9 @@ let PasswordRequestResetForm = React.createClass({
{getLangText('Reset your password')} {getLangText('Reset your password')}
</button>} </button>}
spinner={ spinner={
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner"> <span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" /> <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button> </span>
}> }>
<Property <Property
name='email' name='email'
@ -129,9 +129,9 @@ let PasswordResetForm = React.createClass({
{getLangText('Reset your password')} {getLangText('Reset your password')}
</button>} </button>}
spinner={ spinner={
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner"> <span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" /> <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button> </span>
}> }>
<Property <Property
name='password' name='password'

View File

@ -2,14 +2,10 @@
import React from 'react'; import React from 'react';
import DatePicker from 'react-datepicker/dist/react-datepicker';
import Router from 'react-router'; import Router 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 AppConstants from '../constants/application_constants';
import LicenseActions from '../actions/license_actions'; import LicenseActions from '../actions/license_actions';
import LicenseStore from '../stores/license_store'; import LicenseStore from '../stores/license_store';
@ -21,17 +17,14 @@ import UserStore from '../stores/user_store';
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 Form from './ascribe_forms/form';
import Property from './ascribe_forms/property'; import Property from './ascribe_forms/property';
import PropertyCollapsible from './ascribe_forms/property_collapsible'; import PropertyCollapsible from './ascribe_forms/property_collapsible';
import FormPropertyHeader from './ascribe_forms/form_property_header'; import RegisterPieceForm from './ascribe_forms/form_register_piece';
//import FormPropertyHeader from './ascribe_forms/form_property_header';
import LoginContainer from './login_container'; import LoginContainer from './login_container';
import SlidesContainer from './ascribe_slides_container/slides_container'; import SlidesContainer from './ascribe_slides_container/slides_container';
import apiUrls from '../constants/api_urls';
import ReactS3FineUploader from './ascribe_uploader/react_s3_fine_uploader';
import { mergeOptions } from '../utils/general_utils'; import { mergeOptions } from '../utils/general_utils';
import { getCookie } from '../utils/fetch_api_utils'; import { getCookie } from '../utils/fetch_api_utils';
@ -46,8 +39,6 @@ let RegisterPiece = React.createClass( {
UserStore.getState(), UserStore.getState(),
PieceListStore.getState(), PieceListStore.getState(),
{ {
digitalWorkKey: null,
uploadStatus: false,
selectedLicense: 0, selectedLicense: 0,
isFineUploaderEditable: false isFineUploaderEditable: false
}); });
@ -97,32 +88,6 @@ let RegisterPiece = React.createClass( {
this.transitionTo('piece', {pieceId: response.piece.id}); this.transitionTo('piece', {pieceId: response.piece.id});
}, },
getFormData(){
return {
digital_work_key: this.state.digitalWorkKey
};
},
submitKey(key){
this.setState({
digitalWorkKey: key
});
},
setIsUploadReady(isReady) {
this.setState({
isUploadReady: isReady
});
},
isReadyForFormSubmission(files) {
files = files.filter((file) => file.status !== 'deleted' && file.status !== 'canceled');
if (files.length > 0 && files[0].status === 'upload successful') {
return true;
} else {
return false;
}
},
onLicenseChange(event){ onLicenseChange(event){
//console.log(this.state.licenses[event.target.selectedIndex].url); //console.log(this.state.licenses[event.target.selectedIndex].url);
this.setState({selectedLicense: event.target.selectedIndex}); this.setState({selectedLicense: event.target.selectedIndex});
@ -170,59 +135,9 @@ let RegisterPiece = React.createClass( {
onFocus={this.changeSlide}> onFocus={this.changeSlide}>
<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}>
<Form <RegisterPieceForm
className="ascribe-form-bordered" isFineUploaderEditable={this.state.isFineUploaderEditable}
ref='form' handleSuccess={this.handleSuccess}>
url={apiUrls.pieces_list}
getFormData={this.getFormData}
handleSuccess={this.handleSuccess}
buttons={<button
type="submit"
className="btn ascribe-btn ascribe-btn-login"
disabled={!this.state.isUploadReady}>
{getLangText('Register work')}
</button>}
spinner={
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button>
}>
<FormPropertyHeader>
<h3>{getLangText('Register your work')}</h3>
</FormPropertyHeader>
<Property
ignoreFocus={true}>
<FileUploader
submitKey={this.submitKey}
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={this.isReadyForFormSubmission}
editable={this.state.isFineUploaderEditable}/>
</Property>
<Property
name='artist_name'
label={getLangText('Artist Name')}>
<input
type="text"
placeholder="(e.g. Andy Warhol)"
required/>
</Property>
<Property
name='title'
label={getLangText('Title')}>
<input
type="text"
placeholder="(e.g. 32 Campbell's Soup Cans)"
required/>
</Property>
<Property
name='date_created'
label={getLangText('Year Created')}>
<input
type="number"
placeholder="(e.g. 1962)"
min={0}
required/>
</Property>
<PropertyCollapsible <PropertyCollapsible
checkboxLabel={getLangText('Specify editions')}> checkboxLabel={getLangText('Specify editions')}>
<span>{getLangText('Editions')}</span> <span>{getLangText('Editions')}</span>
@ -232,7 +147,7 @@ let RegisterPiece = React.createClass( {
min={0}/> min={0}/>
</PropertyCollapsible> </PropertyCollapsible>
{this.getLicenses()} {this.getLicenses()}
</Form> </RegisterPieceForm>
</Col> </Col>
</Row> </Row>
</div> </div>
@ -248,54 +163,4 @@ let RegisterPiece = React.createClass( {
}); });
let FileUploader = React.createClass({
propTypes: {
setIsUploadReady: React.PropTypes.func,
submitKey: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func,
onClick: React.PropTypes.func,
// editable is used to lock react fine uploader in case
// a user is actually not logged in already to prevent him from droping files
// before login in
editable: React.PropTypes.bool
},
render() {
return (
<ReactS3FineUploader
onClick={this.props.onClick}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'digitalwork'
}}
createBlobRoutine={{
url: apiUrls.blob_digitalworks
}}
submitKey={this.props.submitKey}
validation={{
itemLimit: 100000,
sizeLimit: '25000000000'
}}
setIsUploadReady={this.props.setIsUploadReady}
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
areAssetsDownloadable={false}
areAssetsEditable={this.props.editable}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/',
customHeaders: {
'X-CSRFToken': getCookie('csrftoken')
}
}}
deleteFile={{
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
customHeaders: {
'X-CSRFToken': getCookie('csrftoken')
}
}}/>
);
}
});
export default RegisterPiece; export default RegisterPiece;

View File

@ -210,6 +210,7 @@ const languages = {
'I agree to the Terms of Service': 'I agree to the Terms of Service', 'I agree to the Terms of Service': 'I agree to the Terms of Service',
'read': 'read', 'read': 'read',
'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'If your email address exists in our database, you will receive a password recovery link in a few minutes.', 'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'If your email address exists in our database, you will receive a password recovery link in a few minutes.',
'REGISTREE': 'REGISTREE',
}, },
'de': { 'de': {
'ID': 'ID', 'ID': 'ID',
@ -420,6 +421,7 @@ const languages = {
'I agree to the Terms of Service': 'I agree to the Terms of Service', 'I agree to the Terms of Service': 'I agree to the Terms of Service',
'read': 'read', 'read': 'read',
'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'If your email address exists in our database, you will receive a password recovery link in a few minutes.', 'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'If your email address exists in our database, you will receive a password recovery link in a few minutes.',
'REGISTREE': 'REGISTREE',
}, },
'fr': { 'fr': {
'ID': 'ID', 'ID': 'ID',
@ -630,6 +632,7 @@ const languages = {
'I agree to the Terms of Service': 'I agree to the Terms of Service', 'I agree to the Terms of Service': 'I agree to the Terms of Service',
'read': 'read', 'read': 'read',
'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'Si votre adresse électronique existe dans notre base de données, vous recevrez un lien de récupération de mot de passe dans quelques minutes.', 'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'Si votre adresse électronique existe dans notre base de données, vous recevrez un lien de récupération de mot de passe dans quelques minutes.',
'REGISTREE': 'REGISTREE',
} }
}; };

View File

@ -19,6 +19,7 @@ class PieceListStore {
* the number of items the resource actually - without pagination - provides. * the number of items the resource actually - without pagination - provides.
*/ */
this.pieceList = []; this.pieceList = [];
// -1 specifies that the store is currently loading
this.pieceListCount = -1; this.pieceListCount = -1;
this.page = 1; this.page = 1;
this.pageSize = 10; this.pageSize = 10;

View File

@ -13,6 +13,14 @@ class PieceStore {
onUpdatePiece(piece) { onUpdatePiece(piece) {
this.piece = piece; this.piece = piece;
} }
onUpdateProperty({key, value}) {
if(this.piece && key in this.piece) {
this.piece[key] = value;
} else {
throw new Error('There is no piece defined in PieceStore or the piece object does not have the property you\'re looking for.');
}
}
} }
export default alt.createStore(PieceStore, 'PieceStore'); export default alt.createStore(PieceStore, 'PieceStore');