Merge with master

This commit is contained in:
Brett Sun 2016-01-11 17:52:32 +01:00
parent d4bb00168c
commit 8531b77633
34 changed files with 322 additions and 293 deletions

View File

@ -82,6 +82,10 @@ let AccordionListItemTableEditions = React.createClass({
editionList.orderBy, editionList.orderAsc, editionList.filterBy);
},
render() {
const { className, parentId } = this.props;
const { editionList, isEditionListOpenForPieceId, showMoreLoading } = this.state;
const editionsForPiece = editionList[parentId];
let selectedEditionsCount = 0;
let allEditionsCount = 0;
let orderBy;
@ -89,95 +93,97 @@ let AccordionListItemTableEditions = React.createClass({
let show = false;
let showExpandOption = false;
let editionsForPiece = this.state.editionList[this.props.parentId];
let loadingSpinner = <AscribeSpinner size="sm" color="dark-blue" />;
// here we need to check if all editions of a specific
// piece are already defined. Otherwise .length will throw an error and we'll not
// be notified about it.
if(editionsForPiece) {
if (editionsForPiece) {
selectedEditionsCount = this.filterSelectedEditions().length;
allEditionsCount = editionsForPiece.length;
orderBy = editionsForPiece.orderBy;
orderAsc = editionsForPiece.orderAsc;
}
if(this.props.parentId in this.state.isEditionListOpenForPieceId) {
show = this.state.isEditionListOpenForPieceId[this.props.parentId].show;
if (parentId in isEditionListOpenForPieceId) {
show = isEditionListOpenForPieceId[parentId].show;
}
// if the number of editions in the array is equal to the maximum number of editions,
// then the "Show me more" dialog should be hidden from the user's view
if(editionsForPiece && editionsForPiece.count > editionsForPiece.length) {
if (editionsForPiece && editionsForPiece.count > editionsForPiece.length) {
showExpandOption = true;
}
let transition = new TransitionModel('editions', 'editionId', 'bitcoin_id', (e) => e.stopPropagation() );
const transition = new TransitionModel({
to: 'editions',
queryKey: 'editionId',
valueKey: 'bitcoin_id',
callback: (e) => e.stopPropagation()
});
let columnList = [
new ColumnModel(
(item) => {
const columnList = [
new ColumnModel({
transformFn: (item) => {
return {
'editionId': item.id,
'pieceId': this.props.parentId,
'pieceId': parentId,
'selectItem': this.selectItem,
'selected': item.selected
}; },
'',
};
},
displayElement: (
<AccordionListItemTableSelectAllEditionsCheckbox
onChange={this.toggleAllItems}
numOfSelectedEditions={selectedEditionsCount}
numOfAllEditions={allEditionsCount}/>,
TableItemCheckbox,
1,
false
),
new ColumnModel(
(item) => {
numOfAllEditions={allEditionsCount}/>
),
displayType: TableItemCheckbox,
rowWidth: 1
}),
new ColumnModel({
transition,
transformFn: (item) => {
return {
'content': item.edition_number + ' ' + getLangText('of') + ' ' + item.num_editions
}; },
'edition_number',
getLangText('Edition'),
TableItemText,
1,
false,
transition
),
new ColumnModel(
(item) => {
};
},
columnName: 'edition_number',
displayElement: getLangText('Edition'),
displayType: TableItemText,
rowWidth: 1
}),
new ColumnModel({
transition,
transformFn: (item) => {
return {
'content': item.bitcoin_id
}; },
'bitcoin_id',
getLangText('ID'),
TableItemText,
5,
false,
transition,
'hidden-xs visible-sm visible-md visible-lg'
),
new ColumnModel(
(item) => {
let content = item.acl;
};
},
columnName: 'bitcoin_id',
displayElement: getLangText('ID'),
displayType: TableItemText,
rowWidth: 5,
className: 'hidden-xs visible-sm visible-md visible-lg'
}),
new ColumnModel({
transition,
transformFn: (item) => {
return {
'content': content,
'content': item.acl,
'notifications': item.notifications
}; },
'acl',
getLangText('Actions'),
TableItemAclFiltered,
4,
false,
transition
)
};
},
columnName: 'acl',
displayElement: getLangText('Actions'),
displayType: TableItemAclFiltered,
rowWidth: 4
})
];
if(show && editionsForPiece && editionsForPiece.length > 0) {
if (show && editionsForPiece && editionsForPiece.length) {
return (
<div className={this.props.className}>
<div className={className}>
<AccordionListItemTable
parentId={this.props.parentId}
parentId={parentId}
itemList={editionsForPiece}
columnList={columnList}
show={show}
@ -188,7 +194,14 @@ let AccordionListItemTableEditions = React.createClass({
<AccordionListItemTableToggle
className="ascribe-accordion-list-table-toggle"
onClick={this.loadFurtherEditions}
message={show && showExpandOption ? <span>{this.state.showMoreLoading ? loadingSpinner : <span className="glyphicon glyphicon-option-horizontal" aria-hidden="true" style={{top: 3}} />} Show me more</span> : null} />
message={show && showExpandOption ? (
<span>
{showMoreLoading ? <AscribeSpinner size="sm" color="dark-blue" />
: <span className="glyphicon glyphicon-option-horizontal" aria-hidden="true" style={{top: 3}} />}
{getLangText('Show me more')}
</span>
) : null
} />
</div>
);
} else {

View File

@ -1,7 +1,7 @@
'use strict';
import React from 'react';
import { Link, History } from 'react-router';
import { Link } from 'react-router';
import Moment from 'moment';
import Row from 'react-bootstrap/lib/Row';
@ -44,8 +44,6 @@ let Edition = React.createClass({
loadEdition: React.PropTypes.func
},
mixins: [History],
getDefaultProps() {
return {
furtherDetailsType: FurtherDetails
@ -53,98 +51,103 @@ let Edition = React.createClass({
},
render() {
let FurtherDetailsType = this.props.furtherDetailsType;
const {
actionPanelButtonListType,
coaError,
currentUser,
edition,
furtherDetailsType: FurtherDetailsType,
loadEdition } = this.props;
return (
<Row>
<Col md={6} className="ascribe-print-col-left">
<MediaContainer
content={this.props.edition}/>
content={edition}
currentUser={currentUser} />
</Col>
<Col md={6} className="ascribe-edition-details ascribe-print-col-right">
<div className="ascribe-detail-header">
<hr className="hidden-print" style={{marginTop: 0}}/>
<h1 className="ascribe-detail-title">{this.props.edition.title}</h1>
<DetailProperty label="BY" value={this.props.edition.artist_name} />
<DetailProperty label="DATE" value={Moment(this.props.edition.date_created, 'YYYY-MM-DD').year()} />
<h1 className="ascribe-detail-title">{edition.title}</h1>
<DetailProperty label="BY" value={edition.artist_name} />
<DetailProperty label="DATE" value={Moment(edition.date_created, 'YYYY-MM-DD').year()} />
<hr/>
</div>
<EditionSummary
actionPanelButtonListType={this.props.actionPanelButtonListType}
edition={this.props.edition}
currentUser={this.props.currentUser}
handleSuccess={this.props.loadEdition}/>
actionPanelButtonListType={actionPanelButtonListType}
edition={edition}
currentUser={currentUser}
handleSuccess={loadEdition}/>
<CollapsibleParagraph
title={getLangText('Certificate of Authenticity')}
show={this.props.edition.acl.acl_coa === true}>
show={edition.acl.acl_coa === true}>
<CoaDetails
coa={this.props.edition.coa}
coaError={this.props.coaError}
editionId={this.props.edition.bitcoin_id}/>
coa={edition.coa}
coaError={coaError}
editionId={edition.bitcoin_id}/>
</CollapsibleParagraph>
<CollapsibleParagraph
title={getLangText('Provenance/Ownership History')}
show={this.props.edition.ownership_history && this.props.edition.ownership_history.length > 0}>
show={edition.ownership_history && edition.ownership_history.length > 0}>
<HistoryIterator
history={this.props.edition.ownership_history} />
history={edition.ownership_history} />
</CollapsibleParagraph>
<CollapsibleParagraph
title={getLangText('Consignment History')}
show={this.props.edition.consign_history && this.props.edition.consign_history.length > 0}>
show={edition.consign_history && edition.consign_history.length > 0}>
<HistoryIterator
history={this.props.edition.consign_history} />
history={edition.consign_history} />
</CollapsibleParagraph>
<CollapsibleParagraph
title={getLangText('Loan History')}
show={this.props.edition.loan_history && this.props.edition.loan_history.length > 0}>
show={edition.loan_history && edition.loan_history.length > 0}>
<HistoryIterator
history={this.props.edition.loan_history} />
history={edition.loan_history} />
</CollapsibleParagraph>
<CollapsibleParagraph
title="Notes"
show={!!(this.props.currentUser.username
|| this.props.edition.acl.acl_edit
|| this.props.edition.public_note)}>
show={!!(currentUser.username || edition.acl.acl_edit || edition.public_note)}>
<Note
id={() => {return {'bitcoin_id': this.props.edition.bitcoin_id}; }}
id={() => {return {'bitcoin_id': edition.bitcoin_id}; }}
label={getLangText('Personal note (private)')}
defaultValue={this.props.edition.private_note ? this.props.edition.private_note : null}
defaultValue={edition.private_note ? edition.private_note : null}
placeholder={getLangText('Enter your comments ...')}
editable={true}
successMessage={getLangText('Private note saved')}
url={ApiUrls.note_private_edition}
currentUser={this.props.currentUser}/>
currentUser={currentUser}/>
<Note
id={() => {return {'bitcoin_id': this.props.edition.bitcoin_id}; }}
id={() => {return {'bitcoin_id': edition.bitcoin_id}; }}
label={getLangText('Personal note (public)')}
defaultValue={this.props.edition.public_note ? this.props.edition.public_note : null}
defaultValue={edition.public_note ? edition.public_note : null}
placeholder={getLangText('Enter your comments ...')}
editable={!!this.props.edition.acl.acl_edit}
show={!!this.props.edition.public_note || !!this.props.edition.acl.acl_edit}
editable={!!edition.acl.acl_edit}
show={!!edition.public_note || !!edition.acl.acl_edit}
successMessage={getLangText('Public edition note saved')}
url={ApiUrls.note_public_edition}
currentUser={this.props.currentUser}/>
currentUser={currentUser}/>
</CollapsibleParagraph>
<CollapsibleParagraph
title={getLangText('Further Details')}
show={this.props.edition.acl.acl_edit
|| Object.keys(this.props.edition.extra_data).length > 0
|| this.props.edition.other_data.length > 0}>
show={edition.acl.acl_edit ||
Object.keys(edition.extra_data).length > 0 ||
edition.other_data.length > 0}>
<FurtherDetailsType
editable={this.props.edition.acl.acl_edit}
pieceId={this.props.edition.parent}
extraData={this.props.edition.extra_data}
otherData={this.props.edition.other_data}
handleSuccess={this.props.loadEdition} />
editable={edition.acl.acl_edit}
pieceId={edition.parent}
extraData={edition.extra_data}
otherData={edition.other_data}
handleSuccess={loadEdition} />
</CollapsibleParagraph>
<CollapsibleParagraph
title={getLangText('SPOOL Details')}>
<SpoolDetails
edition={this.props.edition} />
edition={edition} />
</CollapsibleParagraph>
</Col>
</Row>
@ -221,7 +224,11 @@ let EditionSummary = React.createClass({
let CoaDetails = React.createClass({
propTypes: {
editionId: React.PropTypes.string,
coa: React.PropTypes.object,
coa: React.PropTypes.oneOfType([
React.PropTypes.number,
React.PropTypes.string,
React.PropTypes.object
]),
coaError: React.PropTypes.object
},

View File

@ -76,7 +76,7 @@ let EditionActionPanel = React.createClass({
const notification = new GlobalNotificationModel(response.notification, 'success');
GlobalNotificationActions.appendGlobalNotification(notification);
this.history.pushState(null, '/collection');
this.history.push('/collection');
},
refreshCollection() {
@ -85,7 +85,7 @@ let EditionActionPanel = React.createClass({
EditionListActions.refreshEditionList({pieceId: this.props.edition.parent});
},
handleSuccess(response){
handleSuccess(response) {
this.refreshCollection();
this.props.handleSuccess();
if (response){
@ -94,7 +94,7 @@ let EditionActionPanel = React.createClass({
}
},
render(){
render() {
const {
actionPanelButtonListType: ActionPanelButtonListType,
edition,

View File

@ -14,10 +14,6 @@ import CollapsibleButton from './../ascribe_collapsible/collapsible_button';
import AclProxy from '../acl_proxy';
import UserActions from '../../actions/user_actions';
import UserStore from '../../stores/user_store';
import { mergeOptions } from '../../utils/general_utils.js';
import { getLangText } from '../../utils/lang_utils.js';
const EMBED_IFRAME_HEIGHT = {
@ -28,25 +24,22 @@ const EMBED_IFRAME_HEIGHT = {
let MediaContainer = React.createClass({
propTypes: {
content: React.PropTypes.object,
currentUser: React.PropTypes.object,
refreshObject: React.PropTypes.func
},
getInitialState() {
return mergeOptions(
UserStore.getState(),
{
timerId: null
});
return {
timerId: null
};
},
componentDidMount() {
UserStore.listen(this.onChange);
UserActions.fetchCurrentUser();
if (!this.props.content.digital_work) {
return;
}
let isEncoding = this.props.content.digital_work.isEncoding;
const isEncoding = this.props.content.digital_work.isEncoding;
if (this.props.content.digital_work.mime === 'video' && typeof isEncoding === 'number' && isEncoding !== 100 && !this.state.timerId) {
let timerId = window.setInterval(this.props.refreshObject, 10000);
this.setState({timerId: timerId});
@ -60,22 +53,16 @@ let MediaContainer = React.createClass({
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
window.clearInterval(this.state.timerId);
},
onChange(state) {
this.setState(state);
},
render() {
const { content } = this.props;
const { content, currentUser } = this.props;
// Pieces and editions are joined to the user by a foreign key in the database, so
// the information in content will be updated if a user updates their username.
// We also force uniqueness of usernames, so this check is safe to dtermine if the
// content was registered by the current user.
const didUserRegisterContent = this.state.currentUser && (this.state.currentUser.username === content.user_registered);
const didUserRegisterContent = currentUser && (currentUser.username === content.user_registered);
let thumbnail = content.thumbnail.thumbnail_sizes && content.thumbnail.thumbnail_sizes['600x600'] ?
content.thumbnail.thumbnail_sizes['600x600'] : content.thumbnail.url_safe;
@ -94,8 +81,11 @@ let MediaContainer = React.createClass({
embed = (
<CollapsibleButton
button={
<Button bsSize="xsmall" className="ascribe-margin-1px" disabled={isEmbedDisabled ? '"disabled"' : ''}>
Embed
<Button
bsSize="xsmall"
className="ascribe-margin-1px"
disabled={isEmbedDisabled}>
{getLangText('Embed')}
</Button>
}
panel={
@ -125,8 +115,12 @@ let MediaContainer = React.createClass({
show={['video', 'audio', 'image'].indexOf(mimetype) === -1 || content.acl.acl_download}
aclObject={content.acl}
aclName="acl_download">
<Button bsSize="xsmall" className="ascribe-margin-1px" href={this.props.content.digital_work.url} target="_blank">
Download .{mimetype} <Glyphicon glyph="cloud-download"/>
<Button
bsSize="xsmall"
className="ascribe-margin-1px"
href={content.digital_work.url}
target="_blank">
{getLangText('Download')} .{mimetype} <Glyphicon glyph="cloud-download"/>
</Button>
</AclProxy>
{embed}

View File

@ -16,10 +16,10 @@ import MediaContainer from './media_container';
let Piece = React.createClass({
propTypes: {
piece: React.PropTypes.object,
currentUser: React.PropTypes.object,
header: React.PropTypes.object,
subheader: React.PropTypes.object,
buttons: React.PropTypes.object,
loadPiece: React.PropTypes.func,
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
@ -32,20 +32,22 @@ let Piece = React.createClass({
},
render() {
const { buttons, children, currentUser, header, piece, subheader } = this.props;
return (
<Row>
<Col md={6} className="ascribe-print-col-left">
<MediaContainer
refreshObject={this.updateObject}
content={this.props.piece}/>
content={piece}
currentUser={currentUser}
refreshObject={this.updateObject} />
</Col>
<Col md={6} className="ascribe-edition-details ascribe-print-col-right">
{this.props.header}
{this.props.subheader}
{this.props.buttons}
{this.props.children}
{header}
{subheader}
{buttons}
{children}
</Col>
</Row>
);

View File

@ -162,7 +162,7 @@ let PieceContainer = React.createClass({
let notification = new GlobalNotificationModel(response.notification, 'success');
GlobalNotificationActions.appendGlobalNotification(notification);
this.history.pushState(null, '/collection');
this.history.push('/collection');
},
getCreateEditionsDialog() {
@ -254,76 +254,76 @@ let PieceContainer = React.createClass({
},
render() {
if (this.state.piece && this.state.piece.id) {
let FurtherDetailsType = this.props.furtherDetailsType;
setDocumentTitle([this.state.piece.artist_name, this.state.piece.title].join(', '));
const { currentUser, furtherDetailsType: FurtherDetailsType } = this.props;
const { piece } = this.state;
if (piece && piece.id) {
setDocumentTitle([piece.artist_name, piece.title].join(', '));
return (
<Piece
piece={this.state.piece}
loadPiece={this.loadPiece}
piece={piece}
currentUser={currentUser}
header={
<div className="ascribe-detail-header">
<hr className="hidden-print" style={{marginTop: 0}}/>
<h1 className="ascribe-detail-title">{this.state.piece.title}</h1>
<DetailProperty label="BY" value={this.state.piece.artist_name} />
<DetailProperty label="DATE" value={Moment(this.state.piece.date_created, 'YYYY-MM-DD').year() } />
{this.state.piece.num_editions > 0 ? <DetailProperty label="EDITIONS" value={ this.state.piece.num_editions } /> : null}
<hr className="hidden-print" style={{marginTop: 0}} />
<h1 className="ascribe-detail-title">{piece.title}</h1>
<DetailProperty label="BY" value={piece.artist_name} />
<DetailProperty label="DATE" value={Moment(piece.date_created, 'YYYY-MM-DD').year() } />
{piece.num_editions > 0 ? <DetailProperty label="EDITIONS" value={ piece.num_editions } /> : null}
<hr/>
</div>
}
subheader={
<div className="ascribe-detail-header">
<DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } />
<DetailProperty label={getLangText('ID')} value={ this.state.piece.bitcoin_id } ellipsis={true} />
<LicenseDetail license={this.state.piece.license_type} />
<DetailProperty label={getLangText('REGISTREE')} value={ piece.user_registered } />
<DetailProperty label={getLangText('ID')} value={ piece.bitcoin_id } ellipsis={true} />
<LicenseDetail license={piece.license_type} />
</div>
}
buttons={this.getActions()}>
{this.getCreateEditionsDialog()}
<CollapsibleParagraph
title={getLangText('Loan History')}
show={this.state.piece.loan_history && this.state.piece.loan_history.length > 0}>
show={piece.loan_history && piece.loan_history.length > 0}>
<HistoryIterator
history={this.state.piece.loan_history} />
history={piece.loan_history} />
</CollapsibleParagraph>
<CollapsibleParagraph
title={getLangText('Notes')}
show={!!(this.props.currentUser.username
|| this.state.piece.acl.acl_edit
|| this.state.piece.public_note)}>
show={!!(currentUser.username || piece.acl.acl_edit || piece.public_note)}>
<Note
id={this.getId}
label={getLangText('Personal note (private)')}
defaultValue={this.state.piece.private_note || null}
show = {!!this.props.currentUser.username}
defaultValue={piece.private_note || null}
show = {!!currentUser.username}
placeholder={getLangText('Enter your comments ...')}
editable={true}
successMessage={getLangText('Private note saved')}
url={ApiUrls.note_private_piece}
currentUser={this.props.currentUser}/>
currentUser={currentUser} />
<Note
id={this.getId}
label={getLangText('Personal note (public)')}
defaultValue={this.state.piece.public_note || null}
defaultValue={piece.public_note || null}
placeholder={getLangText('Enter your comments ...')}
editable={!!this.state.piece.acl.acl_edit}
show={!!(this.state.piece.public_note || this.state.piece.acl.acl_edit)}
editable={!!piece.acl.acl_edit}
show={!!(piece.public_note || piece.acl.acl_edit)}
successMessage={getLangText('Public note saved')}
url={ApiUrls.note_public_piece}
currentUser={this.props.currentUser}/>
currentUser={currentUser} />
</CollapsibleParagraph>
<CollapsibleParagraph
title={getLangText('Further Details')}
show={this.state.piece.acl.acl_edit
|| Object.keys(this.state.piece.extra_data).length > 0
|| this.state.piece.other_data.length > 0}
show={piece.acl.acl_edit
|| Object.keys(piece.extra_data).length > 0
|| piece.other_data.length > 0}
defaultExpanded={true}>
<FurtherDetailsType
editable={this.state.piece.acl.acl_edit}
pieceId={this.state.piece.id}
extraData={this.state.piece.extra_data}
otherData={this.state.piece.other_data}
editable={piece.acl.acl_edit}
pieceId={piece.id}
extraData={piece.extra_data}
otherData={piece.other_data}
handleSuccess={this.loadPiece} />
</CollapsibleParagraph>

View File

@ -58,7 +58,7 @@ let SendContractAgreementForm = React.createClass({
notification = new GlobalNotificationModel(notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
this.history.pushState(null, '/collection');
this.history.push('/collection');
},
getFormData() {

View File

@ -184,7 +184,7 @@ let Video = React.createClass({
);
} else {
return (
<Image src={this.props.preview} />
<Image preview={this.props.preview} />
);
}
}

View File

@ -91,13 +91,13 @@ let PieceListToolbarFilterWidget = React.createClass({
label also iterate over its items, to get all filterable options */}
{this.props.filterParams.map(({ label, items }, i) => {
return (
<div>
<li
style={{'textAlign': 'center'}}
key={i}>
<div key={label}>
<li style={{'textAlign': 'center'}}>
<em>{label}:</em>
</li>
{items.map((param, j) => {
{items.map((paramItem) => {
let itemLabel;
let param;
// As can be seen in the PropTypes, a param can either
// be a string or an object of the shape:
@ -108,22 +108,22 @@ let PieceListToolbarFilterWidget = React.createClass({
// }
//
// This is why we need to distinguish between both here.
if (typeof param !== 'string') {
label = param.label;
param = param.key;
if (typeof paramItem !== 'string') {
param = paramItem.key;
itemLabel = paramItem.label;
} else {
param = param;
label = param.split('acl_')[1].replace(/_/g, ' ');
param = paramItem;
itemLabel = paramItem.split('acl_')[1].replace(/_/g, ' ');
}
return (
<li
key={j}
key={itemLabel}
onClick={this.filterBy(param)}
className="filter-widget-item">
<div className="checkbox-line">
<span>
{getLangText(label)}
{getLangText(itemLabel)}
</span>
<input
readOnly

View File

@ -63,22 +63,20 @@ let PieceListToolbarOrderWidget = React.createClass({
</li>
{this.props.orderParams.map((param) => {
return (
<div>
<li
key={param}
onClick={this.orderBy(param)}
className="filter-widget-item">
<div className="checkbox-line">
<span>
{getLangText(param.replace('_', ' '))}
</span>
<input
readOnly
type="radio"
checked={param.indexOf(this.props.orderBy) > -1} />
</div>
</li>
</div>
<li
key={param}
onClick={this.orderBy(param)}
className="filter-widget-item">
<div className="checkbox-line">
<span>
{getLangText(param.replace('_', ' '))}
</span>
<input
readOnly
type="radio"
checked={param.indexOf(this.props.orderBy) > -1} />
</div>
</li>
);
})}
</DropdownButton>
@ -89,4 +87,4 @@ let PieceListToolbarOrderWidget = React.createClass({
}
});
export default PieceListToolbarOrderWidget;
export default PieceListToolbarOrderWidget;

View File

@ -20,8 +20,8 @@ const WHEN_ENUM = ['loggedIn', 'loggedOut'];
export function AuthRedirect({to, when}) {
// validate `when`, must be contained in `WHEN_ENUM`.
// Throw an error otherwise.
if(WHEN_ENUM.indexOf(when) === -1) {
let whenValues = WHEN_ENUM.join(', ');
if (WHEN_ENUM.indexOf(when) === -1) {
const whenValues = WHEN_ENUM.join(', ');
throw new Error(`"when" must be one of: [${whenValues}] got "${when}" instead`);
}
@ -39,14 +39,14 @@ export function AuthRedirect({to, when}) {
// and redirect if `true`.
if (exprToValidate) {
window.setTimeout(() => history.replaceState(null, to, query));
window.setTimeout(() => history.replace({ query, pathname: to }));
return true;
// Otherwise there can also be the case that the backend
// wants to redirect the user to a specific route when the user is logged out already
} else if (!exprToValidate && when === 'loggedIn' && redirect) {
delete query.redirect;
window.setTimeout(() => history.replaceState(null, '/' + redirect, query));
window.setTimeout(() => history.replace({ query, pathname: '/' + redirect }));
return true;
} else if (!exprToValidate && when === 'loggedOut' && redirectAuthenticated) {

View File

@ -57,21 +57,21 @@ const SlidesContainer = React.createClass({
// When the start_from parameter is used, this.setSlideNum can not simply be used anymore.
nextSlide(additionalQueryParams) {
const slideNum = parseInt(this.props.location.query.slide_num, 10) || 0;
let nextSlide = slideNum + 1;
this.setSlideNum(nextSlide, additionalQueryParams);
this.setSlideNum(slideNum + 1, additionalQueryParams);
},
setSlideNum(nextSlideNum, additionalQueryParams = {}) {
let queryParams = Object.assign(this.props.location.query, additionalQueryParams);
queryParams.slide_num = nextSlideNum;
this.history.pushState(null, this.props.location.pathname, queryParams);
const { location: { pathname } } = this.props;
const query = Object.assign({}, this.props.location.query, additionalQueryParams, { slide_num: nextSlideNum });
this.history.push({ pathname, query });
},
// breadcrumbs are defined as attributes of the slides.
// To extract them we have to read the DOM element's attributes
extractBreadcrumbs() {
const startFrom = parseInt(this.props.location.query.start_from, 10) || -1;
let breadcrumbs = [];
const breadcrumbs = [];
React.Children.map(this.props.children, (child, i) => {
if(child && i >= startFrom && child.props['data-slide-title']) {
@ -179,4 +179,4 @@ const SlidesContainer = React.createClass({
}
});
export default SlidesContainer;
export default SlidesContainer;

View File

@ -2,15 +2,15 @@
export class ColumnModel {
// ToDo: Add validation for all passed-in parameters
constructor(transformFn, columnName, displayName, displayType, rowWidth, canBeOrdered, transition, className) {
constructor({ transformFn, columnName = '', displayElement, displayType, rowWidth, canBeOrdered, transition, className = '' }) {
this.transformFn = transformFn;
this.columnName = columnName;
this.displayName = displayName;
this.displayElement = displayElement;
this.displayType = displayType;
this.rowWidth = rowWidth;
this.canBeOrdered = canBeOrdered;
this.transition = transition;
this.className = className ? className : '';
this.className = className;
}
}
@ -28,7 +28,7 @@ export class ColumnModel {
* our selfes, using this TransitionModel.
*/
export class TransitionModel {
constructor(to, queryKey, valueKey, callback) {
constructor({ to, queryKey, valueKey, callback }) {
this.to = to;
this.queryKey = queryKey;
this.valueKey = valueKey;
@ -38,4 +38,4 @@ export class TransitionModel {
toReactRouterLink(queryValue) {
return '/' + this.to + '/' + queryValue;
}
}
}

View File

@ -1,5 +1,5 @@
'use strict';
'use strict';
import React from 'react';
import TableHeaderItem from './table_header_item';
@ -29,7 +29,7 @@ let TableHeader = React.createClass({
<TableHeaderItem
className={column.className}
key={i}
displayName={column.displayName}
displayElement={column.displayElement}
columnName={columnName}
canBeOrdered={canBeOrdered}
orderAsc={this.props.orderAsc}

View File

@ -7,7 +7,7 @@ import TableHeaderItemCarret from './table_header_item_carret';
let TableHeaderItem = React.createClass({
propTypes: {
displayName: React.PropTypes.oneOfType([
displayElement: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.element
]).isRequired,
@ -24,29 +24,31 @@ let TableHeaderItem = React.createClass({
},
render() {
if(this.props.canBeOrdered && this.props.changeOrder && this.props.orderAsc != null && this.props.orderBy) {
if(this.props.columnName === this.props.orderBy) {
const { canBeOrdered, changeOrder, className, columnName, displayElement, orderAsc, orderBy } = this.props;
if (canBeOrdered && changeOrder && orderAsc != null && orderBy) {
if (columnName === orderBy) {
return (
<th
className={'ascribe-table-header-column ' + this.props.className}
className={'ascribe-table-header-column ' + className}
onClick={this.changeOrder}>
<span>{this.props.displayName} <TableHeaderItemCarret orderAsc={this.props.orderAsc} /></span>
<span>{displayElement} <TableHeaderItemCarret orderAsc={orderAsc} /></span>
</th>
);
} else {
return (
<th
className={'ascribe-table-header-column ' + this.props.className}
className={'ascribe-table-header-column ' + className}
onClick={this.changeOrder}>
<span>{this.props.displayName}</span>
<span>{displayElement}</span>
</th>
);
}
} else {
return (
<th className={'ascribe-table-header-column ' + this.props.className}>
<th className={'ascribe-table-header-column ' + className}>
<span>
{this.props.displayName}
{displayElement}
</span>
</th>
);

View File

@ -3,15 +3,15 @@
import React from 'react';
let TableItemAclFiltered = React.createClass({
const TableItemAclFiltered = React.createClass({
propTypes: {
content: React.PropTypes.object,
notifications: React.PropTypes.string
notifications: React.PropTypes.array
},
render() {
var availableAcls = ['acl_consign', 'acl_loan', 'acl_transfer', 'acl_view', 'acl_share', 'acl_unshare', 'acl_delete'];
if (this.props.notifications && this.props.notifications.length > 0){
const availableAcls = ['acl_consign', 'acl_loan', 'acl_transfer', 'acl_view', 'acl_share', 'acl_unshare', 'acl_delete'];
if (this.props.notifications && this.props.notifications.length) {
return (
<span>
{this.props.notifications[0].action_str}
@ -19,15 +19,14 @@ let TableItemAclFiltered = React.createClass({
);
}
let filteredAcls = Object.keys(this.props.content).filter((key) => {
return availableAcls.indexOf(key) > -1 && this.props.content[key];
});
filteredAcls = filteredAcls.map((acl) => acl.split('acl_')[1]);
const filteredAcls = Object.keys(this.props.content)
.filter((key) => availableAcls.indexOf(key) > -1 && this.props.content[key])
.map((acl) => acl.split('acl_')[1])
.join('/');
return (
<span>
{filteredAcls.join('/')}
{filteredAcls}
</span>
);
}

View File

@ -135,7 +135,8 @@ let PasswordResetForm = React.createClass({
},
handleSuccess() {
this.history.pushState(null, '/collection');
this.history.push('/collection');
const notification = new GlobalNotificationModel(getLangText('password successfully updated'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
},

View File

@ -121,13 +121,13 @@ let PieceList = React.createClass({
},
componentDidUpdate() {
const { redirectTo, shouldRedirect } = this.props;
const { location: { query }, redirectTo, shouldRedirect } = this.props;
const { unfilteredPieceListCount } = this.state;
if (redirectTo && unfilteredPieceListCount === 0 &&
(typeof shouldRedirect === 'function' && shouldRedirect(unfilteredPieceListCount))) {
// FIXME: hack to redirect out of the dispatch cycle
window.setTimeout(() => this.history.pushState(null, this.props.redirectTo, this.props.location.query), 0);
window.setTimeout(() => this.history.push({ query, pathname: redirectTo }), 0);
}
},
@ -180,15 +180,16 @@ let PieceList = React.createClass({
}
},
searchFor(searchTerm) {
this.loadPieceList({
page: 1,
search: searchTerm
});
this.history.pushState(null, this.props.location.pathname, {page: 1});
searchFor(search) {
const { location: { pathname } } = this.props;
this.loadPieceList({ search, page: 1 });
this.history.push({ pathname, query: { page: 1 } });
},
applyFilterBy(filterBy){
applyFilterBy(filterBy) {
const { location: { pathname } } = this.props;
this.setState({
isFilterDirty: true
});
@ -212,7 +213,7 @@ let PieceList = React.createClass({
// we have to redirect the user always to page one as it could be that there is no page two
// for filtered pieces
this.history.pushState(null, this.props.location.pathname, {page: 1});
this.history.push({ pathname, query: { page: 1 } });
},
applyOrderBy(orderBy) {

View File

@ -73,7 +73,7 @@ let RegisterPiece = React.createClass( {
this.state.filterBy
);
this.history.pushState(null, `/pieces/${response.piece.id}`);
this.history.push(`/pieces/${response.piece.id}`);
},
getSpecifyEditions() {

View File

@ -105,7 +105,7 @@ const PRRegisterPieceForm = React.createClass({
GlobalNotificationActions.appendGlobalNotification(notificationMessage);
});
})
.then(() => this.history.pushState(null, `/pieces/${this.state.piece.id}`))
.then(() => this.history.push(`/pieces/${this.state.piece.id}`))
.catch((err) => {
const errMessage = (getErrorNotificationMessage(err) || getLangText("Oops! We weren't able to send your submission.")) +
getLangText(' Please contact support@ascribe.io');

View File

@ -11,6 +11,7 @@ import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
import PrizeActions from '../../simple_prize/actions/prize_actions';
import PrizeStore from '../../simple_prize/stores/prize_store';
import { omitFromObject } from '../../../../../utils/general_utils';
import { getLangText } from '../../../../../utils/lang_utils';
@ -34,12 +35,15 @@ const PRLanding = React.createClass({
const { location } = this.props;
PrizeStore.listen(this.onChange);
UserActions.fetchCurrentUser();
PrizeActions.fetchPrize();
if (location.query.redirect) {
let queryCopy = JSON.parse(JSON.stringify(location.query));
delete queryCopy.redirect;
window.setTimeout(() => this.history.replaceState(null, `/${location.query.redirect}`, queryCopy));
window.setTimeout(() => this.history.replace({
pathname: `/${location.query.redirect}`,
query: omitFromObject(location.query, ['redirect'])
}));
}
},

View File

@ -32,7 +32,7 @@ const PRRegisterPiece = React.createClass({
if (currentUser && currentUser.email) {
const submittedPieceId = getCookie(currentUser.email);
if (submittedPieceId) {
this.history.pushState(null, `/pieces/${submittedPieceId}`);
this.history.push(`/pieces/${submittedPieceId}`);
}
}
},

View File

@ -17,7 +17,7 @@ export function AuthPrizeRoleRedirect({ to, when }) {
.reduce((a, b) => a || b);
if (exprToValidate) {
window.setTimeout(() => history.replaceState(null, to, query));
window.setTimeout(() => history.replace({ query, pathname: to }));
return true;
} else {
return false;

View File

@ -33,53 +33,61 @@ let WalletPieceContainer = React.createClass({
},
render() {
if (this.props.piece && this.props.piece.id) {
const {
children,
currentUser,
handleDeleteSuccess,
loadPiece,
piece,
submitButtonType } = this.props;
if (piece && piece.id) {
return (
<Piece
piece={this.props.piece}
loadPiece={this.props.loadPiece}
piece={piece}
currentUser={currentUser}
header={
<div className="ascribe-detail-header">
<hr style={{marginTop: 0}}/>
<h1 className="ascribe-detail-title">{this.props.piece.title}</h1>
<DetailProperty label="BY" value={this.props.piece.artist_name} />
<DetailProperty label="DATE" value={Moment(this.props.piece.date_created, 'YYYY-MM-DD').year()} />
<h1 className="ascribe-detail-title">{piece.title}</h1>
<DetailProperty label="BY" value={piece.artist_name} />
<DetailProperty label="DATE" value={Moment(piece.date_created, 'YYYY-MM-DD').year()} />
<hr/>
</div>
}
subheader={
<div className="ascribe-detail-header">
<DetailProperty label={getLangText('REGISTREE')} value={ this.props.piece.user_registered } />
<DetailProperty label={getLangText('ID')} value={ this.props.piece.bitcoin_id } ellipsis={true} />
<DetailProperty label={getLangText('REGISTREE')} value={ piece.user_registered } />
<DetailProperty label={getLangText('ID')} value={ piece.bitcoin_id } ellipsis={true} />
<hr/>
</div>
}>
<WalletActionPanel
piece={this.props.piece}
currentUser={this.props.currentUser}
loadPiece={this.props.loadPiece}
handleDeleteSuccess={this.props.handleDeleteSuccess}
submitButtonType={this.props.submitButtonType}/>
piece={piece}
currentUser={currentUser}
loadPiece={loadPiece}
handleDeleteSuccess={handleDeleteSuccess}
submitButtonType={submitButtonType}/>
<CollapsibleParagraph
title={getLangText('Loan History')}
show={this.props.piece.loan_history && this.props.piece.loan_history.length > 0}>
show={piece.loan_history && piece.loan_history.length > 0}>
<HistoryIterator
history={this.props.piece.loan_history}/>
history={piece.loan_history}/>
</CollapsibleParagraph>
<CollapsibleParagraph
title={getLangText('Notes')}
show={!!(this.props.currentUser.username || this.props.piece.public_note)}>
show={!!(currentUser.username || piece.public_note)}>
<Note
id={() => {return {'id': this.props.piece.id}; }}
id={() => {return {'id': piece.id}; }}
label={getLangText('Personal note (private)')}
defaultValue={this.props.piece.private_note || null}
defaultValue={piece.private_note || null}
placeholder={getLangText('Enter your comments ...')}
editable={true}
successMessage={getLangText('Private note saved')}
url={ApiUrls.note_private_piece}
currentUser={this.props.currentUser}/>
currentUser={currentUser}/>
</CollapsibleParagraph>
{this.props.children}
{children}
</Piece>
);
} else {

View File

@ -86,7 +86,7 @@ let CylandPieceContainer = React.createClass({
const notification = new GlobalNotificationModel(response.notification, 'success');
GlobalNotificationActions.appendGlobalNotification(notification);
this.history.pushState(null, '/collection');
this.history.push('/collection');
},
render() {

View File

@ -117,7 +117,7 @@ let CylandRegisterPiece = React.createClass({
PieceActions.fetchOne(this.state.piece.id);
this.history.pushState(null, `/pieces/${this.state.piece.id}`);
this.history.push(`/pieces/${this.state.piece.id}`);
},
// We need to increase the step to lock the forms that are already filled out

View File

@ -115,7 +115,7 @@ let IkonotvContractNotifications = React.createClass({
NotificationActions.flushContractAgreementListNotifications();
NotificationActions.fetchContractAgreementListNotifications();
this.history.pushState(null, '/collection');
this.history.push('/collection');
},
handleDeny() {
@ -129,7 +129,7 @@ let IkonotvContractNotifications = React.createClass({
const notification = new GlobalNotificationModel(getLangText('You have denied the conditions'), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
this.history.pushState(null, '/collection');
this.history.push('/collection');
},
getCopyrightAssociationForm(){

View File

@ -95,7 +95,7 @@ let IkonotvPieceContainer = React.createClass({
const notification = new GlobalNotificationModel(response.notification, 'success');
GlobalNotificationActions.appendGlobalNotification(notification);
this.history.pushState(null, '/collection');
this.history.push('/collection');
},
render() {

View File

@ -94,7 +94,7 @@ let IkonotvRegisterPiece = React.createClass({
PieceActions.updatePiece(response.piece);
}
if (!this.canSubmit()) {
this.history.pushState(null, '/collection');
this.history.push('/collection');
}
else {
this.incrementStep();
@ -125,7 +125,7 @@ let IkonotvRegisterPiece = React.createClass({
this.refreshPieceList();
PieceActions.fetchOne(this.state.piece.id);
this.history.pushState(null, `/pieces/${this.state.piece.id}`);
this.history.push(`/pieces/${this.state.piece.id}`);
},
// We need to increase the step to lock the forms that are already filled out

View File

@ -74,7 +74,7 @@ let MarketRegisterPiece = React.createClass({
handleAdditionalDataSuccess() {
this.refreshPieceList();
this.history.pushState(null, '/collection');
this.history.push('/collection');
},
// We need to increase the step to lock the forms that are already filled out

View File

@ -1,6 +1,7 @@
'use strict';
import useBasename from 'history/lib/useBasename';
import useQueries from 'history/lib/useQueries';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import AppConstants from './constants/application_constants';
@ -8,6 +9,6 @@ import AppConstants from './constants/application_constants';
// Remove the trailing slash if present
let baseUrl = AppConstants.baseUrl.replace(/\/$/, '');
export default useBasename(createBrowserHistory)({
export default useBasename(useQueries(createBrowserHistory))({
basename: baseUrl
});

View File

@ -30,7 +30,7 @@ class NotificationsHandler {
console.log('Contractagreement notifications loaded');
this.loaded = true;
history.pushState(null, '/contract_notifications');
history.push('/contract_notifications');
}
}
);

View File

@ -2,7 +2,7 @@
import camelCase from 'camelcase';
import decamelize from 'decamelize';
import qs from 'qs';
import queryString from 'query-string';
import { sanitize } from './general_utils';
@ -36,8 +36,7 @@ export function argsToQueryParams(obj) {
queryParamObj[decamelize(key)] = sanitizedObj[key];
});
// Use bracket arrayFormat as history.js and react-router use it
return '?' + qs.stringify(queryParamObj, { arrayFormat: 'brackets' });
return '?' + queryString.stringify(queryParamObj);
}
/**
@ -56,13 +55,13 @@ export function getCurrentQueryParams() {
* @return {object} Query params dictionary
*/
export function queryParamsToArgs(queryParamString) {
const qsQueryParamObj = qs.parse(queryParamString);
const queryParamObj = queryString.parse(queryParamString);
const camelCaseParamObj = {};
Object
.keys(qsQueryParamObj)
.keys(queryParamObj)
.forEach((key) => {
camelCaseParamObj[camelCase(key)] = qsQueryParamObj[key];
camelCaseParamObj[camelCase(key)] = queryParamObj[key];
});
return camelCaseParamObj;

View File

@ -67,7 +67,7 @@
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.4",
"harmonize": "^1.4.2",
"history": "^1.13.1",
"history": "1.17.0",
"invariant": "^2.1.1",
"isomorphic-fetch": "^2.0.2",
"jest-cli": "^0.4.0",
@ -76,12 +76,12 @@
"object-assign": "^2.0.0",
"opn": "^3.0.2",
"q": "^1.4.1",
"qs": "^4.0.0",
"query-string": "^3.0.0",
"raven-js": "^1.1.19",
"react": "0.13.2",
"react-bootstrap": "0.25.1",
"react-datepicker": "^0.12.0",
"react-router": "1.0.0",
"react-router": "1.0.3",
"react-router-bootstrap": "^0.19.0",
"react-star-rating": "~1.3.2",
"react-textarea-autosize": "^2.5.2",