1
0
mirror of https://github.com/ascribe/onion.git synced 2024-06-30 21:52:08 +02: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 16:35:49 +02:00
commit 3878dc12da
41 changed files with 611 additions and 527 deletions

View File

@ -44,7 +44,7 @@ class EditionListActions {
}) })
.catch((err) => { .catch((err) => {
reject(err); reject(err);
console.log(err); //console.log(err);
}); });
}); });

View File

@ -9,7 +9,8 @@ class PieceListActions {
this.generateActions( this.generateActions(
'updatePieceList', 'updatePieceList',
'updatePieceListRequestActions', 'updatePieceListRequestActions',
'addFirstEditionToPiece' 'addFirstEditionToPiece',
'updatePropertyForPiece'
); );
} }

View File

@ -3,13 +3,18 @@
import React from 'react'; import React from 'react';
import Router from 'react-router'; import Router from 'react-router';
import PieceListActions from '../../actions/piece_list_actions';
import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; 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 PieceListActions from '../../actions/piece_list_actions';
import EditionListActions from '../../actions/edition_list_actions';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
@ -24,8 +29,15 @@ let AccordionListItem = React.createClass({
mixins: [Router.Navigation], mixins: [Router.Navigation],
getInitialState() {
return {
showCreateEditionsDialog: false,
creatingEditions: false
};
},
componentDidMount() { componentDidMount() {
if(this.props.content.num_editions !== 0) { if(this.props.content.num_editions > 0) {
PieceListActions.fetchFirstEditionForPiece(this.props.content.id); PieceListActions.fetchFirstEditionForPiece(this.props.content.id);
} }
}, },
@ -45,10 +57,57 @@ let AccordionListItem = React.createClass({
return null; return null;
}, },
toggleCreateEditionsDialog() {
this.setState({
showCreateEditionsDialog: !this.state.showCreateEditionsDialog
});
},
handleEditionCreationSuccess(response) {
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
PieceListActions.updatePropertyForPiece({pieceId: this.props.content.id, key: 'num_editions', value: 0});
this.setState({
creatingEditions: true
});
this.toggleCreateEditionsDialog();
// start polling until editions are defined
let pollingIntervalIndex = setInterval(() => {
EditionListActions.fetchEditionList(this.props.content.id)
.then((res) => {
clearInterval(this.state.pollingIntervalIndex);
this.setState({
creatingEditions: false
});
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
});
},
render() { render() {
let linkData; let linkData;
if(this.props.content.num_editions === 0) { if(this.props.content.num_editions < 1) {
linkData = { linkData = {
to: 'piece', to: 'piece',
params: { params: {
@ -81,9 +140,13 @@ let AccordionListItem = React.createClass({
</Link> </Link>
<h3>{getLangText('by %s', this.props.content.artist_name)}</h3> <h3>{getLangText('by %s', this.props.content.artist_name)}</h3>
<div> <div>
<span>{this.props.content.date_created.split('-')[0]}, </span> <span>{this.props.content.date_created.split('-')[0]}</span>
</div>
<div>
<AccordionListItemEditionWidget <AccordionListItemEditionWidget
piece={this.props.content}/> piece={this.props.content}
toggleCreateEditionsDialog={this.toggleCreateEditionsDialog}
creatingEditions={this.state.creatingEditions}/>
{/* <a href={this.props.content.license_type.url} target="_blank" className="pull-right"> {/* <a href={this.props.content.license_type.url} target="_blank" className="pull-right">
{getLangText('%s license', this.props.content.license_type.code)} {getLangText('%s license', this.props.content.license_type.code)}
</a> */} </a> */}
@ -95,6 +158,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.props.children is AccordionListItemTableEditions */} {/* this.props.children is AccordionListItemTableEditions */}
{this.props.children} {this.props.children}
</div> </div>

View File

@ -0,0 +1,51 @@
'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

@ -9,7 +9,9 @@ import { getLangText } from '../../utils/lang_utils';
let AccordionListItemEditionWidget = React.createClass({ let AccordionListItemEditionWidget = React.createClass({
propTypes: { propTypes: {
piece: React.PropTypes.object.isRequired piece: React.PropTypes.object.isRequired,
toggleCreateEditionsDialog: React.PropTypes.func.isRequired,
creatingEditions: React.PropTypes.bool.isRequired
}, },
getInitialState() { getInitialState() {
@ -73,15 +75,23 @@ 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 === 0) { if(numEditions === -1) {
return ( return (
<span <span
onClick={this.toggleTable} onClick={this.props.toggleCreateEditionsDialog}
className="ascribe-accordion-list-item-edition-widget"> className="ascribe-accordion-list-item-edition-widget">
Create editions + Editions
</span> </span>
); );
} else if(numEditions === 1) { }
else if(numEditions === 0) {
return (
<span>
Creating Editions <span className="glyph-ascribe-spool-chunked ascribe-color spin"/>
</span>
);
}
else if(numEditions === 1) {
let editionMapping = piece && piece.firstEdition ? piece.firstEdition.edition_number + '/' + piece.num_editions : ''; let editionMapping = piece && piece.firstEdition ? piece.firstEdition.edition_number + '/' + piece.num_editions : '';
return ( return (
@ -91,7 +101,8 @@ let AccordionListItemEditionWidget = React.createClass({
{editionMapping + ' ' + getLangText('Edition')} {this.getGlyphicon()} {editionMapping + ' ' + getLangText('Edition')} {this.getGlyphicon()}
</span> </span>
); );
} else { }
else {
return ( return (
<span <span
onClick={this.toggleTable} onClick={this.toggleTable}

View File

@ -13,25 +13,28 @@ import AppConstants from '../../constants/application_constants';
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 { getLangText } from '../../utils/lang_utils.js' import { getLangText } from '../../utils/lang_utils.js';
import apiUrls from '../../constants/api_urls';
let AclButton = React.createClass({ let AclButton = React.createClass({
propTypes: { propTypes: {
action: React.PropTypes.oneOf(AppConstants.aclList).isRequired, action: React.PropTypes.oneOf(AppConstants.aclList).isRequired,
availableAcls: React.PropTypes.array.isRequired, availableAcls: React.PropTypes.array.isRequired,
editions: React.PropTypes.array.isRequired, pieceOrEditions: React.PropTypes.object.isRequired,
currentUser: React.PropTypes.object, currentUser: React.PropTypes.object,
handleSuccess: React.PropTypes.func.isRequired, handleSuccess: React.PropTypes.func.isRequired,
className: React.PropTypes.string className: React.PropTypes.string
}, },
isPiece(){
return !(this.props.pieceOrEditions.constructor === Array);
},
actionProperties(){ actionProperties(){
if (this.props.action === 'consign'){ if (this.props.action === 'consign'){
return { return {
title: getLangText('Consign artwork'), title: getLangText('Consign artwork'),
tooltip: getLangText('Have someone else sell the artwork'), tooltip: getLangText('Have someone else sell the artwork'),
form: <ConsignForm currentUser={ this.props.currentUser } editions={ this.props.editions }/>, form: <ConsignForm currentUser={ this.props.currentUser } editions={ this.props.pieceOrEditions }/>,
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
} }
@ -39,14 +42,14 @@ let AclButton = React.createClass({
return { return {
title: getLangText('Unconsign artwork'), title: getLangText('Unconsign artwork'),
tooltip: getLangText('Have the owner manage his sales again'), tooltip: getLangText('Have the owner manage his sales again'),
form: <UnConsignForm currentUser={ this.props.currentUser } editions={ this.props.editions }/>, form: <UnConsignForm currentUser={ this.props.currentUser } editions={ this.props.pieceOrEditions }/>,
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
}else if (this.props.action === 'transfer') { }else if (this.props.action === 'transfer') {
return { return {
title: getLangText('Transfer artwork'), title: getLangText('Transfer artwork'),
tooltip: getLangText('Transfer the ownership of the artwork'), tooltip: getLangText('Transfer the ownership of the artwork'),
form: <TransferForm currentUser={ this.props.currentUser } editions={ this.props.editions }/>, form: <TransferForm currentUser={ this.props.currentUser } editions={ this.props.pieceOrEditions }/>,
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
} }
@ -54,7 +57,7 @@ let AclButton = React.createClass({
return { return {
title: getLangText('Loan artwork'), title: getLangText('Loan artwork'),
tooltip: getLangText('Loan your artwork for a limited period of time'), tooltip: getLangText('Loan your artwork for a limited period of time'),
form: <LoanForm currentUser={ this.props.currentUser } editions={ this.props.editions }/>, form: <LoanForm currentUser={ this.props.currentUser } editions={ this.props.pieceOrEditions }/>,
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
} }
@ -62,16 +65,46 @@ let AclButton = React.createClass({
return { return {
title: getLangText('Share artwork'), title: getLangText('Share artwork'),
tooltip: getLangText('Share the artwork'), tooltip: getLangText('Share the artwork'),
form: <ShareForm currentUser={ this.props.currentUser } editions={ this.props.editions }/>, form: (
<ShareForm
message={this.getShareMessage()}
id={this.getFormDataId()}
url={this.isPiece() ? apiUrls.ownership_shares_pieces : apiUrls.ownership_shares_editions }/>
),
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
} }
}, },
showNotification(response){ showNotification(response){
this.props.handleSuccess(); this.props.handleSuccess();
let notification = new GlobalNotificationModel(response.notification, 'success'); let notification = new GlobalNotificationModel(response.notification, 'success');
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
getTitlesString(){
if (this.isPiece()){
return '\"' + this.props.pieceOrEditions.title + '\"';
}
else {
return this.props.pieceOrEditions.map(function(edition) {
return '- \"' + edition.title + ', ' + getLangText('edition') + ' ' + edition.edition_number + '\"\n';
}).join('');
}
},
getFormDataId(){
if (this.isPiece()) {
return {piece_id: this.props.pieceOrEditions.id};
}
else {
return {bitcoin_id: this.props.pieceOrEditions.map(function(edition){
return edition.bitcoin_id;
}).join()};
}
},
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();
@ -88,6 +121,18 @@ 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,11 +7,12 @@ 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.array, editions: React.PropTypes.object,
availableAcls: React.PropTypes.array, availableAcls: React.PropTypes.array,
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func
}, },
@ -39,34 +40,37 @@ let AclButtonList = React.createClass({
<AclButton <AclButton
availableAcls={this.props.availableAcls} availableAcls={this.props.availableAcls}
action="transfer" action="transfer"
editions={this.props.editions} pieceOrEditions={this.props.editions}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.props.handleSuccess}/> handleSuccess={this.props.handleSuccess}/>
<AclButton <AclButton
availableAcls={this.props.availableAcls} availableAcls={this.props.availableAcls}
action="consign" action="consign"
editions={this.props.editions} pieceOrEditions={this.props.editions}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.props.handleSuccess} /> handleSuccess={this.props.handleSuccess} />
<AclButton <AclButton
availableAcls={this.props.availableAcls} availableAcls={this.props.availableAcls}
action="unconsign" action="unconsign"
editions={this.props.editions} pieceOrEditions={this.props.editions}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.props.handleSuccess} /> handleSuccess={this.props.handleSuccess} />
<AclButton <AclButton
availableAcls={this.props.availableAcls} availableAcls={this.props.availableAcls}
action="loan" action="loan"
editions={this.props.editions} pieceOrEditions={this.props.editions}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.props.handleSuccess} /> handleSuccess={this.props.handleSuccess} />
<AclButton <AclButton
availableAcls={this.props.availableAcls} availableAcls={this.props.availableAcls}
action="share" action="share"
editions={this.props.editions} pieceOrEditions={this.props.editions}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.props.handleSuccess} /> handleSuccess={this.props.handleSuccess} />
<DeleteButton editions={this.props.editions}/> <DeleteButton
editions={this.props.editions}/>
<CreateEditionButton
piece={this.props.editions}/>
</div> </div>
); );
} }

View File

@ -0,0 +1,44 @@
'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';
let CreateEditionsButton = React.createClass({
propTypes: {
piece: React.PropTypes.object.isRequired
},
mixins: [Router.Navigation],
render: function () {
if (this.props.piece.constructor === Array){
return null;
}
let availableAcls = getAvailableAcls([this.props.piece]);
if (availableAcls.indexOf('editions') === -1){
return null;
}
return (
<button className="btn btn-default btn-sm">
CREATE EDITIONS
</button>
);
}
});
export default CreateEditionsButton;

View File

@ -84,7 +84,8 @@ let Edition = React.createClass({
<CollapsibleParagraph <CollapsibleParagraph
title="Notes" title="Notes"
show={(this.state.currentUser.username && true || false) || show={(this.state.currentUser.username && true || false) ||
(this.props.edition.acl.indexOf('edit') > -1 || this.props.edition.public_note)}> (this.props.edition.acl.indexOf('edit') > -1 || this.props.edition.public_note)}
defaultExpanded={true}>
<EditionPersonalNote <EditionPersonalNote
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.props.loadEdition} handleSuccess={this.props.loadEdition}
@ -217,7 +218,6 @@ let EditionSummary = React.createClass({
<EditionDetailProperty label={getLangText('ID')} value={ this.props.edition.bitcoin_id } /> <EditionDetailProperty label={getLangText('ID')} value={ this.props.edition.bitcoin_id } />
<EditionDetailProperty label={getLangText('OWNER')} value={ this.props.edition.owner } /> <EditionDetailProperty label={getLangText('OWNER')} value={ this.props.edition.owner } />
{this.getStatus()} {this.getStatus()}
<br/>
{this.getActions()} {this.getActions()}
<hr/> <hr/>
</div> </div>
@ -275,11 +275,11 @@ let EditionPersonalNote = React.createClass({
label={getLangText('Personal note (private)')} label={getLangText('Personal note (private)')}
editable={true}> editable={true}>
<InputTextAreaToggable <InputTextAreaToggable
rows={3} rows={1}
editable={true} editable={true}
defaultValue={this.props.edition.note_from_user} defaultValue={this.props.edition.note_from_user}
placeholder={getLangText('Enter a personal note%s', '...')} placeholder={getLangText('Enter a personal note%s', '...')}
required/> required="required"/>
</Property> </Property>
<Property hidden={true} name='bitcoin_id'> <Property hidden={true} name='bitcoin_id'>
<input defaultValue={this.props.edition.bitcoin_id}/> <input defaultValue={this.props.edition.bitcoin_id}/>
@ -314,11 +314,11 @@ let EditionPublicEditionNote = React.createClass({
label={getLangText('Edition note (public)')} label={getLangText('Edition note (public)')}
editable={isEditable}> editable={isEditable}>
<InputTextAreaToggable <InputTextAreaToggable
rows={3} rows={1}
editable={isEditable} editable={isEditable}
defaultValue={this.props.edition.public_note} defaultValue={this.props.edition.public_note}
placeholder={getLangText('Enter a public note for this edition%', '...')} placeholder={getLangText('Enter a public note for this edition%s', '...')}
required/> required="required"/>
</Property> </Property>
<Property hidden={true} name='bitcoin_id'> <Property hidden={true} name='bitcoin_id'>
<input defaultValue={this.props.edition.bitcoin_id}/> <input defaultValue={this.props.edition.bitcoin_id}/>
@ -345,8 +345,7 @@ let CoaDetails = React.createClass({
if (this.props.edition.coa) { if (this.props.edition.coa) {
CoaActions.fetchOne(this.props.edition.coa); CoaActions.fetchOne(this.props.edition.coa);
} }
else{ else {
console.log('create coa');
CoaActions.create(this.props.edition); CoaActions.create(this.props.edition);
} }
}, },

View File

@ -24,7 +24,7 @@ import { getCookie } from '../../utils/fetch_api_utils';
let FurtherDetails = React.createClass({ let FurtherDetails = React.createClass({
propTypes: { propTypes: {
editable: React.PropTypes.bool, editable: React.PropTypes.bool,
pieceId: React.PropTypes.int, pieceId: React.PropTypes.number,
extraData: React.PropTypes.object, extraData: React.PropTypes.object,
otherData: React.PropTypes.object, otherData: React.PropTypes.object,
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func
@ -105,7 +105,7 @@ let FurtherDetails = React.createClass({
let FileUploader = React.createClass({ let FileUploader = React.createClass({
propTypes: { propTypes: {
pieceId: React.PropTypes.int, pieceId: React.PropTypes.number,
otherData: React.PropTypes.object, otherData: React.PropTypes.object,
setIsUploadReady: React.PropTypes.func, setIsUploadReady: React.PropTypes.func,
submitKey: React.PropTypes.func, submitKey: React.PropTypes.func,

View File

@ -7,14 +7,30 @@ import Col from 'react-bootstrap/lib/Col';
import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph'; import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph';
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 MediaContainer from './media_container'; import MediaContainer from './media_container';
import Header from './header'; import Header from './header';
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 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';
/** /**
* This is the component that implements display-specific functionality * This is the component that implements display-specific functionality
*/ */
@ -24,22 +40,22 @@ let Piece = React.createClass({
loadPiece: React.PropTypes.func loadPiece: React.PropTypes.func
}, },
//getInitialState() { getInitialState() {
// return UserStore.getState(); return UserStore.getState();
//}, },
//
//componentDidMount() { componentDidMount() {
// UserStore.listen(this.onChange); UserStore.listen(this.onChange);
// UserActions.fetchCurrentUser(); UserActions.fetchCurrentUser();
//}, },
//
//componentWillUnmount() { componentWillUnmount() {
// UserStore.unlisten(this.onChange); UserStore.unlisten(this.onChange);
//}, },
//
//onChange(state) { onChange(state) {
// this.setState(state); this.setState(state);
//}, },
render() { render() {
@ -52,11 +68,16 @@ let Piece = React.createClass({
<Col md={6} className="ascribe-edition-details"> <Col md={6} className="ascribe-edition-details">
<Header <Header
content={this.props.piece}/> content={this.props.piece}/>
<PieceSummary
currentUser={this.state.currentUser}
piece={this.props.piece}
handleSuccess={this.props.loadPiece}/>
<CollapsibleParagraph <CollapsibleParagraph
title="Further Details" title="Further Details"
show={this.props.piece.acl.indexOf('edit') > -1 show={this.props.piece.acl.indexOf('edit') > -1
|| Object.keys(this.props.piece.extra_data).length > 0 || Object.keys(this.props.piece.extra_data).length > 0
|| this.props.piece.other_data !== null}> || this.props.piece.other_data !== null}
defaultExpanded={true}>
<FurtherDetails <FurtherDetails
editable={this.props.piece.acl.indexOf('edit') > -1} editable={this.props.piece.acl.indexOf('edit') > -1}
pieceId={this.props.piece.id} pieceId={this.props.piece.id}
@ -71,4 +92,35 @@ let Piece = React.createClass({
} }
}); });
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

@ -7,6 +7,7 @@ import Button from 'react-bootstrap/lib/Button';
import requests from '../../utils/requests'; import requests from '../../utils/requests';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
import { mergeOptionsWithDuplicates } from '../../utils/general_utils';
import AlertDismissable from './alert'; import AlertDismissable from './alert';
@ -35,6 +36,7 @@ let Form = React.createClass({
this.refs[ref].reset(); this.refs[ref].reset();
} }
} }
this.setState(this.getInitialState());
}, },
submit(event){ submit(event){
if (event) { if (event) {
@ -53,13 +55,13 @@ let Form = React.createClass({
}, },
getFormData(){ getFormData(){
if ('getFormData' in this.props){
return this.props.getFormData();
}
let data = {}; let data = {};
for (let ref in this.refs){ for (let ref in this.refs){
data[this.refs[ref].props.name] = this.refs[ref].state.value; data[this.refs[ref].props.name] = this.refs[ref].state.value;
} }
if ('getFormData' in this.props){
data = mergeOptionsWithDuplicates(data, this.props.getFormData());
}
return data; return data;
}, },

View File

@ -1,64 +0,0 @@
'use strict';
import React from 'react';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputText from './input_text';
import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close';
import SignupModal from '../ascribe_modal/modal_signup';
import PasswordResetRequestModal from '../ascribe_modal/modal_password_request_reset';
import { getLangText } from '../../utils/lang_utils.js';
let LoginForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.users_login;
},
getFormData() {
return {
email: this.refs.email.state.value,
password: this.refs.password.state.value
};
},
renderForm() {
return (
<form id="login_modal_content" role="form" onSubmit={this.submit}>
<input className="invisible" type="email" name="fake_consignee"/>
<input className="invisible" type="password" name="fake_password"/>
<InputText
ref="email"
placeHolder={getLangText('Email')}
required="required"
type="email"
submitted={this.state.submitted}/>
<InputText
ref="password"
placeHolder={getLangText('Password')}
required="required"
type="password"
submitted={this.state.submitted}/>
<div>
{getLangText('Forgot your password')}&#63;
<PasswordResetRequestModal
button={<a className="button" href="#"> {getLangText('Reset password')}</a>}/>
</div>
<div>
{getLangText('Not a member yet')}&#63;
<SignupModal
button={<a className="button" href="#"> {getLangText('Sign up')}</a>}/>
</div>
<ButtonSubmitOrClose
text={getLangText('LOGIN')}
onClose={this.props.onRequestHide}
submitted={this.state.submitted} />
</form>
);
}
});
export default LoginForm;

View File

@ -1,43 +0,0 @@
'use strict';
import React from 'react';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputTextAreaToggable from './input_textarea_toggable';
let EditionNoteForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.note_edition;
},
getFormData() {
return {
bitcoin_id: this.getBitcoinIds().join(),
note: this.refs.personalNote.state.value
};
},
renderForm() {
return (
<form id="personal_note_content" role="form" key="personal_note_content">
<InputTextAreaToggable
ref="personalNote"
className="form-control"
defaultValue={this.props.editions[0].public_note}
rows={3}
editable={this.props.editable}
required=""
onSubmit={this.submit}
/>
</form>
);
}
});
export default EditionNoteForm;

View File

@ -1,43 +0,0 @@
'use strict';
import React from 'react';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputTextAreaToggable from './input_textarea_toggable';
let PersonalNoteForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.note_notes;
},
getFormData() {
return {
bitcoin_id: this.getBitcoinIds().join(),
note: this.refs.personalNote.state.value
};
},
renderForm() {
return (
<form id="personal_note_content" role="form" key="personal_note_content">
<InputTextAreaToggable
ref="personalNote"
className="form-control"
defaultValue={this.props.editions[0].note_from_user}
rows={3}
editable={this.props.editable}
required=""
onSubmit={this.submit}
/>
</form>
);
}
});
export default PersonalNoteForm;

View File

@ -1,51 +0,0 @@
'use strict';
import React from 'react';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputText from './input_text';
import ButtonSubmit from '../ascribe_buttons/button_submit';
import { getLangText } from '../../utils/lang_utils.js'
let PasswordResetForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.users_password_reset;
},
getFormData() {
return {
email: this.props.email,
token: this.props.token,
password: this.refs.password.state.value,
password_confirm: this.refs.password_confirm.state.value
};
},
renderForm() {
return (
<form id="reset_password_modal_content" role="form" onSubmit={this.submit}>
<div>Reset the password for {this.props.email}:</div>
<InputText
ref="password"
placeHolder={getLangText('Choose a password')}
required="required"
type="password"
submitted={this.state.submitted}/>
<InputText
ref="password_confirm"
placeHolder={getLangText('Confirm password')}
required="required"
type="password"
submitted={this.state.submitted}/>
<ButtonSubmit
text={getLangText('RESET PASSWORD')}
submitted={this.state.submitted} />
</form>
);
}
});
export default PasswordResetForm;

View File

@ -1,42 +0,0 @@
'use strict';
import React from 'react';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputText from './input_text';
import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close';
import { getLangText } from '../../utils/lang_utils.js'
let PasswordResetRequestForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.users_password_reset_request;
},
getFormData() {
return {
email: this.refs.email.state.value
};
},
renderForm() {
return (
<form id="request_reset_password_modal_content" role="form" onSubmit={this.submit}>
<InputText
ref="email"
placeHolder={getLangText('Email')}
required="required"
type="email"
submitted={this.state.submitted}/>
<ButtonSubmitOrClose
text={getLangText('RESET PASSWORD')}
onClose={this.props.onRequestHide}
submitted={this.state.submitted} />
</form>
);
}
});
export default PasswordResetRequestForm;

View File

@ -13,7 +13,7 @@ import InputTextAreaToggable from './input_textarea_toggable';
let PieceExtraDataForm = React.createClass({ let PieceExtraDataForm = React.createClass({
propTypes: { propTypes: {
pieceId: React.PropTypes.int, pieceId: React.PropTypes.number,
extraData: React.PropTypes.object, extraData: React.PropTypes.object,
handleSuccess: React.PropTypes.func, handleSuccess: React.PropTypes.func,
name: React.PropTypes.string, name: React.PropTypes.string,
@ -49,7 +49,7 @@ let PieceExtraDataForm = React.createClass({
editable={this.props.editable} editable={this.props.editable}
defaultValue={defaultValue} defaultValue={defaultValue}
placeholder={getLangText('Fill in%s', ' ') + this.props.title} placeholder={getLangText('Fill in%s', ' ') + this.props.title}
required/> required="required"/>
</Property> </Property>
<hr /> <hr />
</Form> </Form>

View File

@ -4,7 +4,10 @@ import React from 'react';
let FormPropertyHeader = React.createClass({ let FormPropertyHeader = React.createClass({
propTypes: { propTypes: {
children: React.PropTypes.arrayOf(React.PropTypes.element) children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
])
}, },
render() { render() {

View File

@ -24,8 +24,9 @@ let EditionRemoveFromCollectionForm = React.createClass({
<p>{getLangText('Are you sure you would like to remove these editions from your collection')}&#63;</p> <p>{getLangText('Are you sure you would like to remove these editions from your collection')}&#63;</p>
<p>{getLangText('This is an irrevocable action%s', '.')}</p> <p>{getLangText('This is an irrevocable action%s', '.')}</p>
<div className="modal-footer"> <div className="modal-footer">
<button type="submit" className="btn btn-ascribe-inv" onClick={this.submit}>{getLangText('YES, REMOVE')}</button> <button type="submit" className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" onClick={this.submit}>{getLangText('YES, REMOVE')}</button>
<button className="btn btn-ascribe" onClick={this.props.onRequestHide}>{getLangText('CLOSE')}</button> <button className="btn btn-default btn-sm ascribe-margin-1px" style={{marginLeft: '0'}}
onClick={this.props.onRequestHide}>{getLangText('CLOSE')}</button>
</div> </div>
</div> </div>
); );

View File

@ -2,59 +2,76 @@
import React from 'react'; import React from 'react';
import ApiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputText from './input_text'; import Form from './form';
import InputTextArea from './input_textarea'; import Property from './property';
import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close'; import InputTextAreaToggable from './input_textarea_toggable';
import { getLangText } from '../../utils/lang_utils.js' import Button from 'react-bootstrap/lib/Button';
import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils.js';
let ShareForm = React.createClass({ let ShareForm = React.createClass({
mixins: [FormMixin], propTypes: {
url: React.PropTypes.string,
url() { id: React.PropTypes.string,
return ApiUrls.ownership_shares; message: React.PropTypes.string,
editions: React.PropTypes.array,
currentUser: React.PropTypes.object,
onRequestHide: React.PropTypes.func,
handleSuccess: React.PropTypes.func
}, },
getFormData() { getFormData(){
return { return this.props.id;
bitcoin_id: this.getBitcoinIds().join(),
share_emails: this.refs.share_emails.state.value,
share_message: this.refs.share_message.state.value
};
}, },
renderForm() { render() {
let title = this.getTitlesString().join('');
let username = this.props.currentUser.username;
let message =
`${getLangText('Hi')},
${getLangText('I am sharing')} :
${title}${getLangText('with you')}.
${getLangText('Truly yours')},
${username}`;
return ( return (
<form id="share_modal_content" role="form" key="share_modal_content" onSubmit={this.submit}> <Form
<InputText ref='form'
ref="share_emails" url={this.props.url}
placeHolder={getLangText('Comma separated emails')} getFormData={this.getFormData}
required="required" handleSuccess={this.props.handleSuccess}
type="text" buttons={
submitted={this.state.submitted}/> <div className="modal-footer">
<InputTextArea <p className="pull-right">
ref="share_message" <Button
defaultValue={message} className="btn btn-default btn-sm ascribe-margin-1px"
required="" type="submit">SHARE</Button>
/> <Button
<ButtonSubmitOrClose className="btn btn-danger btn-delete btn-sm ascribe-margin-1px"
text={getLangText('SHARE')} style={{marginLeft: '0'}}
onClose={this.props.onRequestHide} onClick={this.props.onRequestHide}>CLOSE</Button>
submitted={this.state.submitted} /> </p>
</form> </div>}
spinner={
<div className="modal-footer">
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
</div>}>
<Property
name='share_emails'
label={getLangText('Emails')}>
<input
type="text"
placeholder={getLangText('Comma separated emails')}
required/>
</Property>
<Property
name='share_message'
label='Personal Message'
editable={true}>
<InputTextAreaToggable
rows={1}
editable={true}
defaultValue={this.props.message}
placeholder={getLangText('Enter a message...')}
required="required"/>
</Property>
</Form>
); );
} }
}); });

View File

@ -2,48 +2,44 @@
import React from 'react'; import React from 'react';
import AlertMixin from '../../mixins/alert_mixin'; import { getLangText } from '../../utils/lang_utils';
let InputCheckbox = React.createClass({ let InputCheckbox = React.createClass({
propTypes: { propTypes: {
submitted: React.PropTypes.bool.isRequired,
required: React.PropTypes.string.isRequired, required: React.PropTypes.string.isRequired,
label: React.PropTypes.string.isRequired label: React.PropTypes.string.isRequired
}, },
mixins: [AlertMixin],
getInitialState() { getInitialState() {
return { return {
value: null, show: false
alerts: null // needed in AlertMixin
}; };
}, },
handleChange(event) { handleFocus() {
this.setState({value: event.target.value}); this.refs.checkbox.getDOMNode().checked = !this.refs.checkbox.getDOMNode().checked;
this.setState({
show: this.refs.checkbox.getDOMNode().checked,
value: this.refs.checkbox.getDOMNode().checked
});
}, },
render() { render() {
let alerts = (this.props.submitted) ? null : this.state.alerts;
return ( return (
<div className="form-group"> <span
{alerts} onClick={this.handleFocus}
<div className="input-checkbox-ascribe"> onFocus={this.handleFocus}>
<div className="checkbox"> <input type="checkbox" ref="checkbox"/>
<label> <span className="checkbox">
<input <span>
type="checkbox" {' ' + getLangText('I agree to the Terms of Service') + ' '}
required={this.props.required} (<a href="/terms" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
onChange={this.handleChange} {getLangText('read')}
/> </a>)
{this.props.label} </span>
</label> </span>
</div> </span>
</div>
</div>
); );
} }
}); });

View File

@ -18,13 +18,22 @@ let Property = React.createClass({
]), ]),
footer: React.PropTypes.element, footer: React.PropTypes.element,
handleChange: React.PropTypes.func, handleChange: React.PropTypes.func,
ignoreFocus: React.PropTypes.bool ignoreFocus: React.PropTypes.bool,
className: React.PropTypes.string,
onClick: React.PropTypes.func,
onChange: React.PropTypes.func,
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
]),
style: React.PropTypes.object
}, },
getDefaultProps() { getDefaultProps() {
return { return {
editable: true, editable: true,
hidden: false hidden: false,
className: ''
}; };
}, },
@ -36,12 +45,14 @@ let Property = React.createClass({
errors: null errors: null
}; };
}, },
componentWillReceiveProps(){ componentWillReceiveProps(){
this.setState({ this.setState({
initialValue: this.refs.input.getDOMNode().defaultValue, initialValue: this.refs.input.getDOMNode().defaultValue,
value: this.refs.input.getDOMNode().value value: this.refs.input.getDOMNode().value
}); });
}, },
reset(){ reset(){
// maybe do reset by reload instead of frontend state? // maybe do reset by reload instead of frontend state?
this.setState({value: this.state.initialValue}); this.setState({value: this.state.initialValue});
@ -62,26 +73,39 @@ let Property = React.createClass({
} }
this.setState({value: event.target.value}); this.setState({value: event.target.value});
}, },
handleFocus() { handleFocus() {
// if ignoreFocus (bool) is defined, then just ignore focusing on
// the property and input
if(this.props.ignoreFocus) { if(this.props.ignoreFocus) {
return; return;
} }
// if onClick is defined from the outside,
// just call it
if(this.props.onClick) {
this.props.onClick();
}
this.refs.input.getDOMNode().focus(); this.refs.input.getDOMNode().focus();
this.setState({ this.setState({
isFocused: true isFocused: true
}); });
}, },
handleBlur() { handleBlur() {
this.setState({ this.setState({
isFocused: false isFocused: false
}); });
}, },
handleSuccess(){ handleSuccess(){
this.setState({ this.setState({
isFocused: false, isFocused: false,
errors: null errors: null
}); });
}, },
setErrors(errors){ setErrors(errors){
this.setState({ this.setState({
errors: errors.map((error) => { errors: errors.map((error) => {
@ -89,9 +113,11 @@ let Property = React.createClass({
}) })
}); });
}, },
clearErrors(){ clearErrors(){
this.setState({errors: null}); this.setState({errors: null});
}, },
getClassName() { getClassName() {
if(this.props.hidden){ if(this.props.hidden){
return 'is-hidden'; return 'is-hidden';
@ -108,6 +134,7 @@ let Property = React.createClass({
return ''; return '';
} }
}, },
renderChildren() { renderChildren() {
return ReactAddons.Children.map(this.props.children, (child) => { return ReactAddons.Children.map(this.props.children, (child) => {
return ReactAddons.addons.cloneWithProps(child, { return ReactAddons.addons.cloneWithProps(child, {
@ -120,6 +147,7 @@ let Property = React.createClass({
}); });
}); });
}, },
render() { render() {
let tooltip = <span/>; let tooltip = <span/>;
if (this.props.tooltip){ if (this.props.tooltip){
@ -139,12 +167,13 @@ let Property = React.createClass({
<div <div
className={'ascribe-settings-wrapper ' + this.getClassName()} className={'ascribe-settings-wrapper ' + this.getClassName()}
onClick={this.handleFocus} onClick={this.handleFocus}
onfocus={this.handleFocus}> onFocus={this.handleFocus}
style={this.props.style}>
<OverlayTrigger <OverlayTrigger
delay={500} delay={500}
placement="top" placement="top"
overlay={tooltip}> overlay={tooltip}>
<div className="ascribe-settings-property"> <div className={'ascribe-settings-property ' + this.props.className}>
{this.state.errors} {this.state.errors}
<span>{ this.props.label}</span> <span>{ this.props.label}</span>
{this.renderChildren()} {this.renderChildren()}

View File

@ -1,33 +0,0 @@
'use strict';
import React from 'react';
import ModalWrapper from './modal_wrapper';
import LoginForm from '../ascribe_forms/form_login';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getLangText } from '../../utils/lang_utils.js'
let LoginModal = React.createClass({
handleLoginSuccess(){
this.props.handleSuccess();
let notificationText = getLangText('Login successful');
let notification = new GlobalNotificationModel(notificationText, 'success');
GlobalNotificationActions.appendGlobalNotification(notification);
},
render() {
return (
<ModalWrapper
button={this.props.button}
title={getLangText('Log in to ascribe')}
handleSuccess={this.handleLoginSuccess}
tooltip={getLangText('Log in to ascribe')}>
<LoginForm />
</ModalWrapper>
);
}
});
export default LoginModal;

View File

@ -1,32 +0,0 @@
'use strict';
import React from 'react';
import ModalWrapper from './modal_wrapper';
import SignupForm from '../ascribe_forms/form_signup';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getLangText } from '../../utils/lang_utils.js'
let SignupModal = React.createClass({
handleSignupSuccess(response){
let notificationText = getLangText('We sent an email to your address') + ' ' + response.user.email + ', ' + getLangText('please confirm') + '.';
let notification = new GlobalNotificationModel(notificationText, 'success', 50000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
render() {
return (
<ModalWrapper
button={this.props.button}
title={getLangText('Create an account')}
handleSuccess={this.handleSignupSuccess}
tooltip={getLangText('Sign up to ascribe')}>
<SignupForm />
</ModalWrapper>
);
}
});
export default SignupModal;

View File

@ -24,11 +24,17 @@ var ReactS3FineUploader = React.createClass({
keyRoutine: React.PropTypes.shape({ keyRoutine: React.PropTypes.shape({
url: React.PropTypes.string, url: React.PropTypes.string,
fileClass: React.PropTypes.string, fileClass: React.PropTypes.string,
pieceId: React.PropTypes.string pieceId: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number
])
}), }),
createBlobRoutine: React.PropTypes.shape({ createBlobRoutine: React.PropTypes.shape({
url: React.PropTypes.string, url: React.PropTypes.string,
pieceId: React.PropTypes.string pieceId: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number
])
}), }),
submitKey: React.PropTypes.func, submitKey: React.PropTypes.func,
autoUpload: React.PropTypes.bool, autoUpload: React.PropTypes.bool,
@ -70,7 +76,10 @@ var ReactS3FineUploader = React.createClass({
customHeaders: React.PropTypes.object customHeaders: React.PropTypes.object
}).isRequired, }).isRequired,
session: React.PropTypes.shape({ session: React.PropTypes.shape({
endpoint: React.PropTypes.bool customHeaders: React.PropTypes.object,
endpoint: React.PropTypes.string,
params: React.PropTypes.object,
refreshOnRequests: React.PropTypes.bool
}), }),
validation: React.PropTypes.shape({ validation: React.PropTypes.shape({
itemLimit: React.PropTypes.number, itemLimit: React.PropTypes.number,
@ -276,7 +285,7 @@ var ReactS3FineUploader = React.createClass({
body: JSON.stringify({ body: JSON.stringify({
'filename': file.name, 'filename': file.name,
'key': file.key, 'key': file.key,
'bitcoin_id': this.props.createBlobRoutine.pieceId 'piece_id': this.props.createBlobRoutine.pieceId
}) })
}) })
.then((res) => { .then((res) => {

View File

@ -66,12 +66,16 @@ let Header = React.createClass({
getPoweredBy(){ getPoweredBy(){
if (this.state.whitelabel.logo) { if (this.state.whitelabel.logo) {
return ( return (
<div className="row no-margin ascribe-subheader"> <div className="row ascribe-subheader">
<a className="pull-right" href="https://www.ascribe.io/" target="_blank"> <div className="col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2">
<span id="powered">{getLangText('powered by')} </span> <div className="row">
<span>ascribe </span> <a className="pull-right" href="https://www.ascribe.io/" target="_blank">
<span className="glyph-ascribe-spool-chunked ascribe-color"></span> <span id="powered">{getLangText('powered by')} </span>
</a> <span>ascribe </span>
<span className="glyph-ascribe-spool-chunked ascribe-color"></span>
</a>
</div>
</div>
</div>); </div>);
} }
return null; return null;

View File

@ -105,13 +105,10 @@ let PasswordResetForm = React.createClass({
mixins: [Router.Navigation], mixins: [Router.Navigation],
getFormData(){ getFormData(){
let data = {}; return {
for (let ref in this.refs.form.refs){ email: this.props.email,
data[this.refs.form.refs[ref].props.name] = this.refs.form.refs[ref].state.value; token: this.props.token
} };
data.email = this.props.email;
data.token = this.props.token;
return data;
}, },
handleSuccess() { handleSuccess() {
this.transitionTo('pieces'); this.transitionTo('pieces');

View File

@ -94,16 +94,13 @@ let RegisterPiece = React.createClass( {
this.state.orderBy, this.state.orderBy,
this.state.orderAsc); this.state.orderAsc);
this.transitionTo('piece', {editionId: response.piece.id}); this.transitionTo('piece', {pieceId: response.piece.id});
}, },
getFormData(){ getFormData(){
let data = {}; return {
for (let ref in this.refs.form.refs){ digital_work_key: this.state.digitalWorkKey
data[this.refs.form.refs[ref].props.name] = this.refs.form.refs[ref].state.value; };
}
data.digital_work_key = this.state.digitalWorkKey;
return data;
}, },
submitKey(key){ submitKey(key){
@ -138,8 +135,7 @@ let RegisterPiece = React.createClass( {
label={getLangText('Copyright license%s', '...')} label={getLangText('Copyright license%s', '...')}
onChange={this.onLicenseChange} onChange={this.onLicenseChange}
footer={ footer={
<a className="pull-right" href={this.state.licenses[this.state.selectedLicense].url} target="_blank"> <a className="pull-right" href={this.state.licenses[this.state.selectedLicense].url} target="_blank">{getLangText('Learn more')}
{getLangText('Learn more')}
</a>}> </a>}>
<select name="license"> <select name="license">
{this.state.licenses.map((license, i) => { {this.state.licenses.map((license, i) => {

View File

@ -13,8 +13,10 @@ import GlobalNotificationActions from '../actions/global_notification_actions';
import Form from './ascribe_forms/form'; import Form from './ascribe_forms/form';
import Property from './ascribe_forms/property'; import Property from './ascribe_forms/property';
import FormPropertyHeader from './ascribe_forms/form_property_header';
import InputCheckbox from './ascribe_forms/input_checkbox'; import InputCheckbox from './ascribe_forms/input_checkbox';
import apiUrls from '../constants/api_urls'; import apiUrls from '../constants/api_urls';
@ -66,9 +68,6 @@ let SignupContainer = React.createClass({
return ( return (
<div className="ascribe-login-wrapper"> <div className="ascribe-login-wrapper">
<br/> <br/>
<div className="ascribe-login-text ascribe-login-header">
{getLangText('Welcome to')} ascribe...
</div>
<SignupForm handleSuccess={this.handleSuccess}/> <SignupForm handleSuccess={this.handleSuccess}/>
</div> </div>
); );
@ -77,6 +76,10 @@ let SignupContainer = React.createClass({
let SignupForm = React.createClass({ let SignupForm = React.createClass({
propTypes: {
handleSuccess: React.PropTypes.func
},
mixins: [Router.Navigation], mixins: [Router.Navigation],
handleSuccess(response){ handleSuccess(response){
@ -88,15 +91,20 @@ let SignupForm = React.createClass({
', ' + getLangText('please confirm') + '.'); ', ' + getLangText('please confirm') + '.');
}, },
getFormData(){
return {terms: this.refs.form.refs.terms.refs.input.state.value};
},
render() { render() {
let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' + let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' +
getLangText('This password is securing your digital property like a bank account') + '.\n ' + getLangText('This password is securing your digital property like a bank account') + '.\n ' +
getLangText('Store it in a safe place') + '!'; getLangText('Store it in a safe place') + '!';
return ( return (
<Form <Form
className="ascribe-form-bordered"
ref='form' ref='form'
url={apiUrls.users_signup} url={apiUrls.users_signup}
handleSuccess={this.handleSuccess} handleSuccess={this.handleSuccess}
getFormData={this.getFormData}
buttons={ buttons={
<button type="submit" className="btn ascribe-btn ascribe-btn-login"> <button type="submit" className="btn ascribe-btn ascribe-btn-login">
{getLangText('Sign up to ascribe')} {getLangText('Sign up to ascribe')}
@ -106,6 +114,9 @@ let SignupForm = React.createClass({
<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> </button>
}> }>
<FormPropertyHeader>
<h3>{getLangText('Welcome to ascribe')}</h3>
</FormPropertyHeader>
<Property <Property
name='email' name='email'
label={getLangText('Email')}> label={getLangText('Email')}>
@ -142,16 +153,12 @@ let SignupForm = React.createClass({
type="text" type="text"
placeholder={getLangText('Enter a promocode here (Optional)')}/> placeholder={getLangText('Enter a promocode here (Optional)')}/>
</Property> </Property>
<hr /> <Property
<InputCheckbox name="terms"
name='terms' className="ascribe-settings-property-collapsible-toggle"
required="required" style={{paddingBottom: 0}}>
label={ <InputCheckbox/>
<div> </Property>
<a href="/terms" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
{getLangText('I agree to the')}&nbsp;{getLangText('Terms of Service')}
</a>
</div>}/>
</Form> </Form>
); );
} }

View File

@ -12,7 +12,8 @@ let apiUrls = {
'coa_verify': AppConstants.apiEndpoint + 'coa/verify_coa/', 'coa_verify': AppConstants.apiEndpoint + 'coa/verify_coa/',
'edition': AppConstants.apiEndpoint + 'editions/${bitcoin_id}/', 'edition': AppConstants.apiEndpoint + 'editions/${bitcoin_id}/',
'edition_delete': AppConstants.apiEndpoint + 'editions/${edition_id}/', 'edition_delete': AppConstants.apiEndpoint + 'editions/${edition_id}/',
'edition_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/${edition_id}/', 'edition_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/editions/${edition_id}/',
'editions': AppConstants.apiEndpoint + 'editions/', // this should be moved to the one below
'editions_list': AppConstants.apiEndpoint + 'pieces/${piece_id}/editions/', 'editions_list': AppConstants.apiEndpoint + 'pieces/${piece_id}/editions/',
'licenses': AppConstants.apiEndpoint + 'ownership/licenses/', 'licenses': AppConstants.apiEndpoint + 'ownership/licenses/',
'note_notes': AppConstants.apiEndpoint + 'note/notes/', 'note_notes': AppConstants.apiEndpoint + 'note/notes/',
@ -24,7 +25,8 @@ let apiUrls = {
'ownership_loans_confirm': AppConstants.apiEndpoint + 'ownership/loans/confirm/', 'ownership_loans_confirm': AppConstants.apiEndpoint + 'ownership/loans/confirm/',
'ownership_loans_deny': AppConstants.apiEndpoint + 'ownership/loans/deny/', 'ownership_loans_deny': AppConstants.apiEndpoint + 'ownership/loans/deny/',
'ownership_loans_contract': AppConstants.apiEndpoint + 'ownership/loans/contract/', 'ownership_loans_contract': AppConstants.apiEndpoint + 'ownership/loans/contract/',
'ownership_shares': AppConstants.apiEndpoint + 'ownership/shares/', 'ownership_shares_editions': AppConstants.apiEndpoint + 'ownership/shares/editions/',
'ownership_shares_pieces': AppConstants.apiEndpoint + 'ownership/shares/pieces/',
'ownership_transfers': AppConstants.apiEndpoint + 'ownership/transfers/', 'ownership_transfers': AppConstants.apiEndpoint + 'ownership/transfers/',
'ownership_transfers_withdraw': AppConstants.apiEndpoint + 'ownership/transfers/withdraw/', 'ownership_transfers_withdraw': AppConstants.apiEndpoint + 'ownership/transfers/withdraw/',
'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/', 'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/',

View File

@ -206,6 +206,9 @@ const languages = {
'Title': 'Title', 'Title': 'Title',
'Specify editions': 'Specify editions', 'Specify editions': 'Specify editions',
'Editions': 'Editions', 'Editions': 'Editions',
'Create editions': 'Create editions',
'I agree to the Terms of Service': 'I agree to the Terms of Service',
'read': 'read',
}, },
'de': { 'de': {
'ID': 'ID', 'ID': 'ID',
@ -412,6 +415,9 @@ const languages = {
'Title': 'Titel', 'Title': 'Titel',
'Specify editions': 'Specify editions', 'Specify editions': 'Specify editions',
'Editions': 'Editions', 'Editions': 'Editions',
'Create editions': 'Create editions',
'I agree to the Terms of Service': 'I agree to the Terms of Service',
'read': 'read',
}, },
'fr': { 'fr': {
'ID': 'ID', 'ID': 'ID',
@ -618,6 +624,9 @@ const languages = {
'Title': 'Title', 'Title': 'Title',
'Specify editions': 'Specify editions', 'Specify editions': 'Specify editions',
'Editions': 'Editions', 'Editions': 'Editions',
'Create editions': 'Create editions',
'I agree to the Terms of Service': 'I agree to the Terms of Service',
'read': 'read',
} }
}; };

View File

@ -11,7 +11,6 @@ let CoaFetcher = {
return requests.get('coa', {'id': id}); return requests.get('coa', {'id': id});
}, },
create(bitcoinId) { create(bitcoinId) {
console.log(bitcoinId);
return requests.post('coa_create', {body: {'bitcoin_id': bitcoinId}}); return requests.post('coa_create', {body: {'bitcoin_id': bitcoinId}});
} }
}; };

View File

@ -84,6 +84,19 @@ class PieceListStore {
throw new Error('Could not find a matching piece in piece list since its either not there or piecelist contains duplicates.'); throw new Error('Could not find a matching piece in piece list since its either not there or piecelist contains duplicates.');
} }
} }
onUpdatePropertyForPiece({pieceId, key, value}) {
let filteredPieceList = this.pieceList.filter((piece) => piece.id === pieceId);
if(filteredPieceList.length === 1) {
let piece = filteredPieceList[0];
piece[key] = value;
} else {
throw new Error('Could not find a matching piece in piece list since its either not there or piecelist contains duplicates.');
}
}
} }
export default alt.createStore(PieceListStore, 'PieceListStore'); export default alt.createStore(PieceListStore, 'PieceListStore');

View File

@ -112,6 +112,24 @@ export function mergeOptions(...l) {
return newObj; return newObj;
} }
/**
* Takes a list of object and merges their keys to one object.
* Uses mergeOptions for two objects.
* @param {[type]} l [description]
* @return {[type]} [description]
*/
export function mergeOptionsWithDuplicates(...l) {
// If the objects submitted in the list have duplicates,in their key names,
// abort the merge and tell the function's user to check his objects.
let newObj = {};
for(let i = 1; i < l.length; i++) {
newObj = _mergeOptions(newObj, _mergeOptions(l[i - 1], l[i]));
}
return newObj;
}
/** /**
* Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1 * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
* @param obj1 * @param obj1

View File

@ -50,7 +50,7 @@
} }
.ascribe-global-notification-bubble-off { .ascribe-global-notification-bubble-off {
right: -50em; right: -100em;
} }
.ascribe-global-notification-bubble-on { .ascribe-global-notification-bubble-on {

View File

@ -8,7 +8,7 @@ $ascribe-accordion-list-font: 'Source Sans Pro';
padding-left:0; padding-left:0;
padding-right:0; padding-right:0;
margin-top: 3em; margin-top: 1.5em;
&::first-child { &::first-child {
margin-top:0; margin-top:0;
} }
@ -44,13 +44,14 @@ $ascribe-accordion-list-font: 'Source Sans Pro';
} }
} }
h1 { h1 {
margin-top: .3em; margin: .1em 0 .1em 0;
font-size: 2.25em; font-size: 2.2em;
cursor: pointer; cursor: pointer;
} }
h3 { h3 {
font-size: 1.1em; font-size: 1.3em;
margin: .2em 0 0 0; margin: .2em 0 .3em 0;
cursor: pointer;
} }
a { a {
color: #666; color: #666;
@ -92,6 +93,7 @@ $ascribe-accordion-list-font: 'Source Sans Pro';
} }
} }
border-left: 3px solid rgba(0,0,0,0); border-left: 3px solid rgba(0,0,0,0);
//border-top: 1px solid rgba(0,0,0,.1);
border-bottom: 1px solid rgba(0,0,0,.05); border-bottom: 1px solid rgba(0,0,0,.05);
} }
tbody { tbody {
@ -120,8 +122,11 @@ $ascribe-accordion-list-font: 'Source Sans Pro';
} }
span.ascribe-accordion-list-table-toggle { span.ascribe-accordion-list-table-toggle {
::selection { background: transparent; } -webkit-user-select: none;
::-moz-selection { background: transparent; } -moz-user-select: none;
-khtml-user-select: none;
-ms-user-select: none;
&:hover { &:hover {
color: $ascribe-color-dark; color: $ascribe-color-dark;
cursor:pointer; cursor:pointer;
@ -147,9 +152,17 @@ span.ascribe-accordion-list-table-toggle {
.ascribe-accordion-list-item-edition-widget { .ascribe-accordion-list-item-edition-widget {
cursor: pointer; cursor: pointer;
//margin-left: 0.3em;
-webkit-user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
-ms-user-select: none;
&:hover { &:hover {
color: $ascribe-color-full; color: $ascribe-color-dark;
}
.glyphicon {
top: 1px !important;
font-size: 0.8em;
} }
} }

View File

@ -11,8 +11,8 @@
.ascribe-edition-collapsible-wrapper > div:first-child { .ascribe-edition-collapsible-wrapper > div:first-child {
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
background-color: #F5F5F5; background-color: rgba(0,0,0,0);
padding: 10px; padding: 0 10px 10px 0;
margin-top: 20px; margin-top: 20px;
} }
.ascribe-edition-collapsible-wrapper > div > span { .ascribe-edition-collapsible-wrapper > div > span {

View File

@ -28,13 +28,13 @@
.is-error { .is-error {
background-color: rgba(169, 68, 66, 0.05); background-color: rgba(169, 68, 66, 0.05);
border-left: 3px solid rgba(169, 68, 66, 1); border-left: 3px solid rgba(169, 68, 66, 1);
div { > div {
span { > span {
color: rgba(169, 68, 66, 1); color: rgba(169, 68, 66, 1);
font-size: 0.9em; font-size: 0.9em;
margin-right: 1em; margin-right: 1em;
} }
input, textarea { > input, > textarea {
color: #666; color: #666;
} }
} }
@ -46,20 +46,18 @@
.is-fixed { .is-fixed {
cursor: default; cursor: default;
div { > div {
cursor: default; cursor: default;
span { > span {
cursor: default; cursor: default;
} }
input, div, pre, textarea { > input, > div, > pre, > textarea {
cursor: default; cursor: default;
color: #666; color: #666;
} }
} }
} }
.ascribe-settings-property { .ascribe-settings-property {
display: inline-block; display: inline-block;
width: 100%; width: 100%;
@ -72,19 +70,19 @@
cursor:pointer; cursor:pointer;
input, div, span:not(.glyphicon), pre, textarea, select { > input, > div, > span:not(.glyphicon), > pre, > textarea, > select {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
} }
span { > span {
font-weight: normal; font-weight: normal;
font-size: 0.9em; font-size: 0.9em;
color: rgba(0,0,0,.5); color: rgba(0,0,0,.5);
} }
div { > div {
/* margin-top: 10px; */ /* margin-top: 10px; */
div:not(.file-drag-and-drop div) { > div:not(.file-drag-and-drop div) {
padding-left: 0; padding-left: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: normal; font-weight: normal;
@ -94,11 +92,11 @@
} }
} }
.progressbar-container, .progressbar-progress { > .progressbar-container, .progressbar-progress {
margin-top: 0 !important; margin-top: 0 !important;
} }
input, pre, textarea, select { > input, > pre, > textarea, > select {
font-weight: 400; font-weight: 400;
font-size: 1.1em; font-size: 1.1em;
width:100%; width:100%;
@ -121,7 +119,7 @@
} }
textarea{ > textarea{
color: #666; color: #666;
margin-top: 1em; margin-top: 1em;
padding: 0; padding: 0;
@ -155,14 +153,15 @@
color: rgba(0, 0, 0, .5); color: rgba(0, 0, 0, .5);
vertical-align:middle; vertical-align:middle;
span { > span {
position: relative; position: relative;
top: 1px; top: 1px;
left: 5px; left: 5px;
&:hover { -webkit-user-select: none;
color: rgba(2, 182, 163, 1); -moz-user-select: none;
} -khtml-user-select: none;
-ms-user-select: none;
} }
} }

View File

@ -82,7 +82,7 @@
.file-drag-and-drop-preview-image .action-file { .file-drag-and-drop-preview-image .action-file {
font-size: 2.5em; font-size: 2.5em;
margin-top: .3em; margin-top: .6em;
color: white; color: white;
text-shadow: -2px 0 black, 0 2px black, 2px 0 black, 0 -2px black; text-shadow: -2px 0 black, 0 2px black, 2px 0 black, 0 -2px black;
cursor: pointer; cursor: pointer;

View File

@ -68,6 +68,11 @@ hr {
font-size: 0.8em; font-size: 0.8em;
} }
.img-brand {
padding: 0;
height: 45px;
margin: 5px 0 5px 0;
}
.truncate { .truncate {
white-space: nowrap; white-space: nowrap;
width: 4em; width: 4em;
@ -98,12 +103,13 @@ hr {
padding-right:0; padding-right:0;
} }
.ascribe-color { .clear-margins {
color: $ascribe-color; margin-top:0;
margin-bottom:0;
} }
.img-brand{ .ascribe-color {
height: 25px; color: $ascribe-color;
} }
.ascribe-subheader{ .ascribe-subheader{