{getLangText('Are you sure you would like to permanently delete this edition')}?
diff --git a/js/components/ascribe_forms/form_delete_piece.js b/js/components/ascribe_forms/form_delete_piece.js
index 552c38c0..4b0c9e39 100644
--- a/js/components/ascribe_forms/form_delete_piece.js
+++ b/js/components/ascribe_forms/form_delete_piece.js
@@ -5,7 +5,7 @@ import React from 'react';
import Form from '../ascribe_forms/form';
import ApiUrls from '../../constants/api_urls';
-import AppConstants from '../../constants/application_constants';
+import AscribeSpinner from '../ascribe_spinner';
import { getLangText } from '../../utils/lang_utils';
@@ -46,7 +46,9 @@ let PieceDeleteForm = React.createClass({
}
spinner={
-
+
+
+
}>
{getLangText('Are you sure you would like to permanently delete this piece')}?
diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js
index ef2fbd13..919b6118 100644
--- a/js/components/ascribe_forms/form_loan.js
+++ b/js/components/ascribe_forms/form_loan.js
@@ -15,7 +15,7 @@ import InputCheckbox from './input_checkbox';
import ContractAgreementListStore from '../../stores/contract_agreement_list_store';
import ContractAgreementListActions from '../../actions/contract_agreement_list_actions';
-import AppConstants from '../../constants/application_constants';
+import AscribeSpinner from '../ascribe_spinner';
import { mergeOptions } from '../../utils/general_utils';
import { getLangText } from '../../utils/lang_utils';
@@ -130,6 +130,9 @@ let LoanForm = React.createClass({
src={contract.blob.url_safe}
alt="pdf"
pluginspage="http://www.adobe.com/products/acrobat/readstep2.html"/>
+
+ {getLangText('Download contract')}
+
{/* We still need to send the server information that we're accepting */}
+ className="btn btn-default btn-wide">
{getLangText('Finish process')}
);
@@ -222,7 +225,9 @@ let LoanForm = React.createClass({
buttons={this.getButtons()}
spinner={
-
+
+
+
}>
{this.props.loanHeading}
diff --git a/js/components/ascribe_forms/form_login.js b/js/components/ascribe_forms/form_login.js
index 79bced6c..2f0265b6 100644
--- a/js/components/ascribe_forms/form_login.js
+++ b/js/components/ascribe_forms/form_login.js
@@ -1,7 +1,7 @@
'use strict';
import React from 'react';
-import Router from 'react-router';
+import { History } from 'react-router';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
@@ -14,6 +14,7 @@ import Property from './property';
import ApiUrls from '../../constants/api_urls';
import AppConstants from '../../constants/application_constants';
+import AscribeSpinner from '../ascribe_spinner';
import { getLangText } from '../../utils/lang_utils';
@@ -24,10 +25,10 @@ let LoginForm = React.createClass({
submitMessage: React.PropTypes.string,
redirectOnLoggedIn: React.PropTypes.bool,
redirectOnLoginSuccess: React.PropTypes.bool,
- onLogin: React.PropTypes.func
+ location: React.PropTypes.object
},
- mixins: [Router.Navigation, Router.State],
+ mixins: [History],
getDefaultProps() {
return {
@@ -52,50 +53,19 @@ let LoginForm = React.createClass({
onChange(state) {
this.setState(state);
-
- // if user is already logged in, redirect him to piece list
- if(this.state.currentUser && this.state.currentUser.email && this.props.redirectOnLoggedIn) {
- // FIXME: hack to redirect out of the dispatch cycle
- window.setTimeout(() => this.transitionTo('pieces'), 0);
- }
},
- handleSuccess(){
+ handleSuccess({ success }){
let notification = new GlobalNotificationModel('Login successful', 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
- // register_piece is waiting for a login success as login_container and it is wrapped
- // in a slides_container component.
- // The easiest way to check if the user was successfully logged in is to fetch the user
- // in the user store (which is obviously only possible if the user is logged in), since
- // register_piece is listening to the changes of the user_store.
- UserActions.fetchCurrentUser()
- .then(() => {
- if(this.props.redirectOnLoginSuccess) {
- /* Taken from http://stackoverflow.com/a/14916411 */
- /*
- We actually have to trick the Browser into showing the "save password" dialog
- as Chrome expects the login page to be reloaded after the login.
- Users on Stack Overflow claim this is a bug in chrome and should be fixed in the future.
- Until then, we redirect the HARD way, but reloading the whole page using window.location
- */
- window.location = AppConstants.baseUrl + 'collection';
- } else if(this.props.onLogin) {
- // In some instances we want to give a callback to an outer container,
- // to show that the one login action the user triggered actually went through.
- // We can not do this by listening on a store's state as it wouldn't really tell us
- // if the user did log in or was just fetching the user's data again
- this.props.onLogin();
- }
- })
- .catch((err) => {
- console.logGlobal(err);
- });
-
+ if(success) {
+ UserActions.fetchCurrentUser();
+ }
},
render() {
- let email = this.getQuery().email || null;
+ let email = this.props.location.query.email || null;
return (
diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js
index 9cb8b94f..16886def 100644
--- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js
+++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js
@@ -76,7 +76,7 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({
render() {
let filterIcon = (
-
+
*
);
diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js
index a44b8ca2..a3615aec 100644
--- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js
+++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js
@@ -47,8 +47,8 @@ let PieceListToolbarOrderWidget = React.createClass({
render() {
let filterIcon = (
-
- *
+
+ ·
);
return (
diff --git a/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js b/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js
new file mode 100644
index 00000000..cdfc129b
--- /dev/null
+++ b/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js
@@ -0,0 +1,107 @@
+'use strict';
+
+import React from 'react';
+import { History } from 'react-router';
+
+import UserStore from '../../../stores/user_store';
+import UserActions from '../../../actions/user_actions';
+
+import AppConstants from '../../../constants/application_constants';
+
+
+const { object } = React.PropTypes;
+const WHEN_ENUM = ['loggedIn', 'loggedOut'];
+
+/**
+ * Can be used in combination with `Route` as an intermediate Handler
+ * between the actual component we want to display dependent on a certain state
+ * that is required to display that component.
+ *
+ * @param {string} options.to Any type of route path that is defined in routes.js
+ * @param {enum/string} options.when ('loggedIn' || 'loggedOut')
+ */
+export default function AuthProxyHandler({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(', ');
+ throw new Error(`"when" must be one of: [${whenValues}] got "${when}" instead`);
+ }
+
+ return (Component) => {
+ return React.createClass({
+ propTypes: {
+ location: object
+ },
+
+ mixins: [History],
+
+ getInitialState() {
+ return UserStore.getState();
+ },
+
+ componentDidMount() {
+ UserStore.listen(this.onChange);
+ UserActions.fetchCurrentUser();
+ },
+
+ componentDidUpdate() {
+ this.redirectConditionally();
+ },
+
+ componentWillUnmount() {
+ UserStore.unlisten(this.onChange);
+ },
+
+ redirectConditionally() {
+ const { query } = this.props.location;
+ const { redirectAuthenticated, redirect } = query;
+
+ // The user of this handler specifies with `when`, what kind of status
+ // needs to be checked to conditionally do - if that state is `true` -
+ // a redirect.
+ //
+ // So if when === 'loggedIn', we're checking if the user is logged in (and
+ // vice versa)
+ let exprToValidate = when === 'loggedIn' ?
+ this.state.currentUser && this.state.currentUser.email :
+ this.state.currentUser && !this.state.currentUser.email;
+
+ // and redirect if `true`.
+ if(exprToValidate) {
+ window.setTimeout(() => this.history.replaceState(null, to, query));
+
+ // 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(() => this.history.replaceState(null, '/' + redirect, query));
+
+ } else if(!exprToValidate && when === 'loggedOut' && redirectAuthenticated) {
+ /*
+ * redirectAuthenticated contains an arbirary path
+ * eg pieces/, editions/, collection, settings, ...
+ * hence transitionTo cannot be used directly.
+ *
+ * While we're getting rid of `query.redirect` explicitly in the
+ * above `else if` statement, here it's sufficient to just call
+ * `baseUrl` + `redirectAuthenticated`, as it gets rid of queries as well.
+ */
+ window.location = AppConstants.baseUrl + redirectAuthenticated;
+ }
+ },
+
+ onChange(state) {
+ this.setState(state);
+ },
+
+ render() {
+ return (
+
+ );
+ }
+ });
+ };
+}
diff --git a/js/components/ascribe_settings/account_settings.js b/js/components/ascribe_settings/account_settings.js
index 1898c599..f337a17b 100644
--- a/js/components/ascribe_settings/account_settings.js
+++ b/js/components/ascribe_settings/account_settings.js
@@ -15,7 +15,7 @@ import AclProxy from '../acl_proxy';
import CopyrightAssociationForm from '../ascribe_forms/form_copyright_association';
import ApiUrls from '../../constants/api_urls';
-import AppConstants from '../../constants/application_constants';
+import AscribeSpinner from '../ascribe_spinner';
import { getLangText } from '../../utils/lang_utils';
@@ -37,7 +37,7 @@ let AccountSettings = React.createClass({
},
render() {
- let content = ;
+ let content = ;
let profile = null;
if (this.props.currentUser.username) {
@@ -78,7 +78,7 @@ let AccountSettings = React.createClass({
getFormData={this.getFormDataProfile}>
@@ -96,11 +96,15 @@ let AccountSettings = React.createClass({
title={getLangText('Account')}
defaultExpanded={true}>
{content}
-
+
+
+
{profile}
);
}
});
-export default AccountSettings;
\ No newline at end of file
+export default AccountSettings;
diff --git a/js/components/ascribe_settings/api_settings.js b/js/components/ascribe_settings/api_settings.js
index 0f638675..ed35041b 100644
--- a/js/components/ascribe_settings/api_settings.js
+++ b/js/components/ascribe_settings/api_settings.js
@@ -15,7 +15,7 @@ import ActionPanel from '../ascribe_panel/action_panel';
import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph';
import ApiUrls from '../../constants/api_urls';
-import AppConstants from '../../constants/application_constants';
+import AscribeSpinner from '../ascribe_spinner';
import { getLangText } from '../../utils/lang_utils';
@@ -57,7 +57,7 @@ let APISettings = React.createClass({
},
getApplications(){
- let content = ;
+ let content = ;
if (this.state.applications.length > -1) {
content = this.state.applications.map(function(app, i) {
diff --git a/js/components/ascribe_settings/bitcoin_wallet_settings.js b/js/components/ascribe_settings/bitcoin_wallet_settings.js
index ea528709..91f5e5d6 100644
--- a/js/components/ascribe_settings/bitcoin_wallet_settings.js
+++ b/js/components/ascribe_settings/bitcoin_wallet_settings.js
@@ -10,7 +10,7 @@ import Property from '../ascribe_forms/property';
import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph';
-import AppConstants from '../../constants/application_constants';
+import AscribeSpinner from '../ascribe_spinner';
import { getLangText } from '../../utils/lang_utils';
@@ -38,7 +38,7 @@ let BitcoinWalletSettings = React.createClass({
},
render() {
- let content = ;
+ let content = ;
if (this.state.walletSettings.btc_public_key) {
content = (
diff --git a/js/components/ascribe_settings/contract_settings.js b/js/components/ascribe_settings/contract_settings.js
index 7196032b..71d97542 100644
--- a/js/components/ascribe_settings/contract_settings.js
+++ b/js/components/ascribe_settings/contract_settings.js
@@ -23,10 +23,15 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
import AclProxy from '../acl_proxy';
import { getLangText } from '../../utils/lang_utils';
+import { setDocumentTitle } from '../../utils/dom_utils';
import { mergeOptions, truncateTextAtCharIndex } from '../../utils/general_utils';
let ContractSettings = React.createClass({
+ propTypes: {
+ location: React.PropTypes.object
+ },
+
getInitialState(){
return mergeOptions(
ContractListStore.getState(),
@@ -82,6 +87,8 @@ let ContractSettings = React.createClass({
let privateContracts = this.getPrivateContracts();
let createPublicContractForm = null;
+ setDocumentTitle(getLangText('Contracts settings'));
+
if(publicContracts.length === 0) {
createPublicContractForm = (
+ }}
+ location={this.props.location}/>
);
}
@@ -114,7 +122,9 @@ let ContractSettings = React.createClass({
-
+
+ }}
+ location={this.props.location}/>
{privateContracts.map((contract, i) => {
return (
-
+
+ location={this.props.location}/>
);
}
});
diff --git a/js/components/ascribe_settings/settings_container.js b/js/components/ascribe_settings/settings_container.js
index 2b9ae2a1..923759fd 100644
--- a/js/components/ascribe_settings/settings_container.js
+++ b/js/components/ascribe_settings/settings_container.js
@@ -1,7 +1,6 @@
'use strict';
import React from 'react';
-import Router from 'react-router';
import UserStore from '../../stores/user_store';
import UserActions from '../../actions/user_actions';
@@ -16,6 +15,8 @@ import APISettings from './api_settings';
import AclProxy from '../acl_proxy';
import { mergeOptions } from '../../utils/general_utils';
+import { getLangText } from '../../utils/lang_utils';
+import { setDocumentTitle } from '../../utils/dom_utils';
let SettingsContainer = React.createClass({
@@ -25,8 +26,6 @@ let SettingsContainer = React.createClass({
React.PropTypes.element])
},
- mixins: [Router.Navigation],
-
getInitialState() {
return mergeOptions(
UserStore.getState(),
@@ -56,6 +55,8 @@ let SettingsContainer = React.createClass({
},
render() {
+ setDocumentTitle(getLangText('Account settings'));
+
if (this.state.currentUser && this.state.currentUser.username) {
return (
diff --git a/js/components/ascribe_slides_container/slides_container.js b/js/components/ascribe_slides_container/slides_container.js
index 53092a38..8ed66c1d 100644
--- a/js/components/ascribe_slides_container/slides_container.js
+++ b/js/components/ascribe_slides_container/slides_container.js
@@ -1,96 +1,40 @@
'use strict';
-import React from 'react';
-import Router from 'react-router';
-import ReactAddons from 'react/addons';
+import React from 'react/addons';
+import { History } from 'react-router';
import SlidesContainerBreadcrumbs from './slides_container_breadcrumbs';
-let State = Router.State;
-let Navigation = Router.Navigation;
+const { arrayOf, element, bool, shape, string, object } = React.PropTypes;
-let SlidesContainer = React.createClass({
+const SlidesContainer = React.createClass({
propTypes: {
- children: React.PropTypes.arrayOf(React.PropTypes.element),
- forwardProcess: React.PropTypes.bool.isRequired,
+ children: arrayOf(element),
+ forwardProcess: bool.isRequired,
- glyphiconClassNames: React.PropTypes.shape({
- pending: React.PropTypes.string,
- complete: React.PropTypes.string
- })
+ glyphiconClassNames: shape({
+ pending: string,
+ complete: string
+ }),
+ location: object
},
- mixins: [State, Navigation],
+ mixins: [History],
getInitialState() {
- // handle queryParameters
- let queryParams = this.getQuery();
- let slideNum = -1;
- let startFrom = -1;
-
- // We can actually need to check if slide_num is present as a key in queryParams.
- // We do not really care about its value though...
- if(queryParams && 'slide_num' in queryParams) {
- slideNum = parseInt(queryParams.slide_num, 10);
- }
- // if slide_num is not set, this will be done in componentDidMount
-
- // the query param 'start_from' removes all slide children before the respective number
- // Also, we use the 'in' keyword for the same reason as above in 'slide_num'
- if(queryParams && 'start_from' in queryParams) {
- startFrom = parseInt(queryParams.start_from, 10);
- }
-
return {
- slideNum,
- startFrom,
- containerWidth: 0,
- historyLength: window.history.length
+ containerWidth: 0
};
},
componentDidMount() {
- // check if slide_num was defined, and if not then default to 0
- let queryParams = this.getQuery();
-
- // We use 'in' to check if the key is present in the user's browser url bar,
- // we do not really care about its value at this point
- if(!('slide_num' in queryParams)) {
-
- // we're first requiring all the other possible queryParams and then set
- // the specific one we need instead of overwriting them
- queryParams.slide_num = 0;
-
- this.replaceWith(this.getPathname(), null, queryParams);
- }
-
- // 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);
- },
- componentWillReceiveProps() {
- let queryParams = this.getQuery();
-
- // also check if start_from was updated
- // This applies for example when the user tries to submit a already existing piece
- // (starting from slide 1 for example) and then clicking on + NEW WORK
- if(queryParams && !('start_from' in queryParams)) {
- this.setState({
- startFrom: -1
- });
- }
- },
-
- componentDidUpdate() {
- let queryParams = this.getQuery();
-
- // check if slide_num was defined, and if not then default to 0
- this.setSlideNum(queryParams.slide_num);
+ // Initially, we need to dispatch 'resize' once to render correctly
+ window.dispatchEvent(new Event('resize'));
},
componentWillUnmount() {
@@ -105,80 +49,26 @@ let SlidesContainer = React.createClass({
},
// When the start_from parameter is used, this.setSlideNum can not simply be used anymore.
- nextSlide() {
- let nextSlide = this.state.slideNum + 1;
- this.setSlideNum(nextSlide);
+ nextSlide(additionalQueryParams) {
+ const slideNum = parseInt(this.props.location.query.slide_num, 10) || 0;
+ let nextSlide = slideNum + 1;
+ this.setSlideNum(nextSlide, additionalQueryParams);
},
- // We let every one from the outsite set the page number of the slider,
- // though only if the slideNum is actually in the range of our children-list.
- setSlideNum(slideNum) {
-
- // we do not want to overwrite other queryParams
- let queryParams = this.getQuery();
-
- // slideNum can in some instances be not a number,
- // therefore we have to parse it to one and make sure that its not NaN
- slideNum = parseInt(slideNum, 10);
-
- // if slideNum is not a number (even after we parsed it to one) and there has
- // never been a transition to another slide (this.state.slideNum ==== -1 indicates that)
- // then we want to "replace" (in this case append) the current url with ?slide_num=0
- if(isNaN(slideNum) && this.state.slideNum === -1) {
- slideNum = 0;
- queryParams.slide_num = slideNum;
-
- this.replaceWith(this.getPathname(), null, queryParams);
- this.setState({slideNum: slideNum});
- return;
-
- // slideNum always represents the future state. So if slideNum and
- // this.state.slideNum are equal, there is no sense in redirecting
- } else if(slideNum === this.state.slideNum) {
- return;
-
- // if slideNum is within the range of slides and none of the previous cases
- // where matched, we can actually do transitions
- } else if(slideNum >= 0 || slideNum < this.customChildrenCount()) {
-
- if(slideNum !== this.state.slideNum - 1) {
- // Bootstrapping the component, getInitialState is called once to save
- // the tabs history length.
- // In order to know if we already pushed a new state on the history stack or not,
- // we're comparing the old history length with the new one and if it didn't change then
- // we push a new state on it ONCE (ever).
- // Otherwise, we're able to use the browsers history.forward() method
- // to keep the stack clean
-
- if(this.props.forwardProcess) {
- queryParams.slide_num = slideNum;
- this.transitionTo(this.getPathname(), null, queryParams);
- } else {
- if(this.state.historyLength === window.history.length) {
- queryParams.slide_num = slideNum;
- this.transitionTo(this.getPathname(), null, queryParams);
- } else {
- window.history.forward();
- }
- }
- }
-
- this.setState({
- slideNum: slideNum
- });
-
- } else {
- throw new Error('You\'re calling a page number that is out of range.');
- }
+ 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);
},
// 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 = [];
- ReactAddons.Children.map(this.props.children, (child, i) => {
- if(child && i >= this.state.startFrom && child.props['data-slide-title']) {
+ React.Children.map(this.props.children, (child, i) => {
+ if(child && i >= startFrom && child.props['data-slide-title']) {
breadcrumbs.push(child.props['data-slide-title']);
}
});
@@ -191,9 +81,11 @@ let SlidesContainer = React.createClass({
// Therefore React.Children.count does not work anymore and we
// need to implement our own method.
customChildrenCount() {
+ const startFrom = parseInt(this.props.location.query.start_from, 10) || -1;
let count = 0;
+
React.Children.forEach(this.props.children, (child, i) => {
- if(i >= this.state.startFrom) {
+ if(i >= startFrom) {
count++;
}
});
@@ -212,7 +104,7 @@ let SlidesContainer = React.createClass({
return (
@@ -225,12 +117,13 @@ let SlidesContainer = React.createClass({
// Since we need to give the slides a width, we need to call ReactAddons.addons.cloneWithProps
// Also, a key is nice to have!
renderChildren() {
- return ReactAddons.Children.map(this.props.children, (child, i) => {
+ const startFrom = parseInt(this.props.location.query.start_from, 10) || -1;
+ return React.Children.map(this.props.children, (child, i) => {
// since the default parameter of startFrom is -1, we do not need to check
// if its actually present in the url bar, as it will just not match
- if(child && i >= this.state.startFrom) {
- return ReactAddons.addons.cloneWithProps(child, {
+ if(child && i >= startFrom) {
+ return React.addons.cloneWithProps(child, {
className: 'ascribe-slide',
style: {
width: this.state.containerWidth
@@ -246,7 +139,7 @@ let SlidesContainer = React.createClass({
},
render() {
- let spacing = this.state.containerWidth * this.state.slideNum;
+ let spacing = this.state.containerWidth * parseInt(this.props.location.query.slide_num, 10) || 0;
let translateXValue = 'translateX(' + (-1) * spacing + 'px)';
/*
@@ -280,4 +173,4 @@ let SlidesContainer = React.createClass({
}
});
-export default SlidesContainer;
+export default SlidesContainer;
\ No newline at end of file
diff --git a/js/components/ascribe_spinner.js b/js/components/ascribe_spinner.js
new file mode 100644
index 00000000..ecdf641b
--- /dev/null
+++ b/js/components/ascribe_spinner.js
@@ -0,0 +1,34 @@
+'use strict';
+
+import React from 'react';
+import classNames from 'classnames';
+
+let AscribeSpinner = React.createClass({
+ propTypes: {
+ classNames: React.PropTypes.string,
+ size: React.PropTypes.oneOf(['sm', 'md', 'lg']),
+ color: React.PropTypes.oneOf(['blue', 'dark-blue', 'light-blue', 'pink', 'black', 'loop'])
+ },
+
+ getDefaultProps() {
+ return {
+ inline: false,
+ size: 'md',
+ color: 'loop'
+ };
+ },
+
+ render() {
+ return (
+
+
+
A
+
+ );
+ }
+});
+
+export default AscribeSpinner;
diff --git a/js/components/ascribe_table/models/table_models.js b/js/components/ascribe_table/models/table_models.js
index 7b971afe..b675d14e 100644
--- a/js/components/ascribe_table/models/table_models.js
+++ b/js/components/ascribe_table/models/table_models.js
@@ -35,14 +35,7 @@ export class TransitionModel {
this.callback = callback;
}
- toReactRouterLinkProps(queryValue) {
- let props = {
- to: this.to,
- params: {}
- };
-
- props.params[this.queryKey] = queryValue;
-
- return props;
+ toReactRouterLink(queryValue) {
+ return '/' + this.to + '/' + queryValue;
}
}
\ No newline at end of file
diff --git a/js/components/ascribe_table/table_item_wrapper.js b/js/components/ascribe_table/table_item_wrapper.js
index 6eb7b3b4..09cf562f 100644
--- a/js/components/ascribe_table/table_item_wrapper.js
+++ b/js/components/ascribe_table/table_item_wrapper.js
@@ -1,11 +1,10 @@
'use strict';
import React from 'react';
-import Router from 'react-router';
+import { Link } from 'react-router';
import { ColumnModel } from './models/table_models';
-let Link = Router.Link;
let TableItemWrapper = React.createClass({
propTypes: {
@@ -15,8 +14,6 @@ let TableItemWrapper = React.createClass({
onClick: React.PropTypes.func
},
- mixins: [Router.Navigation],
-
render() {
return (
@@ -35,18 +32,13 @@ let TableItemWrapper = React.createClass({
);
} else {
- let linkProps = column.transition.toReactRouterLinkProps(this.props.columnContent[column.transition.valueKey]);
- /**
- * If a transition is defined in columnContent, then we can use
- * Router.Navigation.transitionTo to redirect the user
- * programmatically
- */
+ let linkString = column.transition.toReactRouterLink(this.props.columnContent[column.transition.valueKey]);
return (
+ onClick={column.transition.callback}>
diff --git a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js
index 4c9211c5..38ec459a 100644
--- a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js
+++ b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js
@@ -12,6 +12,7 @@ import { getLangText } from '../../../utils/lang_utils';
// Taken from: https://github.com/fedosejev/react-file-drag-and-drop
let FileDragAndDrop = React.createClass({
propTypes: {
+ className: React.PropTypes.string,
onDrop: React.PropTypes.func.isRequired,
onDragOver: React.PropTypes.func,
onInactive: React.PropTypes.func,
@@ -40,7 +41,8 @@ let FileDragAndDrop = React.createClass({
plural: React.PropTypes.string
}),
- allowedExtensions: React.PropTypes.string
+ allowedExtensions: React.PropTypes.string,
+ location: React.PropTypes.object
},
handleDragOver(event) {
@@ -107,6 +109,7 @@ let FileDragAndDrop = React.createClass({
},
handleOnClick() {
+ let evt;
// when multiple is set to false and the user already uploaded a piece,
// do not propagate event
if(this.props.dropzoneInactive) {
@@ -118,16 +121,18 @@ let FileDragAndDrop = React.createClass({
return;
}
- // Firefox only recognizes the simulated mouse click if bubbles is set to true,
- // but since Google Chrome propagates the event much further than needed, we
- // need to stop propagation as soon as the event is created
- var evt = new MouseEvent('click', {
- view: window,
- bubbles: true,
- cancelable: true
- });
+ try {
+ evt = new MouseEvent('click', {
+ view: window,
+ bubbles: true,
+ cancelable: true
+ });
+ } catch(e) {
+ // For browsers that do not support the new MouseEvent syntax
+ evt = document.createEvent('MouseEvents');
+ evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
+ }
- evt.stopPropagation();
this.refs.fileinput.getDOMNode().dispatchEvent(evt);
},
@@ -142,7 +147,8 @@ let FileDragAndDrop = React.createClass({
fileClassToUpload,
areAssetsDownloadable,
areAssetsEditable,
- allowedExtensions
+ allowedExtensions,
+ location
} = this.props;
// has files only is true if there are files that do not have the status deleted or canceled
@@ -158,10 +164,10 @@ let FileDragAndDrop = React.createClass({
{getLangText('Computing hash(es)... This may take a few minutes.')}
@@ -179,7 +185,8 @@ let FileDragAndDrop = React.createClass({
hasFiles={hasFiles}
onClick={this.handleOnClick}
enableLocalHashing={enableLocalHashing}
- fileClassToUpload={fileClassToUpload}/>
+ fileClassToUpload={fileClassToUpload}
+ location={location}/>
+ {/*
+ Opera doesn't trigger simulated click events
+ if the targeted input has `display:none` set.
+ Which means we need to set its visibility to hidden
+ instead of using `display:none`.
+
+ See:
+ - http://stackoverflow.com/questions/12880604/jquery-triggerclick-not-working-on-opera-if-the-element-is-not-displayed
+ */}
{getLangText('Drag %s here', fileClass)},
+
{getLangText('Hash your work')}
@@ -52,7 +66,7 @@ let FileDragAndDropDialog = React.createClass({
or
{getLangText('Upload and hash your work')}
@@ -64,8 +78,7 @@ let FileDragAndDropDialog = React.createClass({
if(this.props.multipleFiles) {
return (
-