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 {
constructor() {
this.generateActions(
'updatePiece'
'updatePiece',
'updateProperty'
);
}

View File

@ -14,6 +14,21 @@ class PieceListActions {
}
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) => {
PieceListFetcher
.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 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 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(){
if (this.props.content.requestAction){
return (
@ -66,39 +52,32 @@ let AccordionListItem = React.createClass({
});
},
handleEditionCreationSuccess(response) {
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
handleEditionCreationSuccess() {
PieceListActions.updatePropertyForPiece({pieceId: this.props.content.id, key: 'num_editions', value: 0});
this.toggleCreateEditionsDialog();
},
startPolling() {
// start polling until editions are defined
let pollingIntervalIndex = setInterval(() => {
EditionListActions.fetchEditionList(this.props.content.id)
.then((res) => {
clearInterval(this.state.pollingIntervalIndex);
PieceListActions.updatePropertyForPiece({
pieceId: this.props.content.id,
key: 'num_editions',
value: res.editions[0].num_editions
});
EditionListActions.toggleEditionList(this.props.content.id);
})
.catch(() => {
/* Ignore and keep going */
});
}, 5000);
this.setState({
pollingIntervalIndex
onPollingSuccess(pieceId, numEditions) {
PieceListActions.updatePropertyForPiece({
pieceId,
key: 'num_editions',
value: numEditions
});
EditionListActions.toggleEditionList(pieceId);
},
getCreateEditionsDialog() {
if(this.props.content.num_editions < 1 && this.state.showCreateEditionsDialog) {
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">
<CreateEditionsForm
pieceId={this.props.content.id}
handleSuccess={this.handleEditionCreationSuccess} />
</div>
);
}
},
render() {
@ -141,7 +120,8 @@ let AccordionListItem = React.createClass({
<AccordionListItemEditionWidget
className="pull-right"
piece={this.props.content}
toggleCreateEditionsDialog={this.toggleCreateEditionsDialog}/>
toggleCreateEditionsDialog={this.toggleCreateEditionsDialog}
onPollingSuccess={this.onPollingSuccess}/>
</div>
</div>
<span style={{'clear': 'both'}}></span>
@ -150,8 +130,9 @@ let AccordionListItem = React.createClass({
</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}
</div>

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 EditionListStore from '../../stores/edition_list_store';
import CreateEditionsButton from '../ascribe_buttons/create_editions_button';
import { getLangText } from '../../utils/lang_utils';
let AccordionListItemEditionWidget = React.createClass({
propTypes: {
className: React.PropTypes.string,
piece: React.PropTypes.object.isRequired,
toggleCreateEditionsDialog: React.PropTypes.func.isRequired
toggleCreateEditionsDialog: React.PropTypes.func.isRequired,
onPollingSuccess: React.PropTypes.func
},
getInitialState() {
@ -55,16 +58,9 @@ let AccordionListItemEditionWidget = React.createClass({
let isEditionListOpen = this.state.isEditionListOpenForPieceId[pieceId] ? this.state.isEditionListOpenForPieceId[pieceId].show : false;
if(isEditionListOpen) {
if(typeof this.state.editionList[pieceId] === 'undefined') {
return (
<span className="glyph-ascribe-spool-chunked spin"/>
);
} else {
return (
<span className="glyphicon glyphicon-menu-up" aria-hidden="true" style={{top: 2}}></span>
);
}
return (
<span className="glyphicon glyphicon-menu-up" aria-hidden="true" style={{top: 2}}></span>
);
} else {
return (
<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 numEditions = piece.num_editions;
if(numEditions === -1) {
if(numEditions <= 0) {
return (
<button
onClick={this.props.toggleCreateEditionsDialog}
className={classNames('btn', 'btn-default', 'btn-xs', 'ascribe-accordion-list-item-edition-widget', this.props.className)}>
+ Editions
</button>
<CreateEditionsButton
label={getLangText('Create editions')}
className="btn-xs pull-right"
piece={piece}
toggleCreateEditionsDialog={this.props.toggleCreateEditionsDialog}
onPollingSuccess={this.props.onPollingSuccess}/>
);
}
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) {
} else if(numEditions === 1) {
let editionMapping = piece && piece.first_edition ? piece.first_edition.edition_number + '/' + piece.num_editions : '';
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() {
let shouldDisplay = this.props.availableAcls.indexOf(this.props.action) > -1;
let aclProps = this.actionProperties();
@ -121,18 +135,6 @@ let AclButton = React.createClass({
{ aclProps.form }
</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 DeleteButton from '../ascribe_buttons/delete_button';
import CreateEditionButton from '../ascribe_buttons/create_editions_button';
let AclButtonList = React.createClass({
propTypes: {
className: React.PropTypes.string,
editions: React.PropTypes.object,
editions: React.PropTypes.oneOfType([
React.PropTypes.object,
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() {
@ -69,8 +75,7 @@ let AclButtonList = React.createClass({
handleSuccess={this.props.handleSuccess} />
<DeleteButton
editions={this.props.editions}/>
<CreateEditionButton
piece={this.props.editions}/>
{this.props.children}
</div>
);
}

View File

@ -1,42 +1,93 @@
'use strict';
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 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({
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 () {
if (this.props.piece.constructor === Array){
let piece = this.props.piece;
let availableAcls = getAvailableAcls(piece);
if (availableAcls.indexOf('editions') < -1 || piece.num_editions > 0){
return null;
}
let availableAcls = getAvailableAcls([this.props.piece]);
if (availableAcls.indexOf('editions') === -1){
return null;
if(piece.num_editions === 0 && typeof this.state.editionList[piece.id] === 'undefined') {
return (
<button
disabled
className={classNames('btn', 'btn-default', this.props.className)}>
{getLangText('Creating editions')} <span className="glyph-ascribe-spool-chunked spin"/>
</button>
);
} else {
return (
<button
className={classNames('btn', 'btn-default', this.props.className)}
onClick={this.props.toggleCreateEditionsDialog}>
{this.props.label}
</button>
);
}
return (
<button className="btn btn-default btn-sm">
CREATE EDITIONS
</button>
);
}
});

View File

@ -5,7 +5,6 @@ import Router from 'react-router';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import Button from 'react-bootstrap/lib/Button';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import UserActions from '../../actions/user_actions';
@ -22,7 +21,6 @@ import Property from './../ascribe_forms/property';
import EditionDetailProperty from './detail_property';
import InputTextAreaToggable from './../ascribe_forms/input_textarea_toggable';
import EditionHeader from './header';
import EditionFurtherDetails from './further_details';
//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 AppConstants from '../../constants/application_constants';
import { getCookie } from '../../utils/fetch_api_utils';
import { getLangText } from '../../utils/lang_utils';
let Link = Router.Link;
@ -76,7 +73,12 @@ let Edition = React.createClass({
content={this.props.edition}/>
</Col>
<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
currentUser={this.state.currentUser}
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 FurtherDetails from './further_details';
import UserActions from '../../actions/user_actions';
import UserStore from '../../stores/user_store';
import PieceActions from '../../actions/piece_actions';
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 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 { mergeOptions } from '../../utils/general_utils';
/**
* This is the component that implements display-specific functionality
@ -41,7 +37,12 @@ let Piece = React.createClass({
},
getInitialState() {
return UserStore.getState();
return mergeOptions(
UserStore.getState(),
{
showCreateEditionsDialog: false
}
);
},
componentDidMount() {
@ -57,8 +58,40 @@ let Piece = React.createClass({
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 (
<Row>
<Col md={6}>
@ -66,12 +99,31 @@ let Piece = React.createClass({
content={this.props.piece}/>
</Col>
<Col md={6} className="ascribe-edition-details">
<Header
content={this.props.piece}/>
<PieceSummary
currentUser={this.state.currentUser}
piece={this.props.piece}
handleSuccess={this.props.loadPiece}/>
<div className="ascribe-detail-header">
<EditionDetailProperty label="TITLE" value={<div className="ascribe-detail-title">{this.props.piece.title}</div>} />
<EditionDetailProperty label="BY" value={this.props.piece.artist_name} />
<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}
toggleCreateEditionsDialog={this.toggleCreateEditionsDialog}
onPollingSuccess={this.handlePollingSuccess}/>
</AclButtonList>
{this.getCreateEditionsDialog()}
<CollapsibleParagraph
title="Further Details"
show={this.props.piece.acl.indexOf('edit') > -1
@ -85,42 +137,10 @@ let Piece = React.createClass({
otherData={this.props.piece.other_data}
handleSuccess={this.props.loadPiece}/>
</CollapsibleParagraph>
</Col>
</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;

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)}
</button>}
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" />
</button>
</span>
}>
<FormPropertyHeader>
<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)}
</button>}
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" />
</button>
</span>
}>
<FormPropertyHeader>
<h3>{getLangText(this.props.headerMessage)}</h3>

View File

@ -68,9 +68,9 @@ let CoaVerifyForm = React.createClass({
{getLangText('Verify your Certificate of Authenticity')}
</button>}
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" />
</button>
</span>
}>
<Property
name='message'

View File

@ -12,12 +12,16 @@ let Link = Router.Link;
let LoginContainer = React.createClass({
propTypes: {
message: React.PropTypes.string
message: React.PropTypes.string,
redirectOnLoggedIn: React.PropTypes.bool,
redirectOnLoginSuccess: React.PropTypes.bool
},
getDefaultProps() {
return {
message: getLangText('Enter') + ' ascribe'
message: getLangText('Enter') + ' ascribe',
redirectOnLoggedIn: true,
redirectOnLoginSuccess: true
};
},
@ -25,6 +29,8 @@ let LoginContainer = React.createClass({
return (
<div className="ascribe-login-wrapper">
<LoginForm
redirectOnLoggedIn={this.props.redirectOnLoggedIn}
redirectOnLoginSuccess={this.props.redirectOnLoginSuccess}
message={this.props.message} />
<div className="ascribe-login-text">
{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')}
</button>}
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" />
</button>
</span>
}>
<Property
name='email'
@ -129,9 +129,9 @@ let PasswordResetForm = React.createClass({
{getLangText('Reset your password')}
</button>}
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" />
</button>
</span>
}>
<Property
name='password'

View File

@ -2,14 +2,10 @@
import React from 'react';
import DatePicker from 'react-datepicker/dist/react-datepicker';
import Router from 'react-router';
import Col from 'react-bootstrap/lib/Col';
import Row from 'react-bootstrap/lib/Row';
import AppConstants from '../constants/application_constants';
import LicenseActions from '../actions/license_actions';
import LicenseStore from '../stores/license_store';
@ -21,17 +17,14 @@ import UserStore from '../stores/user_store';
import GlobalNotificationModel from '../models/global_notification_model';
import GlobalNotificationActions from '../actions/global_notification_actions';
import Form from './ascribe_forms/form';
import Property from './ascribe_forms/property';
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 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 { getCookie } from '../utils/fetch_api_utils';
@ -46,8 +39,6 @@ let RegisterPiece = React.createClass( {
UserStore.getState(),
PieceListStore.getState(),
{
digitalWorkKey: null,
uploadStatus: false,
selectedLicense: 0,
isFineUploaderEditable: false
});
@ -97,32 +88,6 @@ let RegisterPiece = React.createClass( {
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){
//console.log(this.state.licenses[event.target.selectedIndex].url);
this.setState({selectedLicense: event.target.selectedIndex});
@ -170,59 +135,9 @@ let RegisterPiece = React.createClass( {
onFocus={this.changeSlide}>
<Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<Form
className="ascribe-form-bordered"
ref='form'
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>
<RegisterPieceForm
isFineUploaderEditable={this.state.isFineUploaderEditable}
handleSuccess={this.handleSuccess}>
<PropertyCollapsible
checkboxLabel={getLangText('Specify editions')}>
<span>{getLangText('Editions')}</span>
@ -232,7 +147,7 @@ let RegisterPiece = React.createClass( {
min={0}/>
</PropertyCollapsible>
{this.getLicenses()}
</Form>
</RegisterPieceForm>
</Col>
</Row>
</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;

View File

@ -210,6 +210,7 @@ const languages = {
'I agree to the Terms of Service': 'I agree to the Terms of Service',
'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.',
'REGISTREE': 'REGISTREE',
},
'de': {
'ID': 'ID',
@ -420,6 +421,7 @@ const languages = {
'I agree to the Terms of Service': 'I agree to the Terms of Service',
'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.',
'REGISTREE': 'REGISTREE',
},
'fr': {
'ID': 'ID',
@ -630,6 +632,7 @@ const languages = {
'I agree to the Terms of Service': 'I agree to the Terms of Service',
'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.',
'REGISTREE': 'REGISTREE',
}
};

View File

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

View File

@ -13,6 +13,14 @@ class PieceStore {
onUpdatePiece(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');