mirror of
https://github.com/ascribe/onion.git
synced 2024-12-22 17:33:14 +01:00
Merge branch 'master' into AD-601-in-piece_detail-longer-titles-are
This commit is contained in:
commit
52f10f178f
@ -5,6 +5,7 @@
|
|||||||
"es6": true
|
"es6": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"new-cap": [2, {newIsCap: true, capIsNew: false}],
|
||||||
"quotes": [2, "single"],
|
"quotes": [2, "single"],
|
||||||
"eol-last": [0],
|
"eol-last": [0],
|
||||||
"no-mixed-requires": [0],
|
"no-mixed-requires": [0],
|
||||||
@ -34,7 +35,8 @@
|
|||||||
"globals": {
|
"globals": {
|
||||||
"Intercom": true,
|
"Intercom": true,
|
||||||
"fetch": true,
|
"fetch": true,
|
||||||
"require": true
|
"require": true,
|
||||||
|
"File": true
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"react"
|
"react"
|
||||||
|
@ -51,8 +51,6 @@
|
|||||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||||
|
|
||||||
ga('create', 'UA-60614729-2', 'auto');
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Intercom library -->
|
<!-- Intercom library -->
|
||||||
|
@ -7,7 +7,8 @@ import CoaFetcher from '../fetchers/coa_fetcher';
|
|||||||
class CoaActions {
|
class CoaActions {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.generateActions(
|
this.generateActions(
|
||||||
'updateCoa'
|
'updateCoa',
|
||||||
|
'flushCoa'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,9 +27,7 @@ class CoaActions {
|
|||||||
this.actions.updateCoa(res.coa);
|
this.actions.updateCoa(res.coa);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log(err)
|
|
||||||
console.logGlobal(err);
|
console.logGlobal(err);
|
||||||
this.actions.updateCoa('Something went wrong, please try again later.');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import alt from '../alt';
|
import alt from '../alt';
|
||||||
|
import Q from 'q';
|
||||||
|
|
||||||
import EditionListFetcher from '../fetchers/edition_list_fetcher.js';
|
import EditionListFetcher from '../fetchers/edition_list_fetcher.js';
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ class EditionListActions {
|
|||||||
pageSize = 10;
|
pageSize = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return Q.Promise((resolve, reject) => {
|
||||||
EditionListFetcher
|
EditionListFetcher
|
||||||
.fetch(pieceId, page, pageSize, orderBy, orderAsc)
|
.fetch(pieceId, page, pageSize, orderBy, orderAsc)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
19
js/actions/event_actions.js
Normal file
19
js/actions/event_actions.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import alt from '../alt';
|
||||||
|
|
||||||
|
|
||||||
|
class EventActions {
|
||||||
|
constructor() {
|
||||||
|
this.generateActions(
|
||||||
|
'applicationWillBoot',
|
||||||
|
'applicationDidBoot',
|
||||||
|
'profileDidLoad',
|
||||||
|
//'userDidLogin',
|
||||||
|
//'userDidLogout',
|
||||||
|
'routeDidChange'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createActions(EventActions);
|
@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import alt from '../alt';
|
import alt from '../alt';
|
||||||
|
import Q from 'q';
|
||||||
|
|
||||||
import PieceListFetcher from '../fetchers/piece_list_fetcher';
|
import PieceListFetcher from '../fetchers/piece_list_fetcher';
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ class PieceListActions {
|
|||||||
|
|
||||||
// afterwards, we can load the list
|
// afterwards, we can load the list
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return Q.Promise((resolve, reject) => {
|
||||||
PieceListFetcher
|
PieceListFetcher
|
||||||
.fetch(page, pageSize, search, orderBy, orderAsc)
|
.fetch(page, pageSize, search, orderBy, orderAsc)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
@ -13,7 +13,7 @@ class UserActions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchCurrentUser() {
|
fetchCurrentUser() {
|
||||||
UserFetcher.fetchOne()
|
return UserFetcher.fetchOne()
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.actions.updateCurrentUser(res.users[0]);
|
this.actions.updateCurrentUser(res.users[0]);
|
||||||
})
|
})
|
||||||
|
29
js/app.js
29
js/app.js
@ -5,7 +5,9 @@ require('babel/polyfill');
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Router from 'react-router';
|
import Router from 'react-router';
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
import fetch from 'isomorphic-fetch';
|
import fetch from 'isomorphic-fetch';
|
||||||
|
/* eslint-enable */
|
||||||
|
|
||||||
import ApiUrls from './constants/api_urls';
|
import ApiUrls from './constants/api_urls';
|
||||||
import { updateApiUrls } from './constants/api_urls';
|
import { updateApiUrls } from './constants/api_urls';
|
||||||
@ -16,6 +18,16 @@ import requests from './utils/requests';
|
|||||||
import { getSubdomainSettings } from './utils/constants_utils';
|
import { getSubdomainSettings } from './utils/constants_utils';
|
||||||
import { initLogging } from './utils/error_utils';
|
import { initLogging } from './utils/error_utils';
|
||||||
|
|
||||||
|
import EventActions from './actions/event_actions';
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
// You can comment out the modules you don't need
|
||||||
|
// import DebugHandler from './third_party/debug';
|
||||||
|
import GoogleAnalyticsHandler from './third_party/ga';
|
||||||
|
import RavenHandler from './third_party/raven';
|
||||||
|
import IntercomHandler from './third_party/intercom';
|
||||||
|
/* eslint-enable */
|
||||||
|
|
||||||
initLogging();
|
initLogging();
|
||||||
|
|
||||||
let headers = {
|
let headers = {
|
||||||
@ -43,25 +55,30 @@ class AppGateway {
|
|||||||
settings = getSubdomainSettings(subdomain);
|
settings = getSubdomainSettings(subdomain);
|
||||||
appConstants.whitelabel = settings;
|
appConstants.whitelabel = settings;
|
||||||
updateApiUrls(settings.type, subdomain);
|
updateApiUrls(settings.type, subdomain);
|
||||||
this.load(settings.type);
|
this.load(settings);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
// if there are no matching subdomains, we're routing
|
// if there are no matching subdomains, we're routing
|
||||||
// to the default frontend
|
// to the default frontend
|
||||||
console.logGlobal(err);
|
console.logGlobal(err);
|
||||||
this.load('default');
|
this.load();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
load(type) {
|
load(settings) {
|
||||||
|
let type = 'default';
|
||||||
|
if (settings) {
|
||||||
|
type = settings.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
EventActions.applicationWillBoot(settings);
|
||||||
Router.run(getRoutes(type), Router.HistoryLocation, (App) => {
|
Router.run(getRoutes(type), Router.HistoryLocation, (App) => {
|
||||||
if (window.ga) {
|
|
||||||
window.ga('send', 'pageview');
|
|
||||||
}
|
|
||||||
React.render(
|
React.render(
|
||||||
<App />,
|
<App />,
|
||||||
document.getElementById('main')
|
document.getElementById('main')
|
||||||
);
|
);
|
||||||
|
EventActions.routeDidChange();
|
||||||
});
|
});
|
||||||
|
EventActions.applicationDidBoot(settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +68,15 @@ let Edition = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
// Flushing the coa state is essential to not displaying the same
|
||||||
|
// data to the user while he's on another edition
|
||||||
|
//
|
||||||
|
// BUGFIX: Previously we had this line in the componentWillUnmount of
|
||||||
|
// CoaDetails, but since we're reloading the edition after performing an ACL action
|
||||||
|
// on it, this resulted in multiple events occupying the dispatcher, which eventually
|
||||||
|
// resulted in crashing the app.
|
||||||
|
CoaActions.flushCoa();
|
||||||
|
|
||||||
UserStore.unlisten(this.onChange);
|
UserStore.unlisten(this.onChange);
|
||||||
PieceListStore.unlisten(this.onChange);
|
PieceListStore.unlisten(this.onChange);
|
||||||
},
|
},
|
||||||
@ -398,7 +407,7 @@ let CoaDetails = React.createClass({
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
CoaStore.listen(this.onChange);
|
CoaStore.listen(this.onChange);
|
||||||
if (this.props.edition.coa) {
|
if(this.props.edition.coa) {
|
||||||
CoaActions.fetchOne(this.props.edition.coa);
|
CoaActions.fetchOne(this.props.edition.coa);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -415,7 +424,7 @@ let CoaDetails = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.coa && this.state.coa.url_safe) {
|
if(this.state.coa && this.state.coa.url_safe) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-center ascribe-button-list">
|
<p className="text-center ascribe-button-list">
|
||||||
@ -433,8 +442,7 @@ let CoaDetails = React.createClass({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
} else if(typeof this.state.coa === 'string'){
|
||||||
else if (typeof this.state.coa === 'string'){
|
|
||||||
return (
|
return (
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
{this.state.coa}
|
{this.state.coa}
|
||||||
|
@ -12,6 +12,11 @@ import CollapsibleButton from './../ascribe_collapsible/collapsible_button';
|
|||||||
import AclProxy from '../acl_proxy';
|
import AclProxy from '../acl_proxy';
|
||||||
|
|
||||||
|
|
||||||
|
const EMBED_IFRAME_HEIGHT = {
|
||||||
|
video: 315,
|
||||||
|
audio: 62
|
||||||
|
};
|
||||||
|
|
||||||
let MediaContainer = React.createClass({
|
let MediaContainer = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
content: React.PropTypes.object
|
content: React.PropTypes.object
|
||||||
@ -29,7 +34,9 @@ let MediaContainer = React.createClass({
|
|||||||
extraData = this.props.content.digital_work.encoding_urls.map(e => { return { url: e.url, type: e.label }; });
|
extraData = this.props.content.digital_work.encoding_urls.map(e => { return { url: e.url, type: e.label }; });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['video', 'audio'].indexOf(mimetype) > -1){
|
if (['video', 'audio'].indexOf(mimetype) > -1) {
|
||||||
|
let height = EMBED_IFRAME_HEIGHT[mimetype];
|
||||||
|
|
||||||
embed = (
|
embed = (
|
||||||
<CollapsibleButton
|
<CollapsibleButton
|
||||||
button={
|
button={
|
||||||
@ -39,7 +46,7 @@ let MediaContainer = React.createClass({
|
|||||||
}
|
}
|
||||||
panel={
|
panel={
|
||||||
<pre className="">
|
<pre className="">
|
||||||
{'<iframe width="560" height="315" src="http://embed.ascribe.io/content/'
|
{'<iframe width="560" height="' + height + '" src="http://embed.ascribe.io/content/'
|
||||||
+ this.props.content.bitcoin_id + '" frameborder="0" allowfullscreen></iframe>'}
|
+ this.props.content.bitcoin_id + '" frameborder="0" allowfullscreen></iframe>'}
|
||||||
</pre>
|
</pre>
|
||||||
}/>
|
}/>
|
||||||
@ -55,6 +62,7 @@ let MediaContainer = React.createClass({
|
|||||||
encodingStatus={this.props.content.digital_work.isEncoding} />
|
encodingStatus={this.props.content.digital_work.isEncoding} />
|
||||||
<p className="text-center">
|
<p className="text-center">
|
||||||
<AclProxy
|
<AclProxy
|
||||||
|
show={['video', 'audio', 'image'].indexOf(mimetype) === -1 || this.props.content.acl.acl_download}
|
||||||
aclObject={this.props.content.acl}
|
aclObject={this.props.content.acl}
|
||||||
aclName="acl_download">
|
aclName="acl_download">
|
||||||
<Button bsSize="xsmall" className="ascribe-margin-1px" href={this.props.content.digital_work.url} target="_blank">
|
<Button bsSize="xsmall" className="ascribe-margin-1px" href={this.props.content.digital_work.url} target="_blank">
|
||||||
|
@ -45,7 +45,7 @@ let Form = React.createClass({
|
|||||||
this.setState({submitted: true});
|
this.setState({submitted: true});
|
||||||
this.clearErrors();
|
this.clearErrors();
|
||||||
let action = (this.httpVerb && this.httpVerb()) || 'post';
|
let action = (this.httpVerb && this.httpVerb()) || 'post';
|
||||||
this[action]();
|
window.setTimeout(() => this[action](), 100);
|
||||||
},
|
},
|
||||||
post(){
|
post(){
|
||||||
requests
|
requests
|
||||||
@ -59,6 +59,7 @@ let Form = React.createClass({
|
|||||||
for (let ref in this.refs){
|
for (let ref in this.refs){
|
||||||
data[this.refs[ref].props.name] = this.refs[ref].state.value;
|
data[this.refs[ref].props.name] = this.refs[ref].state.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('getFormData' in this.props){
|
if ('getFormData' in this.props){
|
||||||
data = mergeOptionsWithDuplicates(data, this.props.getFormData());
|
data = mergeOptionsWithDuplicates(data, this.props.getFormData());
|
||||||
}
|
}
|
||||||
@ -90,7 +91,7 @@ let Form = React.createClass({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.logGlobal(err);
|
console.logGlobal(err, false, this.getFormData());
|
||||||
this.setState({errors: [getLangText('Something went wrong, please try again later')]});
|
this.setState({errors: [getLangText('Something went wrong, please try again later')]});
|
||||||
}
|
}
|
||||||
this.setState({submitted: false});
|
this.setState({submitted: false});
|
||||||
|
@ -15,7 +15,6 @@ import LoanContractActions from '../../actions/loan_contract_actions';
|
|||||||
|
|
||||||
import AppConstants from '../../constants/application_constants';
|
import AppConstants from '../../constants/application_constants';
|
||||||
|
|
||||||
import { mergeOptions } from '../../utils/general_utils';
|
|
||||||
import { getLangText } from '../../utils/lang_utils';
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
|
|
||||||
|
|
||||||
@ -59,7 +58,8 @@ let LoanForm = React.createClass({
|
|||||||
name="terms"
|
name="terms"
|
||||||
className="ascribe-settings-property-collapsible-toggle"
|
className="ascribe-settings-property-collapsible-toggle"
|
||||||
style={{paddingBottom: 0}}>
|
style={{paddingBottom: 0}}>
|
||||||
<InputCheckbox>
|
<InputCheckbox
|
||||||
|
defaultChecked={false}>
|
||||||
<span>
|
<span>
|
||||||
{getLangText('I agree to the')}
|
{getLangText('I agree to the')}
|
||||||
<a href={this.state.contractUrl} target="_blank">
|
<a href={this.state.contractUrl} target="_blank">
|
||||||
@ -72,14 +72,11 @@ let LoanForm = React.createClass({
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Property
|
<Property
|
||||||
hidden={true}
|
|
||||||
name="terms"
|
name="terms"
|
||||||
className="ascribe-settings-property-collapsible-toggle"
|
style={{paddingBottom: 0}}
|
||||||
style={{paddingBottom: 0}}>
|
hidden={true}>
|
||||||
<input
|
<InputCheckbox
|
||||||
ref="input"
|
defaultChecked={true} />
|
||||||
type="checkbox"
|
|
||||||
defaultValue={true} />
|
|
||||||
</Property>
|
</Property>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -134,8 +131,7 @@ let LoanForm = React.createClass({
|
|||||||
</Property>
|
</Property>
|
||||||
<Property
|
<Property
|
||||||
name='gallery_name'
|
name='gallery_name'
|
||||||
label={getLangText('Gallery/exhibition (optional)')}
|
label={getLangText('Gallery/exhibition (optional)')}>
|
||||||
onBlur={this.handleOnBlur}>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={getLangText('Gallery/exhibition (optional)')}/>
|
placeholder={getLangText('Gallery/exhibition (optional)')}/>
|
||||||
|
@ -25,7 +25,8 @@ let LoginForm = React.createClass({
|
|||||||
headerMessage: React.PropTypes.string,
|
headerMessage: React.PropTypes.string,
|
||||||
submitMessage: React.PropTypes.string,
|
submitMessage: React.PropTypes.string,
|
||||||
redirectOnLoggedIn: React.PropTypes.bool,
|
redirectOnLoggedIn: React.PropTypes.bool,
|
||||||
redirectOnLoginSuccess: React.PropTypes.bool
|
redirectOnLoginSuccess: React.PropTypes.bool,
|
||||||
|
onLogin: React.PropTypes.func
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [Router.Navigation],
|
mixins: [Router.Navigation],
|
||||||
@ -70,18 +71,29 @@ let LoginForm = React.createClass({
|
|||||||
// The easiest way to check if the user was successfully logged in is to fetch the user
|
// 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
|
// 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.
|
// register_piece is listening to the changes of the user_store.
|
||||||
UserActions.fetchCurrentUser();
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
/* 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
|
|
||||||
*/
|
|
||||||
if(this.props.redirectOnLoginSuccess) {
|
|
||||||
window.location = AppConstants.baseUrl + 'collection';
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -2,18 +2,21 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import AppConstants from '../../constants/application_constants';
|
import UserStore from '../../stores/user_store';
|
||||||
|
import UserActions from '../../actions/user_actions';
|
||||||
|
|
||||||
import Form from './form';
|
import Form from './form';
|
||||||
import Property from './property';
|
import Property from './property';
|
||||||
import FormPropertyHeader from './form_property_header';
|
import FormPropertyHeader from './form_property_header';
|
||||||
|
|
||||||
import apiUrls from '../../constants/api_urls';
|
|
||||||
|
|
||||||
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
|
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
|
||||||
|
|
||||||
|
import AppConstants from '../../constants/application_constants';
|
||||||
|
import apiUrls from '../../constants/api_urls';
|
||||||
|
|
||||||
import { getCookie } from '../../utils/fetch_api_utils';
|
import { getCookie } from '../../utils/fetch_api_utils';
|
||||||
import { getLangText } from '../../utils/lang_utils';
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
|
import { mergeOptions } from '../../utils/general_utils';
|
||||||
|
|
||||||
|
|
||||||
let RegisterPieceForm = React.createClass({
|
let RegisterPieceForm = React.createClass({
|
||||||
@ -21,8 +24,9 @@ let RegisterPieceForm = React.createClass({
|
|||||||
headerMessage: React.PropTypes.string,
|
headerMessage: React.PropTypes.string,
|
||||||
submitMessage: React.PropTypes.string,
|
submitMessage: React.PropTypes.string,
|
||||||
handleSuccess: React.PropTypes.func,
|
handleSuccess: React.PropTypes.func,
|
||||||
isFineUploaderEditable: React.PropTypes.bool,
|
isFineUploaderActive: React.PropTypes.bool,
|
||||||
children: React.PropTypes.element
|
children: React.PropTypes.element,
|
||||||
|
onLoggedOut: React.PropTypes.func
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps() {
|
getDefaultProps() {
|
||||||
@ -33,10 +37,26 @@ let RegisterPieceForm = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
getInitialState(){
|
getInitialState(){
|
||||||
return {
|
return mergeOptions(
|
||||||
digitalWorkKey: null,
|
{
|
||||||
isUploadReady: false
|
digitalWorkKey: null,
|
||||||
};
|
isUploadReady: false
|
||||||
|
},
|
||||||
|
UserStore.getState()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
UserStore.listen(this.onChange);
|
||||||
|
UserActions.fetchCurrentUser();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
UserStore.unlisten(this.onChange);
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange(state) {
|
||||||
|
this.setState(state);
|
||||||
},
|
},
|
||||||
|
|
||||||
getFormData(){
|
getFormData(){
|
||||||
@ -67,6 +87,9 @@ let RegisterPieceForm = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
let currentUser = this.state.currentUser;
|
||||||
|
let enableLocalHashing = currentUser && currentUser.profile ? currentUser.profile.hash_locally : false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
className="ascribe-form-bordered"
|
className="ascribe-form-bordered"
|
||||||
@ -94,7 +117,10 @@ let RegisterPieceForm = React.createClass({
|
|||||||
submitKey={this.submitKey}
|
submitKey={this.submitKey}
|
||||||
setIsUploadReady={this.setIsUploadReady}
|
setIsUploadReady={this.setIsUploadReady}
|
||||||
isReadyForFormSubmission={this.isReadyForFormSubmission}
|
isReadyForFormSubmission={this.isReadyForFormSubmission}
|
||||||
editable={this.props.isFineUploaderEditable}/>
|
isFineUploaderActive={this.props.isFineUploaderActive}
|
||||||
|
onLoggedOut={this.props.onLoggedOut}
|
||||||
|
editable={this.props.isFineUploaderEditable}
|
||||||
|
enableLocalHashing={enableLocalHashing}/>
|
||||||
</Property>
|
</Property>
|
||||||
<Property
|
<Property
|
||||||
name='artist_name'
|
name='artist_name'
|
||||||
@ -133,10 +159,14 @@ let FileUploader = React.createClass({
|
|||||||
submitKey: React.PropTypes.func,
|
submitKey: React.PropTypes.func,
|
||||||
isReadyForFormSubmission: React.PropTypes.func,
|
isReadyForFormSubmission: React.PropTypes.func,
|
||||||
onClick: React.PropTypes.func,
|
onClick: React.PropTypes.func,
|
||||||
// editable is used to lock react fine uploader in case
|
|
||||||
|
// isFineUploaderActive is used to lock react fine uploader in case
|
||||||
// a user is actually not logged in already to prevent him from droping files
|
// a user is actually not logged in already to prevent him from droping files
|
||||||
// before login in
|
// before login in
|
||||||
editable: React.PropTypes.bool
|
isFineUploaderActive: React.PropTypes.bool,
|
||||||
|
onLoggedOut: React.PropTypes.func,
|
||||||
|
editable: React.PropTypes.bool,
|
||||||
|
enableLocalHashing: React.PropTypes.bool
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -158,7 +188,7 @@ let FileUploader = React.createClass({
|
|||||||
setIsUploadReady={this.props.setIsUploadReady}
|
setIsUploadReady={this.props.setIsUploadReady}
|
||||||
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
|
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
|
||||||
areAssetsDownloadable={false}
|
areAssetsDownloadable={false}
|
||||||
areAssetsEditable={this.props.editable}
|
areAssetsEditable={this.props.isFineUploaderActive}
|
||||||
signature={{
|
signature={{
|
||||||
endpoint: AppConstants.serverUrl + 's3/signature/',
|
endpoint: AppConstants.serverUrl + 's3/signature/',
|
||||||
customHeaders: {
|
customHeaders: {
|
||||||
@ -172,7 +202,9 @@ let FileUploader = React.createClass({
|
|||||||
customHeaders: {
|
customHeaders: {
|
||||||
'X-CSRFToken': getCookie(AppConstants.csrftoken)
|
'X-CSRFToken': getCookie(AppConstants.csrftoken)
|
||||||
}
|
}
|
||||||
}}/>
|
}}
|
||||||
|
onInactive={this.props.onLoggedOut}
|
||||||
|
enableLocalHashing={this.props.enableLocalHashing} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -63,9 +63,7 @@ let SignupForm = React.createClass({
|
|||||||
this.props.handleSuccess(getLangText('We sent an email to your address') + ' ' + response.user.email + ', ' + getLangText('please confirm') + '.');
|
this.props.handleSuccess(getLangText('We sent an email to your address') + ' ' + response.user.email + ', ' + getLangText('please confirm') + '.');
|
||||||
|
|
||||||
},
|
},
|
||||||
getFormData(){
|
|
||||||
return {terms: this.refs.form.refs.terms.refs.input.state.value};
|
|
||||||
},
|
|
||||||
render() {
|
render() {
|
||||||
let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' +
|
let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' +
|
||||||
getLangText('This password is securing your digital property like a bank account') + '.\n ' +
|
getLangText('This password is securing your digital property like a bank account') + '.\n ' +
|
||||||
@ -76,7 +74,6 @@ let SignupForm = React.createClass({
|
|||||||
ref='form'
|
ref='form'
|
||||||
url={apiUrls.users_signup}
|
url={apiUrls.users_signup}
|
||||||
handleSuccess={this.handleSuccess}
|
handleSuccess={this.handleSuccess}
|
||||||
getFormData={this.getFormData}
|
|
||||||
buttons={
|
buttons={
|
||||||
<button type="submit" className="btn ascribe-btn ascribe-btn-login">
|
<button type="submit" className="btn ascribe-btn ascribe-btn-login">
|
||||||
{this.props.submitMessage}
|
{this.props.submitMessage}
|
||||||
|
@ -2,45 +2,87 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component can be used as a custom input element for form properties.
|
||||||
|
* It exposes its state via state.value and can be considered as a reference implementation
|
||||||
|
* for custom input components that live inside of properties.
|
||||||
|
*/
|
||||||
let InputCheckbox = React.createClass({
|
let InputCheckbox = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
required: React.PropTypes.string.isRequired,
|
required: React.PropTypes.bool,
|
||||||
|
|
||||||
|
// As can be read here: https://facebook.github.io/react/docs/forms.html
|
||||||
|
// inputs of type="checkbox" define their state via checked.
|
||||||
|
// Their default state is defined via defaultChecked.
|
||||||
|
//
|
||||||
|
// Since this component even has checkbox in its name, it felt wrong to expose defaultValue
|
||||||
|
// as the default-setting prop to other developers, which is why we choose defaultChecked.
|
||||||
|
defaultChecked: React.PropTypes.bool,
|
||||||
children: React.PropTypes.oneOfType([
|
children: React.PropTypes.oneOfType([
|
||||||
React.PropTypes.arrayOf(React.PropTypes.element),
|
React.PropTypes.arrayOf(React.PropTypes.element),
|
||||||
React.PropTypes.element
|
React.PropTypes.element
|
||||||
]).isRequired
|
])
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
// As HTML inputs, we're setting the default value for an input to checked === false
|
||||||
|
getDefaultProps() {
|
||||||
return {
|
return {
|
||||||
show: false
|
defaultChecked: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
handleFocus(event) {
|
// Setting value to null in initialState is essentially since we're deriving a certain state from
|
||||||
this.refs.checkbox.getDOMNode().checked = !this.refs.checkbox.getDOMNode().checked;
|
// value === null as can be seen in componentWillReceiveProps.
|
||||||
|
getInitialState() {
|
||||||
// This is calling property.js's method handleChange which
|
return {
|
||||||
// expects an event object
|
value: null
|
||||||
// Since we don't have a valid one, we'll just manipulate the one we get and send
|
};
|
||||||
// it to handleChange
|
},
|
||||||
event.target.value = this.refs.checkbox.getDOMNode().checked;
|
|
||||||
this.props.onChange(event);
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
this.setState({
|
componentWillReceiveProps(nextProps) {
|
||||||
show: this.refs.checkbox.getDOMNode().checked,
|
|
||||||
value: this.refs.checkbox.getDOMNode().checked
|
// Developer's are used to define defaultValues for inputs via defaultValue, but since this is a
|
||||||
|
// input of type checkbox we warn the dev to not do that.
|
||||||
|
if(this.props.defaultValue) {
|
||||||
|
console.warn('InputCheckbox is of type checkbox. Therefore its value is represented by checked and defaultChecked. defaultValue will do nothing!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first time InputCheckbox is rendered, we want to set its value to the value of defaultChecked.
|
||||||
|
// This needs to be done in order to expose it for the Property component.
|
||||||
|
// We can determine the first render by checking if value still has it's initialState(from getInitialState)
|
||||||
|
if(this.state.value === null) {
|
||||||
|
this.setState({value: nextProps.defaultChecked });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange() {
|
||||||
|
// On every change, we're inversing the input's value
|
||||||
|
let inverseValue = !this.refs.checkbox.getDOMNode().checked;
|
||||||
|
|
||||||
|
// pass it to the state
|
||||||
|
this.setState({value: inverseValue});
|
||||||
|
|
||||||
|
// and also call Property's onChange method
|
||||||
|
// (in this case we're mocking event.target.value, since we can not use the event
|
||||||
|
// coming from onChange. Its coming from the span (user is clicking on the span) and not the input)
|
||||||
|
this.props.onChange({
|
||||||
|
target: {
|
||||||
|
value: inverseValue
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
onClick={this.handleFocus}
|
onClick={this.onChange}>
|
||||||
onFocus={this.handleFocus}>
|
<input
|
||||||
<input type="checkbox" ref="checkbox" required="required"/>
|
type="checkbox"
|
||||||
|
ref="checkbox"
|
||||||
|
onChange={this.onChange}
|
||||||
|
checked={this.state.value}
|
||||||
|
defaultChecked={this.props.defaultChecked}/>
|
||||||
<span className="checkbox">
|
<span className="checkbox">
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</span>
|
</span>
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import AlertMixin from '../../mixins/alert_mixin';
|
|
||||||
|
|
||||||
let InputHidden = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
submitted: React.PropTypes.bool,
|
|
||||||
value: React.PropTypes.string
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [AlertMixin],
|
|
||||||
|
|
||||||
getInitialState() {
|
|
||||||
return {value: this.props.value,
|
|
||||||
alerts: null // needed in AlertMixin
|
|
||||||
};
|
|
||||||
},
|
|
||||||
handleChange(event) {
|
|
||||||
this.setState({value: event.target.value});
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
let alerts = (this.props.submitted) ? null : this.state.alerts;
|
|
||||||
return (
|
|
||||||
<div className="form-group">
|
|
||||||
{alerts}
|
|
||||||
<input
|
|
||||||
value={this.props.value}
|
|
||||||
type="hidden"
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default InputHidden;
|
|
@ -1,46 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import AlertMixin from '../../mixins/alert_mixin';
|
|
||||||
|
|
||||||
let InputText = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
submitted: React.PropTypes.bool,
|
|
||||||
onBlur: React.PropTypes.func,
|
|
||||||
type: React.PropTypes.string,
|
|
||||||
required: React.PropTypes.string,
|
|
||||||
placeHolder: React.PropTypes.string
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [AlertMixin],
|
|
||||||
|
|
||||||
getInitialState() {
|
|
||||||
return {value: null,
|
|
||||||
alerts: null // needed in AlertMixin
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
handleChange(event) {
|
|
||||||
this.setState({value: event.target.value});
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let className = 'form-control input-text-ascribe';
|
|
||||||
let alerts = (this.props.submitted) ? null : this.state.alerts;
|
|
||||||
return (
|
|
||||||
<div className="form-group">
|
|
||||||
{alerts}
|
|
||||||
<input className={className}
|
|
||||||
placeholder={this.props.placeHolder}
|
|
||||||
required={this.props.required}
|
|
||||||
type={this.props.type}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
onBlur={this.props.onBlur}/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default InputText;
|
|
@ -1,41 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import AlertMixin from '../../mixins/alert_mixin';
|
|
||||||
|
|
||||||
let InputTextArea = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
submitted: React.PropTypes.bool,
|
|
||||||
required: React.PropTypes.string,
|
|
||||||
defaultValue: React.PropTypes.string
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [AlertMixin],
|
|
||||||
|
|
||||||
getInitialState() {
|
|
||||||
return {
|
|
||||||
value: this.props.defaultValue,
|
|
||||||
alerts: null // needed in AlertMixin
|
|
||||||
};
|
|
||||||
},
|
|
||||||
handleChange(event) {
|
|
||||||
this.setState({value: event.target.value});
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
let className = 'form-control input-text-ascribe textarea-ascribe-message';
|
|
||||||
|
|
||||||
let alerts = (this.props.submitted) ? null : this.state.alerts;
|
|
||||||
return (
|
|
||||||
<div className="form-group">
|
|
||||||
{alerts}
|
|
||||||
<textarea className={className}
|
|
||||||
defaultValue={this.props.defaultValue}
|
|
||||||
required={this.props.required}
|
|
||||||
onChange={this.handleChange}></textarea>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default InputTextArea;
|
|
@ -39,6 +39,9 @@ let Property = React.createClass({
|
|||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return {
|
return {
|
||||||
|
// Please don't confuse initialValue with react's defaultValue.
|
||||||
|
// initialValue is set by us to ensure that a user can reset a specific
|
||||||
|
// property (after editing) to its initial value
|
||||||
initialValue: null,
|
initialValue: null,
|
||||||
value: null,
|
value: null,
|
||||||
isFocused: false,
|
isFocused: false,
|
||||||
@ -47,19 +50,29 @@ let Property = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps() {
|
componentWillReceiveProps() {
|
||||||
|
let childInput = this.refs.input;
|
||||||
|
|
||||||
// In order to set this.state.value from another component
|
// In order to set this.state.value from another component
|
||||||
// the state of value should only be set if its not undefined and
|
// the state of value should only be set if its not undefined and
|
||||||
// actually references something
|
// actually references something
|
||||||
if(typeof this.refs.input.getDOMNode().value !== 'undefined') {
|
if(typeof childInput.getDOMNode().value !== 'undefined') {
|
||||||
this.setState({
|
this.setState({
|
||||||
value: this.refs.input.getDOMNode().value
|
value: childInput.getDOMNode().value
|
||||||
|
});
|
||||||
|
|
||||||
|
// When implementing custom input components, their value isn't exposed like the one
|
||||||
|
// from native HTML elements.
|
||||||
|
// To enable developers to create input elements, they can expose a property called value
|
||||||
|
// in their state that will be picked up by property.js
|
||||||
|
} else if(childInput.state && typeof childInput.state.value !== 'undefined') {
|
||||||
|
this.setState({
|
||||||
|
value: childInput.state.value
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!this.state.initialValue) {
|
if(!this.state.initialValue) {
|
||||||
this.setState({
|
this.setState({
|
||||||
initialValue: this.refs.input.getDOMNode().defaultValue
|
initialValue: childInput.defaultValue
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -158,7 +171,6 @@ let Property = React.createClass({
|
|||||||
renderChildren() {
|
renderChildren() {
|
||||||
return ReactAddons.Children.map(this.props.children, (child) => {
|
return ReactAddons.Children.map(this.props.children, (child) => {
|
||||||
return ReactAddons.addons.cloneWithProps(child, {
|
return ReactAddons.addons.cloneWithProps(child, {
|
||||||
value: this.state.value,
|
|
||||||
onChange: this.handleChange,
|
onChange: this.handleChange,
|
||||||
onFocus: this.handleFocus,
|
onFocus: this.handleFocus,
|
||||||
onBlur: this.handleBlur,
|
onBlur: this.handleBlur,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Q from 'q';
|
||||||
|
|
||||||
import InjectInHeadMixin from '../../mixins/inject_in_head_mixin';
|
import InjectInHeadMixin from '../../mixins/inject_in_head_mixin';
|
||||||
import Panel from 'react-bootstrap/lib/Panel';
|
import Panel from 'react-bootstrap/lib/Panel';
|
||||||
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
|
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
|
||||||
@ -47,7 +49,7 @@ let Image = React.createClass({
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.inject('https://code.jquery.com/jquery-2.1.4.min.js')
|
this.inject('https://code.jquery.com/jquery-2.1.4.min.js')
|
||||||
.then(() =>
|
.then(() =>
|
||||||
Promise.all([
|
Q.all([
|
||||||
this.inject(AppConstants.baseUrl + 'static/thirdparty/shmui/shmui.css'),
|
this.inject(AppConstants.baseUrl + 'static/thirdparty/shmui/shmui.css'),
|
||||||
this.inject(AppConstants.baseUrl + 'static/thirdparty/shmui/jquery.shmui.js')
|
this.inject(AppConstants.baseUrl + 'static/thirdparty/shmui/jquery.shmui.js')
|
||||||
]).then(() => { window.jQuery('.shmui-ascribe').shmui(); }));
|
]).then(() => { window.jQuery('.shmui-ascribe').shmui(); }));
|
||||||
@ -99,7 +101,7 @@ let Video = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
Promise.all([
|
Q.all([
|
||||||
this.inject('//vjs.zencdn.net/4.12/video-js.css'),
|
this.inject('//vjs.zencdn.net/4.12/video-js.css'),
|
||||||
this.inject('//vjs.zencdn.net/4.12/video.js')
|
this.inject('//vjs.zencdn.net/4.12/video.js')
|
||||||
]).then(this.ready);
|
]).then(this.ready);
|
||||||
|
@ -17,7 +17,7 @@ let SlidesContainer = React.createClass({
|
|||||||
getInitialState() {
|
getInitialState() {
|
||||||
// handle queryParameters
|
// handle queryParameters
|
||||||
let queryParams = this.getQuery();
|
let queryParams = this.getQuery();
|
||||||
let slideNum = 0;
|
let slideNum = -1;
|
||||||
|
|
||||||
if(queryParams && 'slide_num' in queryParams) {
|
if(queryParams && 'slide_num' in queryParams) {
|
||||||
slideNum = parseInt(queryParams.slide_num, 10);
|
slideNum = parseInt(queryParams.slide_num, 10);
|
||||||
@ -26,7 +26,8 @@ let SlidesContainer = React.createClass({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
containerWidth: 0,
|
containerWidth: 0,
|
||||||
slideNum: slideNum
|
slideNum: slideNum,
|
||||||
|
historyLength: window.history.length
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -34,7 +35,12 @@ let SlidesContainer = React.createClass({
|
|||||||
// check if slide_num was defined, and if not then default to 0
|
// check if slide_num was defined, and if not then default to 0
|
||||||
let queryParams = this.getQuery();
|
let queryParams = this.getQuery();
|
||||||
if(!('slide_num' in queryParams)) {
|
if(!('slide_num' in queryParams)) {
|
||||||
this.replaceWith(this.getPathname(), null, {slide_num: 0});
|
|
||||||
|
// 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
|
// init container width
|
||||||
@ -45,22 +51,73 @@ let SlidesContainer = React.createClass({
|
|||||||
window.addEventListener('resize', this.handleContainerResize);
|
window.addEventListener('resize', this.handleContainerResize);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
// check if slide_num was defined, and if not then default to 0
|
||||||
|
let queryParams = this.getQuery();
|
||||||
|
this.setSlideNum(queryParams.slide_num);
|
||||||
|
},
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.removeEventListener('resize', this.handleContainerResize);
|
window.removeEventListener('resize', this.handleContainerResize);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleContainerResize() {
|
handleContainerResize() {
|
||||||
this.setState({
|
this.setState({
|
||||||
containerWidth: this.refs.containerWrapper.getDOMNode().offsetWidth
|
// +30 to get rid of the padding of the container which is 15px + 15px left and right
|
||||||
|
containerWidth: this.refs.containerWrapper.getDOMNode().offsetWidth + 30
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// We let every one from the outsite set the page number of the slider,
|
// 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.
|
// though only if the slideNum is actually in the range of our children-list.
|
||||||
setSlideNum(slideNum) {
|
setSlideNum(slideNum) {
|
||||||
if(slideNum < 0 || slideNum < React.Children.count(this.props.children)) {
|
|
||||||
|
|
||||||
this.replaceWith(this.getPathname(), null, {slide_num: 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 < React.Children.count(this.props.children)) {
|
||||||
|
|
||||||
|
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.state.historyLength === window.history.length) {
|
||||||
|
|
||||||
|
queryParams.slide_num = slideNum;
|
||||||
|
|
||||||
|
this.transitionTo(this.getPathname(), null, queryParams);
|
||||||
|
} else {
|
||||||
|
window.history.forward();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
slideNum: slideNum
|
slideNum: slideNum
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import ProgressBar from 'react-progressbar';
|
||||||
|
|
||||||
import FileDragAndDropDialog from './file_drag_and_drop_dialog';
|
import FileDragAndDropDialog from './file_drag_and_drop_dialog';
|
||||||
import FileDragAndDropPreviewIterator from './file_drag_and_drop_preview_iterator';
|
import FileDragAndDropPreviewIterator from './file_drag_and_drop_preview_iterator';
|
||||||
|
|
||||||
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
|
|
||||||
// Taken from: https://github.com/fedosejev/react-file-drag-and-drop
|
// Taken from: https://github.com/fedosejev/react-file-drag-and-drop
|
||||||
let FileDragAndDrop = React.createClass({
|
let FileDragAndDrop = React.createClass({
|
||||||
@ -18,6 +20,7 @@ let FileDragAndDrop = React.createClass({
|
|||||||
onDragLeave: React.PropTypes.func,
|
onDragLeave: React.PropTypes.func,
|
||||||
onDragOver: React.PropTypes.func,
|
onDragOver: React.PropTypes.func,
|
||||||
onDragEnd: React.PropTypes.func,
|
onDragEnd: React.PropTypes.func,
|
||||||
|
onInactive: React.PropTypes.func,
|
||||||
filesToUpload: React.PropTypes.array,
|
filesToUpload: React.PropTypes.array,
|
||||||
handleDeleteFile: React.PropTypes.func,
|
handleDeleteFile: React.PropTypes.func,
|
||||||
handleCancelFile: React.PropTypes.func,
|
handleCancelFile: React.PropTypes.func,
|
||||||
@ -26,7 +29,15 @@ let FileDragAndDrop = React.createClass({
|
|||||||
multiple: React.PropTypes.bool,
|
multiple: React.PropTypes.bool,
|
||||||
dropzoneInactive: React.PropTypes.bool,
|
dropzoneInactive: React.PropTypes.bool,
|
||||||
areAssetsDownloadable: React.PropTypes.bool,
|
areAssetsDownloadable: React.PropTypes.bool,
|
||||||
areAssetsEditable: React.PropTypes.bool
|
areAssetsEditable: React.PropTypes.bool,
|
||||||
|
|
||||||
|
enableLocalHashing: React.PropTypes.bool,
|
||||||
|
|
||||||
|
// triggers a FileDragAndDrop-global spinner
|
||||||
|
hashingProgress: React.PropTypes.number,
|
||||||
|
// sets the value of this.state.hashingProgress in reactfineuploader
|
||||||
|
// to -1 which is code for: aborted
|
||||||
|
handleCancelHashing: React.PropTypes.func
|
||||||
},
|
},
|
||||||
|
|
||||||
handleDragStart(event) {
|
handleDragStart(event) {
|
||||||
@ -72,6 +83,15 @@ let FileDragAndDrop = React.createClass({
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
let files;
|
let files;
|
||||||
|
|
||||||
|
if(this.props.dropzoneInactive) {
|
||||||
|
// if there is a handle function for doing stuff
|
||||||
|
// when the dropzone is inactive, then call it
|
||||||
|
if(this.props.onInactive) {
|
||||||
|
this.props.onInactive();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// handle Drag and Drop
|
// handle Drag and Drop
|
||||||
if(event.dataTransfer && event.dataTransfer.files.length > 0) {
|
if(event.dataTransfer && event.dataTransfer.files.length > 0) {
|
||||||
files = event.dataTransfer.files;
|
files = event.dataTransfer.files;
|
||||||
@ -113,10 +133,15 @@ let FileDragAndDrop = React.createClass({
|
|||||||
this.props.handleResumeFile(fileId);
|
this.props.handleResumeFile(fileId);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleOnClick(event) {
|
handleOnClick() {
|
||||||
// when multiple is set to false and the user already uploaded a piece,
|
// when multiple is set to false and the user already uploaded a piece,
|
||||||
// do not propagate event
|
// do not propagate event
|
||||||
if(this.props.dropzoneInactive) {
|
if(this.props.dropzoneInactive) {
|
||||||
|
// if there is a handle function for doing stuff
|
||||||
|
// when the dropzone is inactive, then call it
|
||||||
|
if(this.props.onInactive) {
|
||||||
|
this.props.onInactive();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,40 +165,55 @@ let FileDragAndDrop = React.createClass({
|
|||||||
className += this.props.dropzoneInactive ? 'inactive-dropzone' : 'active-dropzone';
|
className += this.props.dropzoneInactive ? 'inactive-dropzone' : 'active-dropzone';
|
||||||
className += this.props.className ? ' ' + this.props.className : '';
|
className += this.props.className ? ' ' + this.props.className : '';
|
||||||
|
|
||||||
return (
|
// if !== -2: triggers a FileDragAndDrop-global spinner
|
||||||
<div
|
if(this.props.hashingProgress !== -2) {
|
||||||
className={className}
|
return (
|
||||||
onDragStart={this.handleDragStart}
|
<div className={className}>
|
||||||
onDrag={this.handleDrop}
|
<p>{getLangText('Computing hash(es)... This may take a few minutes.')}</p>
|
||||||
onDragEnter={this.handleDragEnter}
|
<p>
|
||||||
onDragLeave={this.handleDragLeave}
|
<span>{Math.ceil(this.props.hashingProgress)}%</span>
|
||||||
onDragOver={this.handleDragOver}
|
<a onClick={this.props.handleCancelHashing}> {getLangText('Cancel hashing')}</a>
|
||||||
onDrop={this.handleDrop}
|
</p>
|
||||||
onDragEnd={this.handleDragEnd}>
|
<ProgressBar completed={this.props.hashingProgress} color="#48DACB"/>
|
||||||
<FileDragAndDropDialog
|
</div>
|
||||||
multipleFiles={this.props.multiple}
|
);
|
||||||
hasFiles={hasFiles}
|
} else {
|
||||||
onClick={this.handleOnClick}/>
|
return (
|
||||||
<FileDragAndDropPreviewIterator
|
<div
|
||||||
files={this.props.filesToUpload}
|
className={className}
|
||||||
handleDeleteFile={this.handleDeleteFile}
|
onDragStart={this.handleDragStart}
|
||||||
handleCancelFile={this.handleCancelFile}
|
onDrag={this.handleDrop}
|
||||||
handlePauseFile={this.handlePauseFile}
|
onDragEnter={this.handleDragEnter}
|
||||||
handleResumeFile={this.handleResumeFile}
|
onDragLeave={this.handleDragLeave}
|
||||||
areAssetsDownloadable={this.props.areAssetsDownloadable}
|
onDragOver={this.handleDragOver}
|
||||||
areAssetsEditable={this.props.areAssetsEditable}/>
|
onDrop={this.handleDrop}
|
||||||
<input
|
onDragEnd={this.handleDragEnd}>
|
||||||
multiple={this.props.multiple}
|
<FileDragAndDropDialog
|
||||||
ref="fileinput"
|
multipleFiles={this.props.multiple}
|
||||||
type="file"
|
hasFiles={hasFiles}
|
||||||
style={{
|
onClick={this.handleOnClick}
|
||||||
display: 'none',
|
enableLocalHashing={this.props.enableLocalHashing}/>
|
||||||
height: 0,
|
<FileDragAndDropPreviewIterator
|
||||||
width: 0
|
files={this.props.filesToUpload}
|
||||||
}}
|
handleDeleteFile={this.handleDeleteFile}
|
||||||
onChange={this.handleDrop} />
|
handleCancelFile={this.handleCancelFile}
|
||||||
</div>
|
handlePauseFile={this.handlePauseFile}
|
||||||
);
|
handleResumeFile={this.handleResumeFile}
|
||||||
|
areAssetsDownloadable={this.props.areAssetsDownloadable}
|
||||||
|
areAssetsEditable={this.props.areAssetsEditable}/>
|
||||||
|
<input
|
||||||
|
multiple={this.props.multiple}
|
||||||
|
ref="fileinput"
|
||||||
|
type="file"
|
||||||
|
style={{
|
||||||
|
display: 'none',
|
||||||
|
height: 0,
|
||||||
|
width: 0
|
||||||
|
}}
|
||||||
|
onChange={this.handleDrop} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,34 +1,80 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Router from 'react-router';
|
||||||
|
|
||||||
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
|
|
||||||
|
let Link = Router.Link;
|
||||||
|
|
||||||
let FileDragAndDropDialog = React.createClass({
|
let FileDragAndDropDialog = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
hasFiles: React.PropTypes.bool,
|
hasFiles: React.PropTypes.bool,
|
||||||
multipleFiles: React.PropTypes.bool,
|
multipleFiles: React.PropTypes.bool,
|
||||||
onClick: React.PropTypes.func
|
onClick: React.PropTypes.func,
|
||||||
|
enableLocalHashing: React.PropTypes.bool
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mixins: [Router.State],
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const queryParams = this.getQuery();
|
||||||
|
|
||||||
if(this.props.hasFiles) {
|
if(this.props.hasFiles) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
if(this.props.multipleFiles) {
|
if(this.props.enableLocalHashing && !queryParams.method) {
|
||||||
|
|
||||||
|
let queryParamsHash = Object.assign({}, queryParams);
|
||||||
|
queryParamsHash.method = 'hash';
|
||||||
|
|
||||||
|
let queryParamsUpload = Object.assign({}, queryParams);
|
||||||
|
queryParamsUpload.method = 'upload';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="file-drag-and-drop-dialog">Click or drag to add files</span>
|
<span className="file-drag-and-drop-dialog present-options">
|
||||||
);
|
<p>{getLangText('Would you rather')}</p>
|
||||||
} else {
|
<Link
|
||||||
return (
|
to={this.getPath()}
|
||||||
<span className="file-drag-and-drop-dialog">
|
query={queryParamsHash}>
|
||||||
<p>Drag a file here</p>
|
<span className="btn btn-default btn-sm">
|
||||||
<p>or</p>
|
{getLangText('Hash your work')}
|
||||||
<span
|
</span>
|
||||||
className="btn btn-default"
|
</Link>
|
||||||
onClick={this.props.onClick}>
|
|
||||||
choose a file to upload
|
<span> or </span>
|
||||||
</span>
|
|
||||||
|
<Link
|
||||||
|
to={this.getPath()}
|
||||||
|
query={queryParamsUpload}>
|
||||||
|
<span className="btn btn-default btn-sm">
|
||||||
|
{getLangText('Upload and hash your work')}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
if(this.props.multipleFiles) {
|
||||||
|
return (
|
||||||
|
<span className="file-drag-and-drop-dialog">
|
||||||
|
{getLangText('Click or drag to add files')}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let dialog = queryParams.method === 'hash' ? getLangText('choose a file to hash') : getLangText('choose a file to upload');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="file-drag-and-drop-dialog">
|
||||||
|
<p>{getLangText('Drag a file here')}</p>
|
||||||
|
<p>{getLangText('or')}</p>
|
||||||
|
<span
|
||||||
|
className="btn btn-default"
|
||||||
|
onClick={this.props.onClick}>
|
||||||
|
{dialog}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import FileDragAndDropPreviewImage from './file_drag_and_drop_preview_image';
|
import FileDragAndDropPreviewImage from './file_drag_and_drop_preview_image';
|
||||||
import FileDragAndDropPreviewOther from './file_drag_and_drop_preview_other';
|
import FileDragAndDropPreviewOther from './file_drag_and_drop_preview_other';
|
||||||
import { getLangText } from '../../utils/lang_utils.js'
|
import { getLangText } from '../../utils/lang_utils.js';
|
||||||
|
|
||||||
let FileDragAndDropPreview = React.createClass({
|
let FileDragAndDropPreview = React.createClass({
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import React from 'react';
|
|||||||
import ProgressBar from 'react-progressbar';
|
import ProgressBar from 'react-progressbar';
|
||||||
|
|
||||||
import AppConstants from '../../constants/application_constants';
|
import AppConstants from '../../constants/application_constants';
|
||||||
import { getLangText } from '../../utils/lang_utils.js'
|
import { getLangText } from '../../utils/lang_utils.js';
|
||||||
|
|
||||||
let FileDragAndDropPreviewImage = React.createClass({
|
let FileDragAndDropPreviewImage = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
@ -4,7 +4,7 @@ import React from 'react';
|
|||||||
import ProgressBar from 'react-progressbar';
|
import ProgressBar from 'react-progressbar';
|
||||||
|
|
||||||
import AppConstants from '../../constants/application_constants';
|
import AppConstants from '../../constants/application_constants';
|
||||||
import { getLangText } from '../../utils/lang_utils.js'
|
import { getLangText } from '../../utils/lang_utils.js';
|
||||||
|
|
||||||
let FileDragAndDropPreviewOther = React.createClass({
|
let FileDragAndDropPreviewOther = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react/addons';
|
import React from 'react/addons';
|
||||||
|
import Router from 'react-router';
|
||||||
import Raven from 'raven-js';
|
import Raven from 'raven-js';
|
||||||
|
import Q from 'q';
|
||||||
|
|
||||||
import { getCookie } from '../../utils/fetch_api_utils';
|
import { getCookie } from '../../utils/fetch_api_utils';
|
||||||
import { getLangText } from '../../utils/lang_utils';
|
import { getLangText } from '../../utils/lang_utils';
|
||||||
@ -16,6 +18,8 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
|
|||||||
|
|
||||||
import AppConstants from '../../constants/application_constants';
|
import AppConstants from '../../constants/application_constants';
|
||||||
|
|
||||||
|
import { computeHashOfFile } from '../../utils/file_utils';
|
||||||
|
|
||||||
var ReactS3FineUploader = React.createClass({
|
var ReactS3FineUploader = React.createClass({
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
@ -95,9 +99,23 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
isReadyForFormSubmission: React.PropTypes.func,
|
isReadyForFormSubmission: React.PropTypes.func,
|
||||||
areAssetsDownloadable: React.PropTypes.bool,
|
areAssetsDownloadable: React.PropTypes.bool,
|
||||||
areAssetsEditable: React.PropTypes.bool,
|
areAssetsEditable: React.PropTypes.bool,
|
||||||
defaultErrorMessage: React.PropTypes.string
|
defaultErrorMessage: React.PropTypes.string,
|
||||||
|
onInactive: React.PropTypes.func,
|
||||||
|
|
||||||
|
// We encountered some cases where people had difficulties to upload their
|
||||||
|
// works to ascribe due to a slow internet connection.
|
||||||
|
// One solution we found in the process of tackling this problem was to hash
|
||||||
|
// the file in the browser using md5 and then uploading the resulting text document instead
|
||||||
|
// of the actual file.
|
||||||
|
// This boolean essentially enables that behavior
|
||||||
|
enableLocalHashing: React.PropTypes.bool,
|
||||||
|
|
||||||
|
// automatically injected by React-Router
|
||||||
|
query: React.PropTypes.object
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mixins: [Router.State],
|
||||||
|
|
||||||
getDefaultProps() {
|
getDefaultProps() {
|
||||||
return {
|
return {
|
||||||
autoUpload: true,
|
autoUpload: true,
|
||||||
@ -120,7 +138,10 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
sendCredentials: true
|
sendCredentials: true
|
||||||
},
|
},
|
||||||
chunking: {
|
chunking: {
|
||||||
enabled: true
|
enabled: true,
|
||||||
|
concurrent: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
resume: {
|
resume: {
|
||||||
enabled: true
|
enabled: true
|
||||||
@ -132,7 +153,7 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
endpoint: null
|
endpoint: null
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
unsupportedBrowser: '<h3>Upload is not functional in IE7 as IE7 has no support for CORS!</h3>'
|
unsupportedBrowser: '<h3>' + getLangText('Upload is not functional in IE7 as IE7 has no support for CORS!') + '</h3>'
|
||||||
},
|
},
|
||||||
formatFileName: function(name){// fix maybe
|
formatFileName: function(name){// fix maybe
|
||||||
if (name !== undefined && name.length > 26) {
|
if (name !== undefined && name.length > 26) {
|
||||||
@ -141,7 +162,7 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
return name;
|
return name;
|
||||||
},
|
},
|
||||||
multiple: false,
|
multiple: false,
|
||||||
defaultErrorMessage: 'Unexpected error. Please contact us if this happens repeatedly.'
|
defaultErrorMessage: getLangText('Unexpected error. Please contact us if this happens repeatedly.')
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -149,14 +170,17 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
return {
|
return {
|
||||||
filesToUpload: [],
|
filesToUpload: [],
|
||||||
uploader: new fineUploader.s3.FineUploaderBasic(this.propsToConfig()),
|
uploader: new fineUploader.s3.FineUploaderBasic(this.propsToConfig()),
|
||||||
csrfToken: getCookie(AppConstants.csrftoken)
|
csrfToken: getCookie(AppConstants.csrftoken),
|
||||||
|
hashingProgress: -2
|
||||||
|
// -1: aborted
|
||||||
|
// -2: uninitialized
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
// since the csrf header is defined in this component's props,
|
|
||||||
// everytime the csrf cookie is changed we'll need to reinitalize
|
|
||||||
// fineuploader and update the actual csrf token
|
|
||||||
componentWillUpdate() {
|
componentWillUpdate() {
|
||||||
|
// since the csrf header is defined in this component's props,
|
||||||
|
// everytime the csrf cookie is changed we'll need to reinitalize
|
||||||
|
// fineuploader and update the actual csrf token
|
||||||
let potentiallyNewCSRFToken = getCookie(AppConstants.csrftoken);
|
let potentiallyNewCSRFToken = getCookie(AppConstants.csrftoken);
|
||||||
if(this.state.csrfToken !== potentiallyNewCSRFToken) {
|
if(this.state.csrfToken !== potentiallyNewCSRFToken) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -207,115 +231,127 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
requestKey(fileId) {
|
requestKey(fileId) {
|
||||||
let defer = new fineUploader.Promise();
|
|
||||||
let filename = this.state.uploader.getName(fileId);
|
let filename = this.state.uploader.getName(fileId);
|
||||||
let uuid = this.state.uploader.getUuid(fileId);
|
let uuid = this.state.uploader.getUuid(fileId);
|
||||||
|
|
||||||
window.fetch(this.props.keyRoutine.url, {
|
return Q.Promise((resolve, reject) => {
|
||||||
method: 'post',
|
window.fetch(this.props.keyRoutine.url, {
|
||||||
headers: {
|
method: 'post',
|
||||||
'Accept': 'application/json',
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Accept': 'application/json',
|
||||||
'X-CSRFToken': getCookie(AppConstants.csrftoken)
|
'Content-Type': 'application/json',
|
||||||
},
|
'X-CSRFToken': getCookie(AppConstants.csrftoken)
|
||||||
credentials: 'include',
|
},
|
||||||
body: JSON.stringify({
|
credentials: 'include',
|
||||||
'filename': filename,
|
body: JSON.stringify({
|
||||||
'category': this.props.keyRoutine.fileClass,
|
'filename': filename,
|
||||||
'uuid': uuid,
|
'category': this.props.keyRoutine.fileClass,
|
||||||
'piece_id': this.props.keyRoutine.pieceId
|
'uuid': uuid,
|
||||||
|
'piece_id': this.props.keyRoutine.pieceId
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
.then((res) => {
|
||||||
.then((res) => {
|
return res.json();
|
||||||
return res.json();
|
})
|
||||||
})
|
.then((res) =>{
|
||||||
.then((res) =>{
|
resolve(res.key);
|
||||||
defer.success(res.key);
|
})
|
||||||
})
|
.catch((err) => {
|
||||||
.catch((err) => {
|
reject(err);
|
||||||
defer.failure(err);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return defer;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createBlob(file) {
|
createBlob(file) {
|
||||||
let defer = new fineUploader.Promise();
|
return Q.Promise((resolve, reject) => {
|
||||||
|
window.fetch(this.props.createBlobRoutine.url, {
|
||||||
window.fetch(this.props.createBlobRoutine.url, {
|
method: 'post',
|
||||||
method: 'post',
|
headers: {
|
||||||
headers: {
|
'Accept': 'application/json',
|
||||||
'Accept': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'X-CSRFToken': getCookie(AppConstants.csrftoken)
|
||||||
'X-CSRFToken': getCookie(AppConstants.csrftoken)
|
},
|
||||||
},
|
credentials: 'include',
|
||||||
credentials: 'include',
|
body: JSON.stringify({
|
||||||
body: JSON.stringify({
|
'filename': file.name,
|
||||||
'filename': file.name,
|
'key': file.key,
|
||||||
'key': file.key,
|
'piece_id': this.props.createBlobRoutine.pieceId
|
||||||
'piece_id': this.props.createBlobRoutine.pieceId
|
})
|
||||||
})
|
})
|
||||||
})
|
.then((res) => {
|
||||||
.then((res) => {
|
return res.json();
|
||||||
return res.json();
|
})
|
||||||
})
|
.then((res) =>{
|
||||||
.then((res) =>{
|
if(res.otherdata) {
|
||||||
if(res.otherdata) {
|
file.s3Url = res.otherdata.url_safe;
|
||||||
file.s3Url = res.otherdata.url_safe;
|
file.s3UrlSafe = res.otherdata.url_safe;
|
||||||
file.s3UrlSafe = res.otherdata.url_safe;
|
} else if(res.digitalwork) {
|
||||||
} else if(res.digitalwork) {
|
file.s3Url = res.digitalwork.url_safe;
|
||||||
file.s3Url = res.digitalwork.url_safe;
|
file.s3UrlSafe = res.digitalwork.url_safe;
|
||||||
file.s3UrlSafe = res.digitalwork.url_safe;
|
} else {
|
||||||
} else {
|
throw new Error(getLangText('Could not find a url to download.'));
|
||||||
throw new Error('Could not find a url to download.');
|
}
|
||||||
}
|
resolve(res);
|
||||||
defer.success(res.key);
|
})
|
||||||
})
|
.catch((err) => {
|
||||||
.catch((err) => {
|
reject(err);
|
||||||
defer.failure(err);
|
console.logGlobal(err);
|
||||||
console.logGlobal(err);
|
});
|
||||||
});
|
});
|
||||||
return defer;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/* FineUploader specific callback function handlers */
|
/* FineUploader specific callback function handlers */
|
||||||
|
|
||||||
onComplete(id) {
|
onComplete(id) {
|
||||||
let files = this.state.filesToUpload;
|
let files = this.state.filesToUpload;
|
||||||
|
|
||||||
|
// Set the state of the completed file to 'upload successful' in order to
|
||||||
|
// remove it from the GUI
|
||||||
files[id].status = 'upload successful';
|
files[id].status = 'upload successful';
|
||||||
files[id].key = this.state.uploader.getKey(id);
|
files[id].key = this.state.uploader.getKey(id);
|
||||||
|
|
||||||
let newState = React.addons.update(this.state, {
|
let newState = React.addons.update(this.state, {
|
||||||
filesToUpload: { $set: files }
|
filesToUpload: { $set: files }
|
||||||
});
|
});
|
||||||
this.setState(newState);
|
|
||||||
this.createBlob(files[id]);
|
|
||||||
|
|
||||||
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey
|
this.setState(newState);
|
||||||
// are optional, we'll only trigger them when they're actually defined
|
|
||||||
if(this.props.submitKey) {
|
// Only after the blob has been created server-side, we can make the form submittable.
|
||||||
this.props.submitKey(files[id].key);
|
this.createBlob(files[id])
|
||||||
} else {
|
.then(() => {
|
||||||
console.warn('You didn\'t define submitKey in as a prop in react-s3-fine-uploader');
|
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey
|
||||||
}
|
// are optional, we'll only trigger them when they're actually defined
|
||||||
|
if(this.props.submitKey) {
|
||||||
|
this.props.submitKey(files[id].key);
|
||||||
|
} else {
|
||||||
|
console.warn('You didn\'t define submitKey in as a prop in react-s3-fine-uploader');
|
||||||
|
}
|
||||||
|
|
||||||
|
// for explanation, check comment of if statement above
|
||||||
|
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
|
||||||
|
// also, lets check if after the completion of this upload,
|
||||||
|
// the form is ready for submission or not
|
||||||
|
if(this.props.isReadyForFormSubmission(this.state.filesToUpload)) {
|
||||||
|
// if so, set uploadstatus to true
|
||||||
|
this.props.setIsUploadReady(true);
|
||||||
|
} else {
|
||||||
|
this.props.setIsUploadReady(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('You didn\'t define the functions isReadyForFormSubmission and/or setIsUploadReady in as a prop in react-s3-fine-uploader');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.logGlobal(err);
|
||||||
|
let notification = new GlobalNotificationModel(err.message, 'danger', 5000);
|
||||||
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// for explanation, check comment of if statement above
|
|
||||||
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
|
|
||||||
// also, lets check if after the completion of this upload,
|
|
||||||
// the form is ready for submission or not
|
|
||||||
if(this.props.isReadyForFormSubmission(this.state.filesToUpload)) {
|
|
||||||
// if so, set uploadstatus to true
|
|
||||||
this.props.setIsUploadReady(true);
|
|
||||||
} else {
|
|
||||||
this.props.setIsUploadReady(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn('You didn\'t define the functions isReadyForFormSubmission and/or setIsUploadReady in as a prop in react-s3-fine-uploader');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onError() {
|
onError(id, name, errorReason) {
|
||||||
Raven.captureException('react-fineuploader-error');
|
Raven.captureException(errorReason);
|
||||||
let notification = new GlobalNotificationModel(this.props.defaultErrorMessage, 'danger', 5000);
|
let notification = new GlobalNotificationModel(this.props.defaultErrorMessage, 'danger', 5000);
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
},
|
},
|
||||||
@ -323,7 +359,9 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
onValidate(data) {
|
onValidate(data) {
|
||||||
if(data.size > this.props.validation.sizeLimit) {
|
if(data.size > this.props.validation.sizeLimit) {
|
||||||
this.state.uploader.cancelAll();
|
this.state.uploader.cancelAll();
|
||||||
let notification = new GlobalNotificationModel('Your file is bigger than 10MB', 'danger', 5000);
|
|
||||||
|
let fileSizeInMegaBytes = this.props.validation.sizeLimit / 1000000;
|
||||||
|
let notification = new GlobalNotificationModel(getLangText('Your file is bigger than %d MB', fileSizeInMegaBytes), 'danger', 5000);
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -331,7 +369,7 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
onCancel(id) {
|
onCancel(id) {
|
||||||
this.removeFileWithIdFromFilesToUpload(id);
|
this.removeFileWithIdFromFilesToUpload(id);
|
||||||
|
|
||||||
let notification = new GlobalNotificationModel('File upload canceled', 'success', 5000);
|
let notification = new GlobalNotificationModel(getLangText('File upload canceled'), 'success', 5000);
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
|
|
||||||
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey
|
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey
|
||||||
@ -448,7 +486,7 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
if(this.state.uploader.pauseUpload(fileId)) {
|
if(this.state.uploader.pauseUpload(fileId)) {
|
||||||
this.setStatusOfFile(fileId, 'paused');
|
this.setStatusOfFile(fileId, 'paused');
|
||||||
} else {
|
} else {
|
||||||
throw new Error('File upload could not be paused.');
|
throw new Error(getLangText('File upload could not be paused.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -457,7 +495,7 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
if(this.state.uploader.continueUpload(fileId)) {
|
if(this.state.uploader.continueUpload(fileId)) {
|
||||||
this.setStatusOfFile(fileId, 'uploading');
|
this.setStatusOfFile(fileId, 'uploading');
|
||||||
} else {
|
} else {
|
||||||
throw new Error('File upload could not be resumed.');
|
throw new Error(getLangText('File upload could not be resumed.'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -481,10 +519,128 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state.uploader.addFiles(files);
|
// As mentioned already in the propTypes declaration, in some instances we need to calculate the
|
||||||
|
// md5 hash of a file locally and just upload a txt file containing that hash.
|
||||||
|
//
|
||||||
|
// In the view this only happens when the user is allowed to do local hashing as well
|
||||||
|
// as when the correct query parameter is present in the url ('hash' and not 'upload')
|
||||||
|
let queryParams = this.getQuery();
|
||||||
|
if(this.props.enableLocalHashing && queryParams && queryParams.method === 'hash') {
|
||||||
|
|
||||||
|
let convertedFilePromises = [];
|
||||||
|
let overallFileSize = 0;
|
||||||
|
// "files" is not a classical Javascript array but a Javascript FileList, therefore
|
||||||
|
// we can not use map to convert values
|
||||||
|
for(let i = 0; i < files.length; i++) {
|
||||||
|
|
||||||
|
// for calculating the overall progress of all submitted files
|
||||||
|
// we'll need to calculate the overall sum of all files' sizes
|
||||||
|
overallFileSize += files[i].size;
|
||||||
|
|
||||||
|
// also, we need to set the files' initial progress value
|
||||||
|
files[i].progress = 0;
|
||||||
|
|
||||||
|
// since the actual computation of a file's hash is an async task ,
|
||||||
|
// we're using promises to handle that
|
||||||
|
let hashedFilePromise = computeHashOfFile(files[i]);
|
||||||
|
convertedFilePromises.push(hashedFilePromise);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// To react after the computation of all files, we define the resolvement
|
||||||
|
// with the all function for iterables and essentially replace all original files
|
||||||
|
// with their txt representative
|
||||||
|
Q.all(convertedFilePromises)
|
||||||
|
.progress(({index, value: {progress, reject}}) => {
|
||||||
|
|
||||||
|
// hashing progress has been aborted from outside
|
||||||
|
// To get out of the executing, we need to call reject from the
|
||||||
|
// inside of the promise's execution.
|
||||||
|
// This is why we're passing (along with value) a function that essentially
|
||||||
|
// just does that (calling reject(err))
|
||||||
|
//
|
||||||
|
// In the promises catch method, we're then checking if the interruption
|
||||||
|
// was due to that error or another generic one.
|
||||||
|
if(this.state.hashingProgress === -1) {
|
||||||
|
reject(new Error(getLangText('Hashing canceled')));
|
||||||
|
}
|
||||||
|
|
||||||
|
// update file's progress
|
||||||
|
files[index].progress = progress;
|
||||||
|
|
||||||
|
// calculate weighted average for overall progress of all
|
||||||
|
// currently hashing files
|
||||||
|
let overallHashingProgress = 0;
|
||||||
|
for(let i = 0; i < files.length; i++) {
|
||||||
|
|
||||||
|
let filesSliceOfOverall = files[i].size / overallFileSize;
|
||||||
|
overallHashingProgress += filesSliceOfOverall * files[i].progress;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply by 100, since react-progressbar expects decimal numbers
|
||||||
|
this.setState({ hashingProgress: overallHashingProgress * 100});
|
||||||
|
|
||||||
|
})
|
||||||
|
.then((convertedFiles) => {
|
||||||
|
|
||||||
|
// clear hashing progress, since its done
|
||||||
|
this.setState({ hashingProgress: -2});
|
||||||
|
|
||||||
|
// actually replacing all files with their txt-hash representative
|
||||||
|
files = convertedFiles;
|
||||||
|
|
||||||
|
// routine for adding all the files submitted to fineuploader for actual uploading them
|
||||||
|
// to the server
|
||||||
|
this.state.uploader.addFiles(files);
|
||||||
|
this.synchronizeFileLists(files);
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
// If the error is that hashing has been canceled, we want to display a success
|
||||||
|
// message instead of a danger message
|
||||||
|
let typeOfMessage = 'danger';
|
||||||
|
|
||||||
|
if(err.message === getLangText('Hashing canceled')) {
|
||||||
|
typeOfMessage = 'success';
|
||||||
|
this.setState({ hashingProgress: -2 });
|
||||||
|
} else {
|
||||||
|
// if there was a more generic error, we also log it
|
||||||
|
console.logGlobal(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let notification = new GlobalNotificationModel(err.message, typeOfMessage, 5000);
|
||||||
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
|
});
|
||||||
|
|
||||||
|
// if we're not hashing the files locally, we're just going to hand them over to fineuploader
|
||||||
|
// to upload them to the server
|
||||||
|
} else {
|
||||||
|
this.state.uploader.addFiles(files);
|
||||||
|
this.synchronizeFileLists(files);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCancelHashing() {
|
||||||
|
// Every progress tick of the hashing function in handleUploadFile there is a
|
||||||
|
// check if this.state.hashingProgress is -1. If so, there is an error thrown that cancels
|
||||||
|
// the hashing of all files immediately.
|
||||||
|
this.setState({ hashingProgress: -1 });
|
||||||
|
},
|
||||||
|
|
||||||
|
// ReactFineUploader is essentially just a react layer around s3 fineuploader.
|
||||||
|
// However, since we need to display the status of a file (progress, uploading) as well as
|
||||||
|
// be able to execute actions on a currently uploading file we need to exactly sync the file list
|
||||||
|
// fineuploader is keeping internally.
|
||||||
|
//
|
||||||
|
// Unfortunately though fineuploader is not keeping all of a File object's properties after
|
||||||
|
// submitting them via .addFiles (it deletes the type, key as well as the ObjectUrl (which we need for
|
||||||
|
// displaying a thumbnail)), we need to readd them manually after each file that gets submitted
|
||||||
|
// to the dropzone.
|
||||||
|
// This method is essentially taking care of all these steps.
|
||||||
|
synchronizeFileLists(files) {
|
||||||
let oldFiles = this.state.filesToUpload;
|
let oldFiles = this.state.filesToUpload;
|
||||||
let oldAndNewFiles = this.state.uploader.getUploads();
|
let oldAndNewFiles = this.state.uploader.getUploads();
|
||||||
|
|
||||||
// Add fineuploader specific information to new files
|
// Add fineuploader specific information to new files
|
||||||
for(let i = 0; i < oldAndNewFiles.length; i++) {
|
for(let i = 0; i < oldAndNewFiles.length; i++) {
|
||||||
for(let j = 0; j < files.length; j++) {
|
for(let j = 0; j < files.length; j++) {
|
||||||
@ -543,6 +699,18 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isDropzoneInactive() {
|
||||||
|
let filesToDisplay = this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1);
|
||||||
|
let queryParams = this.getQuery();
|
||||||
|
|
||||||
|
if((this.props.enableLocalHashing && !queryParams.method) || !this.props.areAssetsEditable || !this.props.multiple && filesToDisplay.length > 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -554,10 +722,14 @@ var ReactS3FineUploader = React.createClass({
|
|||||||
handleCancelFile={this.handleCancelFile}
|
handleCancelFile={this.handleCancelFile}
|
||||||
handlePauseFile={this.handlePauseFile}
|
handlePauseFile={this.handlePauseFile}
|
||||||
handleResumeFile={this.handleResumeFile}
|
handleResumeFile={this.handleResumeFile}
|
||||||
|
handleCancelHashing={this.handleCancelHashing}
|
||||||
multiple={this.props.multiple}
|
multiple={this.props.multiple}
|
||||||
areAssetsDownloadable={this.props.areAssetsDownloadable}
|
areAssetsDownloadable={this.props.areAssetsDownloadable}
|
||||||
areAssetsEditable={this.props.areAssetsEditable}
|
areAssetsEditable={this.props.areAssetsEditable}
|
||||||
dropzoneInactive={!this.props.areAssetsEditable || !this.props.multiple && this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1).length > 0} />
|
onInactive={this.props.onInactive}
|
||||||
|
dropzoneInactive={this.isDropzoneInactive()}
|
||||||
|
hashingProgress={this.state.hashingProgress}
|
||||||
|
enableLocalHashing={this.props.enableLocalHashing} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Router from 'react-router';
|
import Router from 'react-router';
|
||||||
import Raven from 'raven-js';
|
|
||||||
|
|
||||||
import UserActions from '../actions/user_actions';
|
import UserActions from '../actions/user_actions';
|
||||||
import UserStore from '../stores/user_store';
|
import UserStore from '../stores/user_store';
|
||||||
|
|
||||||
import WhitelabelActions from '../actions/whitelabel_actions';
|
import WhitelabelActions from '../actions/whitelabel_actions';
|
||||||
import WhitelabelStore from '../stores/whitelabel_store';
|
import WhitelabelStore from '../stores/whitelabel_store';
|
||||||
|
import EventActions from '../actions/event_actions';
|
||||||
|
|
||||||
import Nav from 'react-bootstrap/lib/Nav';
|
import Nav from 'react-bootstrap/lib/Nav';
|
||||||
import Navbar from 'react-bootstrap/lib/Navbar';
|
import Navbar from 'react-bootstrap/lib/Navbar';
|
||||||
@ -84,19 +84,7 @@ let Header = React.createClass({
|
|||||||
this.setState(state);
|
this.setState(state);
|
||||||
|
|
||||||
if(this.state.currentUser && this.state.currentUser.email) {
|
if(this.state.currentUser && this.state.currentUser.email) {
|
||||||
// bootup intercom if the user is logged in
|
EventActions.profileDidLoad.defer(this.state.currentUser);
|
||||||
window.Intercom('boot', {
|
|
||||||
app_id: 'oboxh5w1',
|
|
||||||
email: this.state.currentUser.email,
|
|
||||||
subdomain: window.location.host.split('.')[0],
|
|
||||||
widget: {
|
|
||||||
activator: '#IntercomDefaultWidget'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Raven.setUserContext({
|
|
||||||
email: this.state.currentUser.email
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -14,7 +14,8 @@ let LoginContainer = React.createClass({
|
|||||||
propTypes: {
|
propTypes: {
|
||||||
message: React.PropTypes.string,
|
message: React.PropTypes.string,
|
||||||
redirectOnLoggedIn: React.PropTypes.bool,
|
redirectOnLoggedIn: React.PropTypes.bool,
|
||||||
redirectOnLoginSuccess: React.PropTypes.bool
|
redirectOnLoginSuccess: React.PropTypes.bool,
|
||||||
|
onLogin: React.PropTypes.func
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps() {
|
getDefaultProps() {
|
||||||
@ -31,7 +32,8 @@ let LoginContainer = React.createClass({
|
|||||||
<LoginForm
|
<LoginForm
|
||||||
redirectOnLoggedIn={this.props.redirectOnLoggedIn}
|
redirectOnLoggedIn={this.props.redirectOnLoggedIn}
|
||||||
redirectOnLoginSuccess={this.props.redirectOnLoginSuccess}
|
redirectOnLoginSuccess={this.props.redirectOnLoginSuccess}
|
||||||
message={this.props.message} />
|
message={this.props.message}
|
||||||
|
onLogin={this.props.onLogin}/>
|
||||||
<div className="ascribe-login-text">
|
<div className="ascribe-login-text">
|
||||||
{getLangText('Not an ascribe user')}? <Link to="signup">{getLangText('Sign up')}...</Link><br/>
|
{getLangText('Not an ascribe user')}? <Link to="signup">{getLangText('Sign up')}...</Link><br/>
|
||||||
{getLangText('Forgot my password')}? <Link to="password_reset">{getLangText('Rescue me')}...</Link>
|
{getLangText('Forgot my password')}? <Link to="password_reset">{getLangText('Rescue me')}...</Link>
|
||||||
|
@ -59,7 +59,7 @@ let RegisterPiece = React.createClass( {
|
|||||||
PieceListStore.getState(),
|
PieceListStore.getState(),
|
||||||
{
|
{
|
||||||
selectedLicense: 0,
|
selectedLicense: 0,
|
||||||
isFineUploaderEditable: false
|
isFineUploaderActive: false
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -82,14 +82,10 @@ let RegisterPiece = React.createClass( {
|
|||||||
onChange(state) {
|
onChange(state) {
|
||||||
this.setState(state);
|
this.setState(state);
|
||||||
|
|
||||||
// once the currentUser object from UserStore is defined (eventually the user was transitioned
|
if(this.state.currentUser && this.state.currentUser.email) {
|
||||||
// to the login form via the slider and successfully logged in), we can direct him back to the
|
|
||||||
// register_piece slide
|
|
||||||
if(state.currentUser && state.currentUser.email || this.state.currentUser && this.state.currentUser.email) {
|
|
||||||
this.refs.slidesContainer.setSlideNum(0);
|
|
||||||
// we should also make the fineuploader component editable again
|
// we should also make the fineuploader component editable again
|
||||||
this.setState({
|
this.setState({
|
||||||
isFineUploaderEditable: true
|
isFineUploaderActive: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -105,7 +101,8 @@ let RegisterPiece = React.createClass( {
|
|||||||
this.state.pageSize,
|
this.state.pageSize,
|
||||||
this.state.searchTerm,
|
this.state.searchTerm,
|
||||||
this.state.orderBy,
|
this.state.orderBy,
|
||||||
this.state.orderAsc);
|
this.state.orderAsc
|
||||||
|
);
|
||||||
|
|
||||||
this.transitionTo('piece', {pieceId: response.piece.id});
|
this.transitionTo('piece', {pieceId: response.piece.id});
|
||||||
},
|
},
|
||||||
@ -160,11 +157,25 @@ let RegisterPiece = React.createClass( {
|
|||||||
changeSlide() {
|
changeSlide() {
|
||||||
// only transition to the login store, if user is not logged in
|
// only transition to the login store, if user is not logged in
|
||||||
// ergo the currentUser object is not properly defined
|
// ergo the currentUser object is not properly defined
|
||||||
if(!this.state.currentUser.email) {
|
if(this.state.currentUser && !this.state.currentUser.email) {
|
||||||
this.refs.slidesContainer.setSlideNum(1);
|
this.refs.slidesContainer.setSlideNum(1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// basically redirects to the second slide (index: 1), when the user is not logged in
|
||||||
|
onLoggedOut() {
|
||||||
|
this.refs.slidesContainer.setSlideNum(1);
|
||||||
|
},
|
||||||
|
|
||||||
|
onLogin() {
|
||||||
|
// once the currentUser object from UserStore is defined (eventually the user was transitioned
|
||||||
|
// to the login form via the slider and successfully logged in), we can direct him back to the
|
||||||
|
// register_piece slide
|
||||||
|
if(this.state.currentUser && this.state.currentUser.email) {
|
||||||
|
window.history.back();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<SlidesContainer ref="slidesContainer">
|
<SlidesContainer ref="slidesContainer">
|
||||||
@ -175,8 +186,9 @@ let RegisterPiece = React.createClass( {
|
|||||||
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
|
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
|
||||||
<RegisterPieceForm
|
<RegisterPieceForm
|
||||||
{...this.props}
|
{...this.props}
|
||||||
isFineUploaderEditable={this.state.isFineUploaderEditable}
|
isFineUploaderActive={this.state.isFineUploaderActive}
|
||||||
handleSuccess={this.handleSuccess}>
|
handleSuccess={this.handleSuccess}
|
||||||
|
onLoggedOut={this.onLoggedOut}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
{this.getLicenses()}
|
{this.getLicenses()}
|
||||||
{this.getSpecifyEditions()}
|
{this.getSpecifyEditions()}
|
||||||
@ -188,7 +200,8 @@ let RegisterPiece = React.createClass( {
|
|||||||
<LoginContainer
|
<LoginContainer
|
||||||
message={getLangText('Please login before ascribing your work%s', '...')}
|
message={getLangText('Please login before ascribing your work%s', '...')}
|
||||||
redirectOnLoggedIn={false}
|
redirectOnLoggedIn={false}
|
||||||
redirectOnLoginSuccess={false}/>
|
redirectOnLoginSuccess={false}
|
||||||
|
onLogin={this.onLogin}/>
|
||||||
</div>
|
</div>
|
||||||
</SlidesContainer>
|
</SlidesContainer>
|
||||||
);
|
);
|
||||||
|
@ -20,6 +20,7 @@ import ReactS3FineUploader from './ascribe_uploader/react_s3_fine_uploader';
|
|||||||
import CollapsibleParagraph from './ascribe_collapsible/collapsible_paragraph';
|
import CollapsibleParagraph from './ascribe_collapsible/collapsible_paragraph';
|
||||||
import Form from './ascribe_forms/form';
|
import Form from './ascribe_forms/form';
|
||||||
import Property from './ascribe_forms/property';
|
import Property from './ascribe_forms/property';
|
||||||
|
import InputCheckbox from './ascribe_forms/input_checkbox';
|
||||||
|
|
||||||
import apiUrls from '../constants/api_urls';
|
import apiUrls from '../constants/api_urls';
|
||||||
import AppConstants from '../constants/application_constants';
|
import AppConstants from '../constants/application_constants';
|
||||||
@ -65,11 +66,18 @@ let AccountSettings = React.createClass({
|
|||||||
|
|
||||||
handleSuccess(){
|
handleSuccess(){
|
||||||
UserActions.fetchCurrentUser();
|
UserActions.fetchCurrentUser();
|
||||||
let notification = new GlobalNotificationModel(getLangText('username succesfully updated'), 'success', 5000);
|
let notification = new GlobalNotificationModel(getLangText('Settings succesfully updated'), 'success', 5000);
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getFormDataProfile(){
|
||||||
|
return {'email': this.state.currentUser.email};
|
||||||
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||||
|
let profile = null;
|
||||||
|
|
||||||
if (this.state.currentUser.username) {
|
if (this.state.currentUser.username) {
|
||||||
content = (
|
content = (
|
||||||
<Form
|
<Form
|
||||||
@ -97,6 +105,40 @@ let AccountSettings = React.createClass({
|
|||||||
<hr />
|
<hr />
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
profile = (
|
||||||
|
<Form
|
||||||
|
url={apiUrls.users_profile}
|
||||||
|
handleSuccess={this.handleSuccess}
|
||||||
|
getFormData={this.getFormDataProfile}>
|
||||||
|
<Property
|
||||||
|
name="hash_locally"
|
||||||
|
className="ascribe-settings-property-collapsible-toggle"
|
||||||
|
style={{paddingBottom: 0}}>
|
||||||
|
<InputCheckbox
|
||||||
|
defaultChecked={this.state.currentUser.profile.hash_locally}>
|
||||||
|
<span>
|
||||||
|
{' ' + getLangText('Enable hash option for slow connections. ' +
|
||||||
|
'Computes and uploads a hash of the work instead.')}
|
||||||
|
</span>
|
||||||
|
</InputCheckbox>
|
||||||
|
</Property>
|
||||||
|
{/*<Property
|
||||||
|
name='language'
|
||||||
|
label={getLangText('Choose your Language')}
|
||||||
|
editable={true}>
|
||||||
|
<select id="select-lang" name="language">
|
||||||
|
<option value="fr">
|
||||||
|
Français
|
||||||
|
</option>
|
||||||
|
<option value="en"
|
||||||
|
selected="selected">
|
||||||
|
English
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</Property>*/}
|
||||||
|
<hr />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<CollapsibleParagraph
|
<CollapsibleParagraph
|
||||||
@ -104,6 +146,7 @@ let AccountSettings = React.createClass({
|
|||||||
show={true}
|
show={true}
|
||||||
defaultExpanded={true}>
|
defaultExpanded={true}>
|
||||||
{content}
|
{content}
|
||||||
|
{profile}
|
||||||
{/*<Form
|
{/*<Form
|
||||||
url={AppConstants.serverUrl + 'api/users/set_language/'}>
|
url={AppConstants.serverUrl + 'api/users/set_language/'}>
|
||||||
<Property
|
<Property
|
||||||
@ -189,7 +232,6 @@ let LoanContractSettings = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleParagraph
|
<CollapsibleParagraph
|
||||||
title="Loan Contract Settings"
|
title="Loan Contract Settings"
|
||||||
@ -276,26 +318,30 @@ let APISettings = React.createClass({
|
|||||||
onChange(state) {
|
onChange(state) {
|
||||||
this.setState(state);
|
this.setState(state);
|
||||||
},
|
},
|
||||||
handleCreateSuccess: function(){
|
|
||||||
|
handleCreateSuccess() {
|
||||||
ApplicationActions.fetchApplication();
|
ApplicationActions.fetchApplication();
|
||||||
let notification = new GlobalNotificationModel(getLangText('Application successfully created'), 'success', 5000);
|
let notification = new GlobalNotificationModel(getLangText('Application successfully created'), 'success', 5000);
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleTokenRefresh: function(event){
|
handleTokenRefresh(event) {
|
||||||
let applicationName = event.target.getAttribute('data-id');
|
let applicationName = event.target.getAttribute('data-id');
|
||||||
ApplicationActions.refreshApplicationToken(applicationName);
|
ApplicationActions.refreshApplicationToken(applicationName);
|
||||||
|
|
||||||
let notification = new GlobalNotificationModel(getLangText('Token refreshed'), 'success', 2000);
|
let notification = new GlobalNotificationModel(getLangText('Token refreshed'), 'success', 2000);
|
||||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||||
if (this.state.applications.length > -1) {
|
if (this.state.applications.length > -1) {
|
||||||
content = this.state.applications.map(function(app) {
|
content = this.state.applications.map(function(app, i) {
|
||||||
return (
|
return (
|
||||||
<Property
|
<Property
|
||||||
name={app.name}
|
name={app.name}
|
||||||
label={app.name}>
|
label={app.name}
|
||||||
|
key={i}>
|
||||||
<div className="row-same-height">
|
<div className="row-same-height">
|
||||||
<div className="no-padding col-xs-6 col-sm-10 col-xs-height col-middle">
|
<div className="no-padding col-xs-6 col-sm-10 col-xs-height col-middle">
|
||||||
{'Bearer ' + app.bearer_token.token}
|
{'Bearer ' + app.bearer_token.token}
|
||||||
|
@ -49,6 +49,7 @@ let apiUrls = {
|
|||||||
'users_password_reset_request': AppConstants.apiEndpoint + 'users/request_reset_password/',
|
'users_password_reset_request': AppConstants.apiEndpoint + 'users/request_reset_password/',
|
||||||
'users_signup': AppConstants.apiEndpoint + 'users/',
|
'users_signup': AppConstants.apiEndpoint + 'users/',
|
||||||
'users_username': AppConstants.apiEndpoint + 'users/username/',
|
'users_username': AppConstants.apiEndpoint + 'users/username/',
|
||||||
|
'users_profile': AppConstants.apiEndpoint + 'users/profile/',
|
||||||
'wallet_settings': AppConstants.apiEndpoint + 'users/wallet_settings/',
|
'wallet_settings': AppConstants.apiEndpoint + 'users/wallet_settings/',
|
||||||
'whitelabel_settings': AppConstants.apiEndpoint + 'whitelabel/settings/${subdomain}/',
|
'whitelabel_settings': AppConstants.apiEndpoint + 'whitelabel/settings/${subdomain}/',
|
||||||
'delete_s3_file': AppConstants.serverUrl + 's3/delete/'
|
'delete_s3_file': AppConstants.serverUrl + 's3/delete/'
|
||||||
|
@ -22,7 +22,8 @@ let constants = {
|
|||||||
'name': 'Creative Commons France',
|
'name': 'Creative Commons France',
|
||||||
'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/public/creativecommons/cc.logo.sm.png',
|
'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/public/creativecommons/cc.logo.sm.png',
|
||||||
'permissions': ['register', 'edit', 'share', 'del_from_collection'],
|
'permissions': ['register', 'edit', 'share', 'del_from_collection'],
|
||||||
'type': 'wallet'
|
'type': 'wallet',
|
||||||
|
'ga': 'UA-60614729-4'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'subdomain': 'cc-staging',
|
'subdomain': 'cc-staging',
|
||||||
@ -36,7 +37,8 @@ let constants = {
|
|||||||
'name': 'Sluice Art Fair',
|
'name': 'Sluice Art Fair',
|
||||||
'logo': 'http://sluice.info/images/logo.gif',
|
'logo': 'http://sluice.info/images/logo.gif',
|
||||||
'permissions': ['register', 'edit', 'share', 'del_from_collection'],
|
'permissions': ['register', 'edit', 'share', 'del_from_collection'],
|
||||||
'type': 'prize'
|
'type': 'prize',
|
||||||
|
'ga': 'UA-60614729-5'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'subdomain': 'sluice-staging',
|
'subdomain': 'sluice-staging',
|
||||||
@ -46,6 +48,10 @@ let constants = {
|
|||||||
'type': 'prize'
|
'type': 'prize'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
'defaultDomain': {
|
||||||
|
'type': 'default',
|
||||||
|
'ga': 'UA-60614729-2'
|
||||||
|
},
|
||||||
|
|
||||||
// in case of whitelabel customization, we store stuff here
|
// in case of whitelabel customization, we store stuff here
|
||||||
'whitelabel': {},
|
'whitelabel': {},
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import Q from 'q';
|
||||||
|
|
||||||
let mapAttr = {
|
let mapAttr = {
|
||||||
link: 'href',
|
link: 'href',
|
||||||
script: 'src'
|
script: 'src'
|
||||||
@ -25,7 +27,7 @@ let InjectInHeadMixin = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
injectTag(tag, src) {
|
injectTag(tag, src) {
|
||||||
let promise = new Promise((resolve, reject) => {
|
return Q.Promise((resolve, reject) => {
|
||||||
if (InjectInHeadMixin.isPresent(tag, src)) {
|
if (InjectInHeadMixin.isPresent(tag, src)) {
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
@ -44,8 +46,6 @@ let InjectInHeadMixin = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return promise;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
injectStylesheet(src) {
|
injectStylesheet(src) {
|
||||||
|
@ -13,9 +13,9 @@ class CoaStore {
|
|||||||
onUpdateCoa(coa) {
|
onUpdateCoa(coa) {
|
||||||
this.coa = coa;
|
this.coa = coa;
|
||||||
}
|
}
|
||||||
onErrorCoa(err) {
|
|
||||||
|
onFlushCoa() {
|
||||||
this.coa = {};
|
this.coa = {};
|
||||||
//this.coa = err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
30
js/third_party/debug.js
vendored
Normal file
30
js/third_party/debug.js
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import alt from '../alt';
|
||||||
|
import EventActions from '../actions/event_actions';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class DebugHandler {
|
||||||
|
constructor() {
|
||||||
|
let symbols = [];
|
||||||
|
|
||||||
|
for (let k in EventActions) {
|
||||||
|
if (typeof EventActions[k] === 'symbol') {
|
||||||
|
symbols.push(EventActions[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bindListeners({
|
||||||
|
onWhateverEvent: symbols
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onWhateverEvent() {
|
||||||
|
let args = arguments[0];
|
||||||
|
let symbol = arguments[1];
|
||||||
|
console.debug(symbol, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createStore(DebugHandler, 'DebugHandler');
|
27
js/third_party/ga.js
vendored
Normal file
27
js/third_party/ga.js
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import alt from '../alt';
|
||||||
|
import EventActions from '../actions/event_actions';
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleAnalyticsHandler {
|
||||||
|
constructor() {
|
||||||
|
this.bindActions(EventActions);
|
||||||
|
}
|
||||||
|
|
||||||
|
onRouteDidChange() {
|
||||||
|
window.ga('send', 'pageview');
|
||||||
|
}
|
||||||
|
|
||||||
|
onApplicationWillBoot(settings) {
|
||||||
|
if (settings.ga) {
|
||||||
|
window.ga('create', settings.ga, 'auto');
|
||||||
|
console.log('Google Analytics loaded');
|
||||||
|
} else {
|
||||||
|
console.log('Cannot load Google Analytics: no tracking code provided');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createStore(GoogleAnalyticsHandler, 'GoogleAnalyticsHandler');
|
34
js/third_party/intercom.js
vendored
Normal file
34
js/third_party/intercom.js
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import alt from '../alt';
|
||||||
|
import EventActions from '../actions/event_actions';
|
||||||
|
|
||||||
|
|
||||||
|
class IntercomHandler {
|
||||||
|
constructor() {
|
||||||
|
this.bindActions(EventActions);
|
||||||
|
this.loaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onProfileDidLoad(profile) {
|
||||||
|
if (this.loaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
Intercom('boot', {
|
||||||
|
/* eslint-enable */
|
||||||
|
app_id: 'oboxh5w1',
|
||||||
|
email: profile.email,
|
||||||
|
subdomain: window.location.host.split('.')[0],
|
||||||
|
widget: {
|
||||||
|
activator: '#IntercomDefaultWidget'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('Intercom loaded');
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createStore(IntercomHandler, 'IntercomHandler');
|
28
js/third_party/raven.js
vendored
Normal file
28
js/third_party/raven.js
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import alt from '../alt';
|
||||||
|
import EventActions from '../actions/event_actions';
|
||||||
|
|
||||||
|
import Raven from 'raven-js';
|
||||||
|
|
||||||
|
|
||||||
|
class RavenHandler {
|
||||||
|
constructor() {
|
||||||
|
this.bindActions(EventActions);
|
||||||
|
this.loaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onProfileDidLoad(profile) {
|
||||||
|
if (this.loaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Raven.setUserContext({
|
||||||
|
email: profile.email
|
||||||
|
});
|
||||||
|
console.log('Raven loaded');
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createStore(RavenHandler, 'RavenHandler');
|
@ -8,8 +8,9 @@ export function getSubdomainSettings(subdomain) {
|
|||||||
if(settings.length === 1) {
|
if(settings.length === 1) {
|
||||||
return settings[0];
|
return settings[0];
|
||||||
} else if(settings.length === 0) {
|
} else if(settings.length === 0) {
|
||||||
throw new Error('There are no subdomain settings for the subdomain: ' + subdomain);
|
return appConstants.defaultDomain;
|
||||||
|
// throw new Error('There are no subdomain settings for the subdomain: ' + subdomain);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Matched multiple subdomains. Adjust constants file.');
|
throw new Error('Matched multiple subdomains. Adjust constants file.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import Q from 'q';
|
||||||
|
|
||||||
import { sanitize } from './general_utils';
|
import { sanitize } from './general_utils';
|
||||||
import AppConstants from '../constants/application_constants';
|
import AppConstants from '../constants/application_constants';
|
||||||
|
|
||||||
@ -86,7 +88,7 @@ export function getCookie(name) {
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
export function fetchImageAsBlob(url) {
|
export function fetchImageAsBlob(url) {
|
||||||
return new Promise((resolve, reject) => {
|
return Q.Promise((resolve, reject) => {
|
||||||
let xhr = new XMLHttpRequest();
|
let xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
xhr.open('GET', url, true);
|
xhr.open('GET', url, true);
|
||||||
|
86
js/utils/file_utils.js
Normal file
86
js/utils/file_utils.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import Q from 'q';
|
||||||
|
import SparkMD5 from 'spark-md5';
|
||||||
|
|
||||||
|
import { getLangText } from './lang_utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a string, creates a text file and returns the URL
|
||||||
|
*
|
||||||
|
* @param {string} text regular javascript string
|
||||||
|
* @return {string} regular javascript string
|
||||||
|
*/
|
||||||
|
function makeTextFile(text, file) {
|
||||||
|
let textFileBlob = new Blob([text], {type: 'text/plain'});
|
||||||
|
let textFile = new File([textFileBlob], 'hash-of-' + file.name + '.txt', {
|
||||||
|
lastModifiedDate: file.lastModifiedDate,
|
||||||
|
lastModified: file.lastModified,
|
||||||
|
type: 'text/plain'
|
||||||
|
});
|
||||||
|
|
||||||
|
return textFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a file Object, computes the MD5 hash and returns the URL of the textfile with the hash
|
||||||
|
*
|
||||||
|
* @param {File} file javascript File object
|
||||||
|
* @return {string} regular javascript string
|
||||||
|
*/
|
||||||
|
export function computeHashOfFile(file) {
|
||||||
|
return Q.Promise((resolve, reject, notify) => {
|
||||||
|
let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
|
||||||
|
let chunkSize = 2097152; // Read in chunks of 2MB
|
||||||
|
let chunks = Math.ceil(file.size / chunkSize);
|
||||||
|
let currentChunk = 0;
|
||||||
|
let spark = new SparkMD5.ArrayBuffer();
|
||||||
|
let fileReader = new FileReader();
|
||||||
|
|
||||||
|
let startTime = new Date();
|
||||||
|
|
||||||
|
// comment: We should convert this to es6 at some point, however if so please consider that
|
||||||
|
// an arrow function will get rid of the function's scope...
|
||||||
|
fileReader.onload = function(e) {
|
||||||
|
//console.log('read chunk nr', currentChunk + 1, 'of', chunks);
|
||||||
|
spark.append(e.target.result); // Append array buffer
|
||||||
|
currentChunk++;
|
||||||
|
|
||||||
|
if (currentChunk < chunks) {
|
||||||
|
loadNext();
|
||||||
|
} else {
|
||||||
|
let fileHash = spark.end();
|
||||||
|
|
||||||
|
console.info('computed hash %s (took %d s)',
|
||||||
|
fileHash,
|
||||||
|
Math.round(((new Date() - startTime) / 1000) % 60)); // Compute hash
|
||||||
|
|
||||||
|
let blobTextFile = makeTextFile(fileHash, file);
|
||||||
|
resolve(blobTextFile);
|
||||||
|
}
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
fileReader.onerror = function () {
|
||||||
|
reject(new Error(getLangText('We weren\'t able to hash your file locally. Try to upload it manually or consider contact us.')));
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadNext() {
|
||||||
|
var start = currentChunk * chunkSize,
|
||||||
|
end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
|
||||||
|
|
||||||
|
// send progress
|
||||||
|
// Due to the fact that progressHandler and notify are going to be removed in v2
|
||||||
|
// of Q, the functionality of throwing errors in the progressHandler will not be implemented
|
||||||
|
// anymore. To still be able to throw an error however, we can just expose the promise's reject
|
||||||
|
// method to the .progress function to stop the execution immediately.
|
||||||
|
notify({
|
||||||
|
progress: start / file.size,
|
||||||
|
reject
|
||||||
|
});
|
||||||
|
|
||||||
|
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNext();
|
||||||
|
});
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import Q from 'q';
|
||||||
|
|
||||||
import { argsToQueryParams, getCookie } from '../utils/fetch_api_utils';
|
import { argsToQueryParams, getCookie } from '../utils/fetch_api_utils';
|
||||||
|
|
||||||
import AppConstants from '../constants/application_constants';
|
import AppConstants from '../constants/application_constants';
|
||||||
@ -22,7 +24,7 @@ class Requests {
|
|||||||
throw new Error(response.status + ' - ' + response.statusText + ' - on URL:' + response.url);
|
throw new Error(response.status + ' - ' + response.statusText + ' - on URL:' + response.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return Q.Promise((resolve, reject) => {
|
||||||
response.text()
|
response.text()
|
||||||
.then((responseText) => {
|
.then((responseText) => {
|
||||||
// If the responses' body does not contain any data,
|
// If the responses' body does not contain any data,
|
||||||
|
@ -49,7 +49,6 @@
|
|||||||
"classnames": "^1.2.2",
|
"classnames": "^1.2.2",
|
||||||
"compression": "^1.4.4",
|
"compression": "^1.4.4",
|
||||||
"envify": "^3.4.0",
|
"envify": "^3.4.0",
|
||||||
"es6-promise": "^2.1.1",
|
|
||||||
"eslint": "^0.22.1",
|
"eslint": "^0.22.1",
|
||||||
"eslint-plugin-react": "^2.5.0",
|
"eslint-plugin-react": "^2.5.0",
|
||||||
"express": "^4.12.4",
|
"express": "^4.12.4",
|
||||||
@ -69,6 +68,7 @@
|
|||||||
"jest-cli": "^0.4.0",
|
"jest-cli": "^0.4.0",
|
||||||
"lodash": "^3.9.3",
|
"lodash": "^3.9.3",
|
||||||
"object-assign": "^2.0.0",
|
"object-assign": "^2.0.0",
|
||||||
|
"q": "^1.4.1",
|
||||||
"raven-js": "^1.1.19",
|
"raven-js": "^1.1.19",
|
||||||
"react": "^0.13.2",
|
"react": "^0.13.2",
|
||||||
"react-bootstrap": "~0.22.6",
|
"react-bootstrap": "~0.22.6",
|
||||||
@ -79,6 +79,7 @@
|
|||||||
"react-textarea-autosize": "^2.2.3",
|
"react-textarea-autosize": "^2.2.3",
|
||||||
"reactify": "^1.1.0",
|
"reactify": "^1.1.0",
|
||||||
"shmui": "^0.1.0",
|
"shmui": "^0.1.0",
|
||||||
|
"spark-md5": "~1.0.0",
|
||||||
"uglifyjs": "^2.4.10",
|
"uglifyjs": "^2.4.10",
|
||||||
"vinyl-buffer": "^1.0.0",
|
"vinyl-buffer": "^1.0.0",
|
||||||
"vinyl-source-stream": "^1.1.0",
|
"vinyl-source-stream": "^1.1.0",
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
$ascribe-accordion-list-item-height: 8em;
|
$ascribe-accordion-list-item-height: 8em;
|
||||||
$ascribe-accordion-list-font: 'Source Sans Pro';
|
$ascribe-accordion-list-font: 'Source Sans Pro';
|
||||||
|
|
||||||
|
.ascribe-accordion-list {
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
.ascribe-accordion-list-item {
|
.ascribe-accordion-list-item {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
.ascribe-media-player {
|
.ascribe-media-player {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
video {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
video,
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-other {
|
.media-other {
|
||||||
|
@ -2,16 +2,17 @@
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ascribe-sliding-container {
|
.ascribe-sliding-container {
|
||||||
transition: transform 1s cubic-bezier(0.23, 1, 0.32, 1);
|
transition: transform 1s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ascribe-slide {
|
.ascribe-slide {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 1px;
|
min-height: 1px;
|
||||||
padding-left: 15px;
|
|
||||||
padding-right: 15px;
|
|
||||||
float:left;
|
float:left;
|
||||||
}
|
}
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
cursor: default !important;
|
cursor: default !important;
|
||||||
|
|
||||||
padding: 1.5em 1.5em 1.5em 0;
|
padding: 1.5em 0 1.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inactive-dropzone {
|
.inactive-dropzone {
|
||||||
@ -19,6 +19,16 @@
|
|||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.present-options {
|
||||||
|
> p {
|
||||||
|
margin-bottom: .75em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
margin: 0 1em 0 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.file-drag-and-drop .file-drag-and-drop-dialog > p:first-child {
|
.file-drag-and-drop .file-drag-and-drop-dialog > p:first-child {
|
||||||
font-size: 1.5em !important;
|
font-size: 1.5em !important;
|
||||||
|
|
||||||
|
@ -41,8 +41,8 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ascribe-default-app {
|
.ascribe-default-app {
|
||||||
max-width: 90%;
|
|
||||||
padding-top: 70px;
|
padding-top: 70px;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
|
Loading…
Reference in New Issue
Block a user