1
0
mirror of https://github.com/ascribe/onion.git synced 2025-01-03 18:35:09 +01:00

Merge remote-tracking branch 'remotes/origin/AD-551-work-on-burn-down-list' into AD-419-decouple-piece-registration-from-

This commit is contained in:
diminator 2015-07-10 14:49:19 +02:00
commit 8679a2446d
19 changed files with 190 additions and 32 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,6 +29,13 @@ 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,6 +57,52 @@ 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);
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;
@ -85,7 +143,9 @@ let AccordionListItem = React.createClass({
</div> </div>
<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> */}
@ -97,6 +157,8 @@ let AccordionListItem = React.createClass({
</div> </div>
</div> </div>
</div> </div>
{this.props.content.num_editions === 0 && 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,54 @@
'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(){
let data = {};
for (let ref in this.refs.form.refs){
data[this.refs.form.refs[ref].props.name] = this.refs.form.refs[ref].state.value;
}
data.piece_id = parseInt(this.props.pieceId, 10);
return data;
},
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() {
@ -74,13 +76,21 @@ let AccordionListItemEditionWidget = React.createClass({
let numEditions = piece.num_editions; let numEditions = piece.num_editions;
if(numEditions === 0) { if(numEditions === 0) {
if(this.props.creatingEditions) {
return (
<span>
Creating Editions <span className="glyph-ascribe-spool-chunked ascribe-color spin"/>
</span>
);
} else {
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">
+ Editions + Editions
</span> </span>
); );
}
} else if(numEditions === 1) { } 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 : '';

View File

@ -279,7 +279,7 @@ let EditionPersonalNote = React.createClass({
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}/>
@ -317,8 +317,8 @@ let EditionPublicEditionNote = React.createClass({
rows={1} 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}/>
@ -346,7 +346,6 @@ let CoaDetails = React.createClass({
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

@ -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,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,

View File

@ -81,7 +81,7 @@ let LoginForm = React.createClass({
}, },
handleSuccess(){ handleSuccess(){
let notification = new GlobalNotificationModel('Login successsful', 'success', 10000); let notification = new GlobalNotificationModel('Login successful', 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
// register_piece is waiting for a login success as login_container and it is wrapped // register_piece is waiting for a login success as login_container and it is wrapped

View File

@ -138,8 +138,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,6 +13,7 @@ let apiUrls = {
'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/${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/',

View File

@ -206,6 +206,7 @@ const languages = {
'Title': 'Title', 'Title': 'Title',
'Specify editions': 'Specify editions', 'Specify editions': 'Specify editions',
'Editions': 'Editions', 'Editions': 'Editions',
'Create editions': 'Create editions',
}, },
'de': { 'de': {
'ID': 'ID', 'ID': 'ID',
@ -412,6 +413,7 @@ const languages = {
'Title': 'Titel', 'Title': 'Titel',
'Specify editions': 'Specify editions', 'Specify editions': 'Specify editions',
'Editions': 'Editions', 'Editions': 'Editions',
'Create editions': 'Create editions',
}, },
'fr': { 'fr': {
'ID': 'ID', 'ID': 'ID',
@ -618,6 +620,7 @@ const languages = {
'Title': 'Title', 'Title': 'Title',
'Specify editions': 'Specify editions', 'Specify editions': 'Specify editions',
'Editions': 'Editions', 'Editions': 'Editions',
'Create editions': 'Create editions',
} }
}; };

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

@ -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

@ -150,6 +150,11 @@ span.ascribe-accordion-list-table-toggle {
.ascribe-accordion-list-item-edition-widget { .ascribe-accordion-list-item-edition-widget {
cursor: pointer; cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
-ms-user-select: none;
&:hover { &:hover {
color: $ascribe-color-dark; color: $ascribe-color-dark;
} }

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;