1
0
mirror of https://github.com/ascribe/onion.git synced 2024-06-28 16:48:04 +02:00

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

Conflicts:
	js/components/ascribe_detail/edition.js
This commit is contained in:
diminator 2015-07-09 14:59:16 +01:00
commit bf0a36f2a3
36 changed files with 664 additions and 198 deletions

View File

@ -4,12 +4,12 @@ import alt from '../alt';
import PieceListFetcher from '../fetchers/piece_list_fetcher';
class PieceListActions {
constructor() {
this.generateActions(
'updatePieceList',
'updatePieceListRequestActions'
'updatePieceListRequestActions',
'addFirstEditionToPiece'
);
}
@ -32,6 +32,7 @@ class PieceListActions {
.catch((err) => reject(err));
});
}
fetchPieceRequestActions() {
PieceListFetcher
.fetchRequestActions()
@ -40,6 +41,16 @@ class PieceListActions {
});
}
fetchFirstEditionForPiece(pieceId) {
return new Promise((resolve, reject) => {
PieceListFetcher.fetchFirstEditionForPiece(pieceId)
.then((firstEdition) => {
this.actions.addFirstEditionToPiece({pieceId, firstEdition});
resolve();
})
.catch((err) => reject(err));
});
}
}
export default alt.createActions(PieceListActions);

View File

@ -3,28 +3,36 @@
import React from 'react';
import Router from 'react-router';
import PieceListStore from '../../stores/piece_list_store';
import PieceListActions from '../../actions/piece_list_actions';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
import Tooltip from 'react-bootstrap/lib/Tooltip';
import requests from '../../utils/requests';
import AccordionListItemEditionWidget from './accordion_list_item_edition_widget';
import { getLangText } from '../../utils/lang_utils';
let AccordionListItem = React.createClass({
mixins: [Router.Navigation],
propTypes: {
className: React.PropTypes.string,
content: React.PropTypes.object,
children: React.PropTypes.object
},
handleClick(event){
requests.get('piece_first_edition_id', {'piece_id': this.props.content.id})
.then((res) => this.transitionTo('edition', {editionId: res.bitcoin_id}));
console.log(event.target);
mixins: [Router.Navigation],
componentDidMount() {
if(this.props.content.num_editions !== 0) {
PieceListActions.fetchFirstEditionForPiece(this.props.content.id);
}
},
onChange(state) {
this.setState(state);
},
getGlyphicon(){
if (this.props.content.requestAction){
return (
@ -35,6 +43,7 @@ let AccordionListItem = React.createClass({
}
return null;
},
render() {
return (
<div className="row">
@ -53,9 +62,12 @@ let AccordionListItem = React.createClass({
<h3>{getLangText('by %s', this.props.content.artist_name)}</h3>
<div>
<span>{this.props.content.date_created.split('-')[0]}</span>
<a href={this.props.content.license_type.url} target="_blank" className="pull-right">
<AccordionListItemEditionWidget
piece={this.props.content} />
{/* <a href={this.props.content.license_type.url} target="_blank" className="pull-right">
{getLangText('%s license', this.props.content.license_type.code)}
</a>
</a> */}
</div>
</div>
<span style={{'clear': 'both'}}></span>

View File

@ -0,0 +1,93 @@
'use strict';
import React from 'react';
import EditionListActions from '../../actions/edition_list_actions';
import EditionListStore from '../../stores/edition_list_store';
import { getLangText } from '../../utils/lang_utils';
import { mergeOptions } from '../../utils/general_utils';
let AccordionListItemEditionWidget = React.createClass({
propTypes: {
piece: React.PropTypes.object.isRequired
},
getInitialState() {
return EditionListStore.getState();
},
componentDidMount() {
EditionListStore.listen(this.onChange);
},
componentWillUnmount() {
EditionListStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
/**
* Calls the store to either show or hide the editionListTable
*/
toggleTable() {
let pieceId = this.props.piece.id;
let isEditionListOpen = this.state.isEditionListOpenForPieceId[pieceId] ? this.state.isEditionListOpenForPieceId[pieceId].show : false;
if(isEditionListOpen) {
EditionListActions.toggleEditionList(pieceId);
} else {
EditionListActions.toggleEditionList(pieceId);
EditionListActions.fetchEditionList(pieceId);
}
},
/**
* Depending on the state of isEditionListOpenForPieceId we either want to display
* a glyphicon arrow pointing upwards or downwards
*/
getGlyphicon() {
let pieceId = this.props.piece.id;
let isEditionListOpen = this.state.isEditionListOpenForPieceId[pieceId] ? this.state.isEditionListOpenForPieceId[pieceId].show : false;
if(isEditionListOpen) {
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>
);
}
},
render() {
let piece = this.props.piece;
let numEditions = piece.num_editions;
if(numEditions === 1) {
let firstEditionId = piece && piece.firstEdition ? ', ' + piece.firstEdition.bitcoin_id : '';
let editionMapping = piece && piece.firstEdition ? piece.firstEdition.edition_number + '/' + piece.num_editions : '';
return (
<span
onClick={this.toggleTable}
className="ascribe-accordion-list-item-edition-widget pull-right">
{this.getGlyphicon()} {editionMapping + ' ' + getLangText('Edition') + firstEditionId}
</span>
);
} else {
return (
<span
onClick={this.toggleTable}
className="ascribe-accordion-list-item-edition-widget pull-right">
{this.getGlyphicon()} {numEditions + ' ' + getLangText('Editions')}
</span>
);
}
}
});
export default AccordionListItemEditionWidget;

View File

@ -3,7 +3,7 @@
import React from 'react';
import Table from '../ascribe_table/table';
import TableItem from '../ascribe_table/table_item';
import TableItemSelectable from '../ascribe_table/table_item_selectable';
import { ColumnModel } from '../ascribe_table/models/table_models';
@ -16,7 +16,8 @@ let AccordionListItemTable = React.createClass({
show: React.PropTypes.bool,
changeOrder: React.PropTypes.func,
orderBy: React.PropTypes.string,
orderAsc: React.PropTypes.bool
orderAsc: React.PropTypes.bool,
selectItem: React.PropTypes.func
},
render() {
@ -32,9 +33,11 @@ let AccordionListItemTable = React.createClass({
orderAsc={this.props.orderAsc}>
{this.props.itemList.map((item, i) => {
return (
<TableItem
<TableItemSelectable
className="ascribe-table-item-selectable"
key={i} />
key={i}
selectItem={this.props.selectItem}
parentId={this.props.parentId}/>
);
})}
</Table>

View File

@ -69,16 +69,6 @@ let AccordionListItemTableEditions = React.createClass({
return selectedEditions;
},
toggleTable() {
let isEditionListOpen = this.state.isEditionListOpenForPieceId[this.props.parentId] ? this.state.isEditionListOpenForPieceId[this.props.parentId].show : false;
if(isEditionListOpen) {
EditionListActions.toggleEditionList(this.props.parentId);
} else {
EditionListActions.toggleEditionList(this.props.parentId);
EditionListActions.fetchEditionList(this.props.parentId);
}
},
loadFurtherEditions() {
// trigger loading animation
this.setState({
@ -187,14 +177,14 @@ let AccordionListItemTableEditions = React.createClass({
)
];
let loadingSpinner = <span className="glyph-ascribe-spool-chunked ascribe-color spin"/> ;
let loadingSpinner = <span className="glyph-ascribe-spool-chunked ascribe-color spin"/>;
return (
<div className={this.props.className}>
<AccordionListItemTableToggle
{/* <AccordionListItemTableToggle
className="ascribe-accordion-list-table-toggle"
onClick={this.toggleTable}
message={show && typeof editionsForPiece !== 'undefined' ? <span><span className="glyphicon glyphicon-menu-up" aria-hidden="true" style={{top: 2}}></span> {getLangText('Hide editions')}</span> : <span><span className="glyphicon glyphicon-menu-down" aria-hidden="true" style={{top: 2}}></span> {getLangText('Show editions')} {show && typeof editionsForPiece === 'undefined' ? loadingSpinner : null}</span>} />
message={show && typeof editionsForPiece !== 'undefined' ? <span><span className="glyphicon glyphicon-menu-up" aria-hidden="true" style={{top: 2}}></span> {getLangText('Hide editions')}</span> : <span><span className="glyphicon glyphicon-menu-down" aria-hidden="true" style={{top: 2}}></span> {getLangText('Show editions')} {show && typeof editionsForPiece === 'undefined' ? loadingSpinner : null}</span>} /> */}
<AccordionListItemTable
parentId={this.props.parentId}
itemList={editionsForPiece}
@ -202,7 +192,8 @@ let AccordionListItemTableEditions = React.createClass({
show={show}
orderBy={orderBy}
orderAsc={orderAsc}
changeOrder={this.changeEditionListOrder} />
changeOrder={this.changeEditionListOrder}
selectItem={this.selectItem}/>
<AccordionListItemTableToggle
className="ascribe-accordion-list-table-toggle"
onClick={this.loadFurtherEditions}

View File

@ -6,8 +6,6 @@ import CollapsibleMixin from 'react-bootstrap/lib/CollapsibleMixin';
import classNames from 'classnames';
import { getLangText } from '../../utils/lang_utils.js'
const CollapsibleParagraph = React.createClass({
@ -43,13 +41,14 @@ const CollapsibleParagraph = React.createClass({
render() {
let styles = this.getCollapsibleClassSet();
let text = this.isExpanded() ? '[' + getLangText('hide') + ']' : '[' + getLangText('show') + ']';
let text = this.isExpanded() ? '-' : '+';
if(this.props.show) {
return (
<div className="ascribe-detail-header">
<div className="ascribe-edition-collapsible-wrapper">
<div onClick={this.handleToggle}>
<span>{this.props.title}</span><span className="pull-right">{text}</span>
<span>{text} {this.props.title}</span>
</div>
<div ref='panel' className={classNames(styles) + ' ascribe-edition-collapible-content'}>
{this.props.children}

View File

@ -39,6 +39,7 @@ 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;
/**

View File

@ -17,10 +17,10 @@ let EditionContainer = React.createClass({
onChange(state) {
this.setState(state);
let isEncoding = state.edition.digital_work.isEncoding;
if (isEncoding !== undefined && isEncoding !== 100) {
let isEncoding = state.edition.digital_work ? state.edition.digital_work.isEncoding : null;
if (typeof isEncoding === 'number' && isEncoding !== 100 && !this.state.timerId) {
let timerId = window.setInterval(() => EditionActions.fetchOne(this.props.params.editionId), 10000);
this.setState({timerId: timerId})
this.setState({timerId: timerId});
}
},

View File

@ -18,7 +18,8 @@ let Form = React.createClass({
children: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.array
])
]),
className: React.PropTypes.string
},
getInitialState() {
@ -141,10 +142,16 @@ let Form = React.createClass({
});
},
render() {
let className = 'ascribe-form';
if(this.props.className) {
className += ' ' + this.props.className;
}
return (
<form
role="form"
className="ascribe-form"
className={className}
onSubmit={this.submit}
autoComplete="on">
{this.getErrors()}

View File

@ -9,7 +9,7 @@ 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'
import { getLangText } from '../../utils/lang_utils.js';
let LoginForm = React.createClass({
mixins: [FormMixin],

View File

@ -0,0 +1,19 @@
'use strict';
import React from 'react';
let FormPropertyHeader = React.createClass({
propTypes: {
children: React.PropTypes.arrayOf(React.PropTypes.element)
},
render() {
return (
<div className="ascribe-form-header">
{this.props.children}
</div>
);
}
});
export default FormPropertyHeader;

View File

@ -0,0 +1,90 @@
'use strict';
import React from 'react';
import CollapsibleMixin from 'react-bootstrap/lib/CollapsibleMixin';
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
import Tooltip from 'react-bootstrap/lib/Tooltip';
import classNames from 'classnames';
let PropertyCollapsile = React.createClass({
propTypes: {
children: React.PropTypes.arrayOf(React.PropTypes.element),
checkboxLabel: React.PropTypes.string,
tooltip: React.PropTypes.string
},
mixins: [CollapsibleMixin],
getInitialState() {
return {
show: false
};
},
getCollapsibleDOMNode(){
return React.findDOMNode(this.refs.panel);
},
getCollapsibleDimensionValue(){
return React.findDOMNode(this.refs.panel).scrollHeight;
},
handleFocus() {
this.refs.checkboxCollapsible.getDOMNode().checked = !this.refs.checkboxCollapsible.getDOMNode().checked;
this.setState({
show: this.refs.checkboxCollapsible.getDOMNode().checked
});
},
renderChildren() {
if(this.state.show) {
return (<div
className={classNames(this.getCollapsibleClassSet()) + ' ascribe-settings-property'}
ref="panel">
{this.props.children}
</div>);
} else {
return null;
}
},
render() {
let tooltip = <span/>;
if (this.props.tooltip){
tooltip = (
<Tooltip>
{this.props.tooltip}
</Tooltip>);
}
let style = this.state.show ? {} : {paddingBottom: 0};
return (
<div
className={'ascribe-settings-wrapper'}
style={style}>
<OverlayTrigger
delay={500}
placement="top"
overlay={tooltip}>
<div
className="ascribe-settings-property-collapsible-toggle"
onClick={this.handleFocus}
onFocus={this.handleFocus}>
<input
type="checkbox"
ref="checkboxCollapsible"/>
{/* PLEASE LEAVE THE SPACE BETWEEN LABEL and this.props.label */}
<span className="checkbox"> {this.props.checkboxLabel}</span>
</div>
</OverlayTrigger>
{this.renderChildren()}
</div>
);
}
});
export default PropertyCollapsile;

View File

@ -133,25 +133,6 @@ let Video = React.createClass({
}
});
let EncodingStatus = React.createClass({
propTypes: {
encodingStatus: React.PropTypes.number.isRequired
},
render() {
return (
<video ref="video" className="video-js vjs-default-skin" poster={this.props.preview}
controls preload="none" width="auto" height="auto">
{this.props.extraData.map((data, i) =>
<source key={i} type={'video/' + data.type} src={data.url} />
)}
</video>
);
}
});
let resourceMap = {
'image': Image,
'video': Video,
@ -169,7 +150,7 @@ let MediaPlayer = React.createClass({
},
render() {
if (this.props.encodingStatus !== undefined && this.props.encodingStatus !== 100) {
if (this.props.mimetype === 'video' && this.props.encodingStatus !== undefined && this.props.encodingStatus !== 100) {
return (
<div className="ascribe-detail-header ascribe-media-player">
<p><em>Please be patient, the video is been encoded</em></p>

View File

@ -12,12 +12,14 @@ let TableItem = React.createClass({
propTypes: {
columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(ColumnModel)),
columnContent: React.PropTypes.object,
className: React.PropTypes.string
className: React.PropTypes.string,
onClick: React.PropTypes.func
},
render() {
return (
<TableItemWrapper
onClick={this.props.onClick}
columnList={this.props.columnList}
columnContent={this.props.columnContent}
columnWidth={12} />

View File

@ -7,18 +7,13 @@ let TableItemCheckbox = React.createClass({
propTypes: {
editionId: React.PropTypes.number,
pieceId: React.PropTypes.number,
selectItem: React.PropTypes.func,
selected: React.PropTypes.bool
},
selectItem() {
this.props.selectItem(this.props.pieceId, this.props.editionId);
},
render() {
return (
<span>
<input type="checkbox" onChange={this.selectItem} checked={this.props.selected}/>
<input type="checkbox" checked={this.props.selected} readOnly/>
</span>
);
}

View File

@ -19,7 +19,7 @@ let TableItemSelectable = React.createClass({
},
selectItem() {
this.props.selectItem(this.props.parentId, this.props.columnContent.edition_number);
this.props.selectItem(this.props.parentId, this.props.columnContent.id);
},
render() {

View File

@ -11,14 +11,15 @@ let TableItemWrapper = React.createClass({
propTypes: {
columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(ColumnModel)),
columnContent: React.PropTypes.object,
columnWidth: React.PropTypes.number.isRequired
columnWidth: React.PropTypes.number.isRequired,
onClick: React.PropTypes.func
},
mixins: [Router.Navigation],
render() {
return (
<tr>
<tr onClick={this.props.onClick}>
{this.props.columnList.map((column, i) => {
let TypeElement = column.displayType;

View File

@ -7,66 +7,116 @@ import GlobalNotificationStore from '../stores/global_notification_store';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import { mergeOptions } from '../utils/general_utils';
let GlobalNotification = React.createClass({
getInitialState() {
return mergeOptions(
{
containerWidth: 0
},
this.extractFirstElem(GlobalNotificationStore.getState().notificationQue)
);
},
componentDidMount() {
GlobalNotificationStore.listen(this.onChange);
// init container width
this.handleContainerResize();
// we're using an event listener on window here,
// as it was not possible to listen to the resize events of a dom node
window.addEventListener('resize', this.handleContainerResize);
},
componentWillUnmount() {
GlobalNotificationStore.unlisten(this.onChange);
},
getInititalState() {
return this.extractFirstElem(GlobalNotificationStore.getState().notificationQue);
window.removeEventListener('resize', this.handleContainerResize);
},
extractFirstElem(l) {
return l.length > 0 ? l[0] : null;
if(l.length > 0) {
return {
show: true,
message: l[0]
};
} else {
return {
show: false,
message: ''
};
}
},
onChange(state) {
let notification = this.extractFirstElem(state.notificationQue);
if(notification) {
if(notification.show) {
this.setState(notification);
} else {
this.replaceState(null);
this.setState({
show: false
});
}
},
handleContainerResize() {
this.setState({
containerWidth: this.refs.notificationWrapper.getDOMNode().offsetWidth
});
},
render() {
let notificationClass = 'ascribe-global-notification ';
let message = this.state && this.state.message ? this.state.message : null;
let notificationClass = 'ascribe-global-notification';
let textClass;
if(message) {
let colors = {
warning: '#f0ad4e',
success: '#5cb85c',
info: 'rgba(2, 182, 163, 1)',
danger: '#d9534f'
};
if(this.state.containerWidth > 768) {
notificationClass = 'ascribe-global-notification-bubble';
let text = (<div style={{color: colors[this.state.type]}}>{message ? message : null}</div>);
if(this.state.show) {
notificationClass += ' ascribe-global-notification-bubble-on';
} else {
notificationClass += ' ascribe-global-notification-bubble-off';
}
return (
} else {
notificationClass = 'ascribe-global-notification';
if(this.state.show) {
notificationClass += ' ascribe-global-notification-on';
} else {
notificationClass += ' ascribe-global-notification-off';
}
}
if(this.state.message) {
switch(this.state.message.type) {
case 'success':
textClass = 'ascribe-global-notification-success';
break;
case 'danger':
textClass = 'ascribe-global-notification-danger';
break;
default:
console.warn('Could not find a matching type in global_notification.js');
}
}
return (
<div ref="notificationWrapper">
<Row>
<Col>
<div className={notificationClass + 'ascribe-global-notification-on'}>
{text}
<div className={notificationClass}>
<div className={textClass}>{this.state.message.message}</div>
</div>
</Col>
</Row>
);
} else {
return (
<Row>
<Col>
<div className={notificationClass + 'ascribe-global-notification-off'} />
</Col>
</Row>
);
}
</div>
);
}
});

View File

@ -19,11 +19,12 @@ import MenuItem from 'react-bootstrap/lib/MenuItem';
import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink';
import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
import HeaderNotificationDebug from './header_notification_debug';
import { mergeOptions } from '../utils/general_utils';
import { getLangText } from '../utils/lang_utils';
let Link = Router.Link;
let Header = React.createClass({
mixins: [Router.Navigation],
@ -43,11 +44,13 @@ let Header = React.createClass({
UserStore.unlisten(this.onChange);
WhitelabelStore.unlisten(this.onChange);
},
handleLogout(){
UserActions.logoutCurrentUser();
Alt.flush();
this.transitionTo('login');
},
getLogo(){
let logo = (
<span>
@ -102,15 +105,14 @@ let Header = React.createClass({
<div>
<Navbar
brand={
<Link className="navbar-brand" to="pieces">
{this.getLogo()}
</Link>}
this.getLogo()
}
toggleNavKey={0}
fixedTop={true}>
<CollapsibleNav eventKey={0}>
<Nav navbar left>
</Nav>
<Nav navbar left />
<Nav navbar right>
<HeaderNotificationDebug show={false}/>
{addNewWork}
{collection}
{account}

View File

@ -0,0 +1,51 @@
'use strict';
import React from 'react';
import GlobalNotificationModel from '../models/global_notification_model';
import GlobalNotificationActions from '../actions/global_notification_actions';
import MenuItem from 'react-bootstrap/lib/MenuItem';
/*
This components purpose is to be inserted into the page's navigation in order
debug the globalnotificationsaction easily
*/
let HeaderNotificationDebug = React.createClass({
propTypes: {
show: React.PropTypes.bool
},
getInitialState() {
return {
index: 0
};
},
triggerNotification() {
if(this.state.index === 1) {
this.setState({index: 0});
} else {
this.setState({index: this.state.index + 1});
}
let actions = ['success', 'danger'];
let notification = new GlobalNotificationModel('this is a test, please ignore', actions[this.state.index]);
GlobalNotificationActions.appendGlobalNotification(notification);
},
render() {
if(this.props.show) {
return (
<MenuItem onClick={this.triggerNotification}>Notification</MenuItem>
);
} else {
return null;
}
}
});
export default HeaderNotificationDebug;

View File

@ -5,39 +5,39 @@ import React from 'react';
import Button from 'react-bootstrap/lib/Button';
import Modal from 'react-bootstrap/lib/Modal';
import OverlayMixin from 'react-bootstrap/lib/OverlayMixin';
import { getLangText } from '../utils/lang_utils.js'
import { getLangText } from '../utils/lang_utils.js';
let LoginModalHandler = React.createClass({
mixins: [OverlayMixin],
mixins: [OverlayMixin],
getInitialState() {
return {
isModalOpen: true
};
},
getInitialState() {
return {
isModalOpen: true
};
},
handleToggle() {
this.setState({
isModalOpen: !this.state.isModalOpen
});
},
handleToggle() {
this.setState({
isModalOpen: !this.state.isModalOpen
});
},
render() {
if (!this.state.isModalOpen || !(this.props.query.login === '')) {
return <span/>;
render() {
if(!this.state.isModalOpen || !(this.props.query.login === '')) {
return <span/>;
}
return (
<Modal title='Modal heading' onRequestHide={this.handleToggle}>
<div className='modal-body'>
This modal is controlled by our custom trigger component.
</div>
<div className='modal-footer'>
<Button onClick={this.handleToggle}>{getLangText('Close')}</Button>
</div>
</Modal>
);
}
return (
<Modal title='Modal heading' onRequestHide={this.handleToggle}>
<div className='modal-body'>
This modal is controlled by our custom trigger component.
</div>
<div className='modal-footer'>
<Button onClick={this.handleToggle}>{getLangText('Close')}</Button>
</div>
</Modal>
);
}
});
export default LoginModalHandler;

View File

@ -47,6 +47,7 @@ let PieceList = React.createClass({
},
paginationGoToPage(page) {
document.body.scrollTop = document.documentElement.scrollTop = 0;
return () => PieceListActions.fetchPieceList(page, this.state.pageSize,
this.state.search, this.state.orderBy,
this.state.orderAsc);
@ -84,21 +85,14 @@ let PieceList = React.createClass({
pageSize={this.state.pageSize}
loadingElement={loadingElement}>
{this.state.pieceList.map((piece, i) => {
let editionsTableForPiece;
if(piece.num_editions !== 1) {
editionsTableForPiece = <AccordionListItemTableEditions
className="ascribe-accordion-list-item-table col-xs-12 col-sm-8 col-md-6 col-lg-6 col-sm-offset-2 col-md-offset-3 col-lg-offset-3"
parentId={piece.id} />;
}
return (
<AccordionListItem
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 ascribe-accordion-list-item"
content={piece}
key={i}>
{editionsTableForPiece}
<AccordionListItemTableEditions
className="ascribe-accordion-list-item-table col-xs-12 col-sm-8 col-md-6 col-lg-6 col-sm-offset-2 col-md-offset-3 col-lg-offset-3"
parentId={piece.id} />
</AccordionListItem>
);
})}

View File

@ -23,6 +23,8 @@ 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 LoginContainer from './login_container';
import SlidesContainer from './ascribe_slides_container/slides_container';
@ -172,8 +174,8 @@ let RegisterPiece = React.createClass( {
onFocus={this.changeSlide}>
<Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<h3 style={{'marginTop': 0, 'marginLeft': '1em'}}>{getLangText('Register your work')}</h3>
<Form
className="ascribe-form-bordered"
ref='form'
url={apiUrls.pieces_list}
getFormData={this.getFormData}
@ -189,6 +191,9 @@ let RegisterPiece = React.createClass( {
<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
@ -222,8 +227,15 @@ let RegisterPiece = React.createClass( {
min={0}
required/>
</Property>
<PropertyCollapsible
checkboxLabel={getLangText('Specify editions')}>
<span>{getLangText('Editions')}</span>
<input
type="number"
placeholder="(e.g. 32)"
min={0}/>
</PropertyCollapsible>
{this.getLicenses()}
<hr />
</Form>
</Col>
</Row>

View File

@ -131,6 +131,10 @@ let AccountSettings = React.createClass({
let BitcoinWalletSettings = React.createClass({
propTypes: {
defaultExpanded: React.PropTypes.bool
},
getInitialState() {
return WalletSettingsStore.getState();
},
@ -172,7 +176,7 @@ let BitcoinWalletSettings = React.createClass({
<CollapsibleParagraph
title={getLangText('Crypto Wallet')}
show={true}
defaultExpanded={true}>
defaultExpanded={this.props.defaultExpanded}>
{content}
</CollapsibleParagraph>
);
@ -180,6 +184,9 @@ let BitcoinWalletSettings = React.createClass({
});
let LoanContractSettings = React.createClass({
propTypes: {
defaultExpanded: React.PropTypes.bool
},
render() {
@ -187,7 +194,7 @@ let LoanContractSettings = React.createClass({
<CollapsibleParagraph
title="Loan Contract Settings"
show={true}
defaultExpanded={true}>
defaultExpanded={this.props.defaultExpanded}>
<FileUploader />
</CollapsibleParagraph>
);
@ -249,6 +256,10 @@ let FileUploader = React.createClass({
});
let APISettings = React.createClass({
propTypes: {
defaultExpanded: React.PropTypes.bool
},
getInitialState() {
return ApplicationStore.getState();
},
@ -312,7 +323,7 @@ let APISettings = React.createClass({
<CollapsibleParagraph
title={getLangText('API Integration')}
show={true}
defaultExpanded={true}>
defaultExpanded={this.props.defaultExpanded}>
<Form
url={apiUrls.applications}
handleSuccess={this.handleCreateSuccess}>

View File

@ -65,7 +65,7 @@ const languages = {
'Account': 'Account',
'Copyright license%s': 'Copyright license%s',
'Files to upload': 'Files to upload',
'Lock down title': 'Lock down title',
'Register your work': 'Register your work',
'Artist Name': 'Artist Name',
'The name of the creator': 'The name of the creator',
'Number of editions': 'Number of editions',
@ -85,8 +85,8 @@ const languages = {
'terms of service': 'terms of service',
'privacy': 'privacy',
'Search%s': 'Search%s',
'Hide all Editions' : 'Hide all Editions',
'Hide editions' : 'Hide editions',
'Hide all Editions': 'Hide all Editions',
'Hide editions': 'Hide editions',
'Show all Editions': 'Show all Editions',
'Show editions': 'Show editions',
'Edition': 'Edition',
@ -203,6 +203,9 @@ const languages = {
'NEW WORK': 'NEW WORK',
'Have someone else sell the artwork': 'Have someone else sell the artwork',
'Loan History': 'Loan History',
'Title': 'Title',
'Specify editions': 'Specify editions',
'Editions': 'Editions',
},
'de': {
'ID': 'ID',
@ -268,7 +271,7 @@ const languages = {
'Account': 'Account',
'Copyright license%s': 'Copyright license%s',
'Files to upload': 'Files to upload',
'Lock down title': 'Lock down title',
'Register your work': 'Register your work',
'Artist Name': 'Artist Name',
'The name of the creator': 'The name of the creator',
'Number of editions': 'Number of editions',
@ -288,8 +291,8 @@ const languages = {
'terms of service': 'terms of service',
'privacy': 'privacy',
'Search%s': 'Search%s',
'Hide all Editions' : 'Hide all Editions',
'Hide editions' : 'Hide editions',
'Hide all Editions': 'Hide all Editions',
'Hide editions': 'Hide editions',
'Show all Editions': 'Show all Editions',
'Show editions': 'Show editions',
'Edition': 'Edition',
@ -406,6 +409,9 @@ const languages = {
'NEW WORK': 'NEW WORK',
'Have someone else sell the artwork': 'Have someone else sell the artwork',
'Loan History': 'Loan History',
'Title': 'Titel',
'Specify editions': 'Specify editions',
'Editions': 'Editions',
},
'fr': {
'ID': 'ID',
@ -471,7 +477,7 @@ const languages = {
'Account': 'Compte',
'Copyright license%s': 'License de droit d\'auteur%s',
'Files to upload': 'Fichiers à télécharger',
'Lock down title': 'Verrouillez le titre',
'Register your work': 'Verrouillez le titre',
'Artist Name': 'Nom de l\'artiste',
'The name of the creator': 'Le nom du créateur',
'Number of editions': 'Nombre d\'éditions',
@ -489,10 +495,10 @@ const languages = {
'api': 'api',
'impressum': 'impressum',
'terms of service': 'conditions d\'utilisation',
'privacy': 'confidentialité',
'privacy': 'confidentialité',
'Search%s': 'Rechercher%s',
'Hide all Editions' : 'Cacher toutes les Éditions',
'Hide editions' : 'Cacher les éditions',
'Hide all Editions': 'Cacher toutes les Éditions',
'Hide editions': 'Cacher les éditions',
'Show all Editions': 'Montrer toutes les Éditions',
'Show editions': 'Montrer les éditions',
'Edition': 'Édition',
@ -565,7 +571,7 @@ const languages = {
'Loan start date': 'Date du commencement du prêt',
'Loan end date': 'Date de la fin de prêt',
'LOAN': 'PRÊT',
'I transfer ownership of': 'Je transfère la propriété de' ,
'I transfer ownership of': 'Je transfère la propriété de',
'Transferee email': 'Courriel du cessionnaire',
'TRANSFER': 'TRANSFÉRER',
'Forgot your password': 'Oubliez votre mot de passe',
@ -609,6 +615,9 @@ const languages = {
'NEW WORK': 'NOUVEL OEUVRE',
'Have someone else sell the artwork': 'Demandez à quelqu\'un de vendre l\'oeuvre',
'Loan History': 'Historique de Prêts',
'Title': 'Title',
'Specify editions': 'Specify editions',
'Editions': 'Editions',
}
};

View File

@ -16,6 +16,10 @@ let PieceListFetcher = {
fetchRequestActions() {
return requests.get('pieces_list_request_actions');
},
fetchFirstEditionForPiece(pieceId) {
return requests.get('piece_first_edition_id', {'piece_id': pieceId});
}
};

View File

@ -27,26 +27,6 @@ class PieceListStore {
this.orderAsc = true;
this.bindActions(PieceListActions);
}
/*onShowEditionList(pieceId) {
this.pieceList
.forEach((piece) => {
if(piece.id === pieceId) {
if(piece.show) {
piece.show = false;
} else {
piece.show = true;
}
}
});
}*/
/*onCloseAllEditionLists() {
this.pieceList
.forEach((piece) => {
piece.show = false;
});
}*/
onUpdatePieceList({ page, pageSize, search, pieceList, orderBy, orderAsc, pieceListCount }) {
this.page = page;
@ -85,11 +65,25 @@ class PieceListStore {
this.pieceList = pieceList;
}
onUpdatePieceListRequestActions(requestActions) {
this.pieceList.forEach((piece) => {
piece.requestAction = requestActions.indexOf(piece.id) > -1;
});
}
onAddFirstEditionToPiece({pieceId, firstEdition}) {
let filteredPieceList = this.pieceList.filter((piece) => piece.id === pieceId);
if(filteredPieceList.length === 1) {
let piece = filteredPieceList[0];
piece.firstEdition = firstEdition.edition;
} 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');

View File

@ -70,11 +70,17 @@ export function formatText() {
function _doesObjectListHaveDuplicates(l) {
let mergedList = [];
l = l.map((obj) => Object.keys(obj));
l = l.map((obj) => {
if(!obj) {
throw new Error('The object you are trying to merge is null instead of an empty object');
}
return Object.keys(obj);
});
// Taken from: http://stackoverflow.com/a/10865042
// How to flatten an array of arrays in javascript.
// If two objects contain the same key, then these two keys
// If two objects contain the same key, then these two keys
// will actually be represented in the merged array
mergedList = mergedList.concat.apply(mergedList, l);
@ -102,6 +108,7 @@ export function mergeOptions(...l) {
for(let i = 1; i < l.length; i++) {
newObj = _mergeOptions(newObj, _mergeOptions(l[i - 1], l[i]));
}
return newObj;
}

View File

@ -2,6 +2,7 @@
position: fixed;
background-color: #212121;
color: white;
width: 100%;
height:3.5em;
left:0;
@ -18,12 +19,48 @@
bottom: 0;
}
.ascribe-global-notification > div {
.ascribe-global-notification > div, .ascribe-global-notification-bubble > div {
display:table-cell;
vertical-align: middle;
color: $ascribe-color-full;
font-size: 1.25em;
font-family: 'Source Sans Pro';
text-align: right;
padding-right: 3em;
}
.ascribe-global-notification-bubble > div {
padding: .75em 1.5em .75em 1.5em;
}
.ascribe-global-notification-bubble {
position: fixed;
bottom: 3em;
right: -50em;
display:table;
height: 3.5em;
background-color: #212121;
border-radius: 2px;
color: white;
transition: 1s right ease;
}
.ascribe-global-notification-bubble-off {
right: -50em;
}
.ascribe-global-notification-bubble-on {
right: 3.5em;
}
.ascribe-global-notification-danger {
background-color: #d9534f;
}
.ascribe-global-notification-success {
background-color: rgba(2, 182, 163, 1);
}

View File

@ -82,7 +82,7 @@ $ascribe-accordion-list-font: 'Source Sans Pro';
thead:first-child {
tr:first-child {
border: none! important;
th{
th {
padding-left: 10px;
border: none! important;
}
@ -139,4 +139,12 @@ span.ascribe-accordion-list-table-toggle {
color: #666;
font-size: 1.2em;
padding: 0.3em;
}
.ascribe-accordion-list-item-edition-widget {
cursor: pointer;
&:hover {
color: $ascribe-color-full;
}
}

View File

@ -11,13 +11,12 @@
.ascribe-edition-collapsible-wrapper > div:first-child {
width: 100%;
cursor: pointer;
background-color: #EEE;
background-color: #F5F5F5;
padding: 10px;
border: 1px solid #CCC;
margin-top: 20px;
}
.ascribe-edition-collapsible-wrapper > div > span {
font-size:1.3em;
font-size: 1.2em;
margin-right: .5em;
}
.ascribe-edition-collapsible-wrapper > div > span:nth-child(2) {
@ -26,8 +25,6 @@
.ascribe-edition-collapible-content {
width:100%;
margin-top: 1em;
}
.coa-file-wrapper{
@ -44,4 +41,8 @@
vertical-align: middle;
border: 1px solid #CCC;
background-color: #F8F8F8;
}
.ascribe-button-list {
margin-top: 1em;
}

16
sass/ascribe_form.scss Normal file
View File

@ -0,0 +1,16 @@
.ascribe-form-bordered {
border: 1px solid #F5F5F5;
}
.ascribe-form-header {
padding-bottom: 0;
margin-bottom: 0;
background-color: white;
}
.ascribe-form-header > h3 {
padding: .75em 0 .75em 1em;
margin-top: 0;
margin-bottom: 0;
color: #616161;
}

View File

@ -4,7 +4,7 @@
text-align: center;
padding-bottom: 1em;
background-color: rgba(0,0,0,0);
background-color: white;
border-left: 3px solid rgba(0,0,0,0);
@ -22,7 +22,7 @@
.is-focused {
background-color: rgba(2, 182, 163, 0.05);
border-left: 3px solid rgba(2, 182, 163, 1)!important;
border-left: 3px solid rgba(2, 182, 163, 1) !important;
}
.is-error {
@ -128,8 +128,67 @@
}
}
.ascribe-property-footer{
.ascribe-property-footer {
font-size: 0.8em;
margin-top: 10px;
width: 100%;
}
.ascribe-settings-property-collapsible-toggle {
text-align: left;
display: inline-block;
width: 100%;
border-top: 1px solid rgba(0,0,0,.05);
padding: .5em 1.5em .5em 1.5em;
cursor:pointer;
/* Taken from: http://www.htmllion.com/css3-checkbox.html */
.checkbox {
display: inline-block;
cursor: pointer;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: normal;
font-size: .9em;
color: rgba(0, 0, 0, .5);
vertical-align:middle;
span {
position: relative;
top: 1px;
left: 5px;
&:hover {
color: rgba(2, 182, 163, 1);
}
}
}
input[type=checkbox] {
display:none;
}
.checkbox:before {
content: "";
display: inline-block;
width: 17px;
height: 17px;
vertical-align:middle;
background-color: white;
color: #f3f3f3;
text-align: center;
border-radius: 1px;
border: 1px solid rgba(0, 0, 0, .5);
}
input[type=checkbox]:checked + .checkbox:before {
line-height: .8;
content: "\2713";
font-size: 20px;
color: rgba(2, 182, 163, 1);
}
}

View File

@ -1,5 +1,6 @@
.ascribe-table {
margin-bottom:0;
font-size: 1.1em;
}
/*This is aligning the first checkbox in pieclist detail with all the other ones*/
@ -8,6 +9,10 @@
}
.table > thead > tr > th {
vertical-align: middle;
}
.ascribe-table-header-column > span {
display: table-cell;
vertical-align: middle;
@ -32,7 +37,7 @@
}
.ascribe-table-item-column > span > input {
margin-top:16px;
margin-top:18px;
}
.ascribe-table-item-selected {

View File

@ -4,7 +4,7 @@
vertical-align: middle;
text-align: center;
height: auto;
background-color: #FAFAFA;
background-color: #FEFEFE;
overflow: auto;
margin-top: 1em;

View File

@ -26,6 +26,7 @@ $BASE_URL: '<%= BASE_URL %>';
@import 'offset_right';
@import 'ascribe_settings';
@import 'ascribe_slides_container';
@import 'ascribe_form';
body {
background-color: #FDFDFD;
@ -37,6 +38,10 @@ html {
overflow-y: scroll;
}
hr {
margin-bottom: 15px;
}
.hidden {
display: none;
}
@ -187,10 +192,6 @@ html {
background-color: rgba(2, 182, 163, 0.5);
}
.ascribe-detail-header {
margin-top: 2em;
}
.ascribe-detail-title {
font-size: 2em;
margin-bottom: -0.2em;